Este script não deve ser instalado diretamente. Este script é uma biblioteca de outros scripts para incluir com o diretório meta // @require https://update.greasyfork.org/scripts/464425/1178359/MyContextMenu.js
// ==UserScript==
// @name MyContextMenu
// @description http://https://wish123.cnblogs.com/?MyContextMenu
// @version 1.0
// @description 原生js右键弹出菜单
// @author Wilson
// @license MIT
// modify by https://github.com/electerious/basicContext/
(function() {
if(document.querySelector("#myContextMenuStyle")) {
return;
}
let style = `
<style id="myModalStyle">
/* base css */
.basicContext,
.basicContext * {
box-sizing: border-box;
}
.basicContextContainer {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1000;
-webkit-tap-highlight-color: transparent;
}
.basicContext {
position: absolute;
opacity: 0;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.basicContext__item {
cursor: pointer;
}
.basicContext__item--separator {
float: left;
width: 100%;
height: 1px;
cursor: default;
}
.basicContext__item--disabled {
cursor: default;
}
.basicContext__data {
min-width: 140px;
padding-right: 20px;
text-align: left;
white-space: nowrap;
}
.basicContext__icon {
display: inline-block;
}
.basicContext--scrollable {
height: 100%;
-webkit-overflow-scrolling: touch;
overflow-y: auto;
}
.basicContext--scrollable .basicContext__data {
min-width: 160px;
}
/* default theme css */
.basicContext {
padding: 6px;
background-color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.basicContext__item {
margin-bottom: 2px;
}
.basicContext__item--separator {
margin: 4px 0;
background-color: rgba(0, 0, 0, 0.1);
}
.basicContext__item--disabled {
opacity: 0.5;
}
.basicContext__item:last-child {
margin-bottom: 0;
}
.basicContext__data {
padding: 6px 8px;
color: #333;
border-radius: 2px;
}
.basicContext__item:not(.basicContext__item--disabled):hover
.basicContext__data {
color: #fff;
background-color: #4393e6;
}
.basicContext__item:not(.basicContext__item--disabled):active
.basicContext__data {
background-color: #1d79d9;
}
.basicContext__icon {
margin-right: 10px;
width: 12px;
text-align: center;
}
/* [自定义] 自定义css样式 */
.basicContextContainer{
width: auto;
height: auto;
}
.basicContext {
padding: 0px;
min-width: 150px;
height: auto;
background-color: rgba(241, 240, 240, 0.96);
}
.basicContext table{
padding: 4px 0;
width: 100%;
}
.basicContext__item {
margin-bottom: 0px;
}
.basicContext__data {
padding: 2px 12px;
border-radius:0px;
font-size: 13.8px;
color: #3a383a;
}
.basicContext__item--separator {
margin: 2px 0;
}
.basicContext__item:not(.basicContext__item--disabled):hover .basicContext__data {
background-color: #4d92f8;
}
</style>
`
document.body.insertAdjacentHTML("beforeend", style);
})();
"use strict";
!(function (basicContext, callback) {
"undefined" != typeof module && module.exports
? (module.exports = callback())
: "function" == typeof define && define.amd
? define(callback)
: (window[basicContext] = callback());
})("basicContext", function () {
let overflow = null
const ITEM = 'item',
SEPARATOR = 'separator'
const dom = function(elem = '') {
return document.querySelector('.basicContext ' + elem)
}
const valid = function(item = {}) {
let emptyItem = (Object.keys(item).length===0 ? true : false)
if (emptyItem===true) item.type = SEPARATOR
if (item.type==null) item.type = ITEM
if (item.class==null) item.class = ''
if (item.visible!==false) item.visible = true
if (item.icon==null) item.icon = null
if (item.title==null) item.title = 'Undefined'
// Add disabled class when item disabled
if (item.disabled!==true) item.disabled = false
if (item.disabled===true) item.class += ' basicContext__item--disabled'
// Item requires a function when
// it's not a separator and not disabled
if (item.fn==null && item.type!==SEPARATOR && item.disabled===false) {
console.warn(`Missing fn for item '${ item.title }'`)
return false
}
return true
}
const buildItem = function(item, num) {
let html = '',
span = ''
// Parse and validate item
if (valid(item)===false) return ''
// Skip when invisible
if (item.visible===false) return ''
// Give item a unique number
item.num = num
// Generate span/icon-element
if (item.icon!==null) span = `<span class='basicContext__icon ${ item.icon }'></span>`
// [自定义]
item.extAttr = item.extAttr || ""
// Generate item
if (item.type===ITEM) {
html = `
<tr class='basicContext__item ${ item.class }'>
<td class='basicContext__data' data-num='${ item.num }' ${item.extAttr}>${ span }${ item.title }</td>
</tr>
`
} else if (item.type===SEPARATOR) {
html = `
<tr class='basicContext__item basicContext__item--separator'></tr>
`
}
return html
}
const build = function(items) {
let html = ''
html += `
<div class='basicContextContainer'>
<div class='basicContext'>
<table cellspacing="0">
<tbody>
`
items.forEach((item, i) => html += buildItem(item, i))
html += `
</tbody>
</table>
</div>
</div>
`
return html
}
const getNormalizedEvent = function(e = {}) {
let pos = {
x : e.clientX,
y : e.clientY
}
if (e.type==='touchend' && (pos.x==null || pos.y==null)) {
// We need to capture clientX and clientY from original event
// when the event 'touchend' does not return the touch position
let touches = e.changedTouches
if (touches!=null&&touches.length>0) {
pos.x = touches[0].clientX
pos.y = touches[0].clientY
}
}
// Position unknown
if (pos.x==null || pos.x < 0) pos.x = 0
if (pos.y==null || pos.y < 0) pos.y = 0
return pos
}
const getPosition = function(e, context) {
// Get the click position
let normalizedEvent = getNormalizedEvent(e)
// Set the initial position
let x = normalizedEvent.x,
y = normalizedEvent.y
// Get size of browser
let browserSize = {
width : window.innerWidth,
height : window.innerHeight
}
// Get size of context
let contextSize = {
width : context.offsetWidth,
height : context.offsetHeight
}
// Fix position based on context and browser size
if ((x + contextSize.width) > browserSize.width) x = x - ((x + contextSize.width) - browserSize.width)
if ((y + contextSize.height) > browserSize.height) y = y - ((y + contextSize.height) - browserSize.height)
// Make context scrollable and start at the top of the browser
// when context is higher than the browser
if (contextSize.height > browserSize.height) {
y = 0
context.classList.add('basicContext--scrollable')
}
// Calculate the relative position of the mouse to the context
let rx = normalizedEvent.x - x,
ry = normalizedEvent.y - y
return { x, y, rx, ry }
}
const bind = function(item = {}) {
if (item.fn==null) return false
if (item.visible===false) return false
if (item.disabled===true) return false
dom(`td[data-num='${ item.num }']`).onclick = item.fn
dom(`td[data-num='${ item.num }']`).oncontextmenu = item.fn
return true
}
const show = function(items, e, fnClose, fnCallback) {
//[自定义] delete old menu
let basicContextContainer = document.querySelector('.basicContextContainer');
if(basicContextContainer){
basicContextContainer.remove();
}
// Build context
let html = build(items)
// Add context to the body
document.body.insertAdjacentHTML('beforeend', html)
// Save current overflow and block scrolling of site
if (overflow==null) {
overflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
}
// Cache the context
let context = dom()
// Calculate position
let position = getPosition(e, context)
// Set position
context.style.left = `${ position.x }px`
context.style.top = `${ position.y }px`
context.style.transformOrigin = `${ position.rx }px ${ position.ry }px`
context.style.opacity = 1
// Close fn fallback
if (fnClose==null) fnClose = close
// Bind click on background
context.parentElement.onclick = fnClose
context.parentElement.oncontextmenu = fnClose
// Bind click on items
items.forEach(bind)
// Do not trigger default event or further propagation
if (typeof e.preventDefault === 'function') e.preventDefault()
if (typeof e.stopPropagation === 'function') e.stopPropagation()
// Call callback when a function
if (typeof fnCallback === 'function') fnCallback()
return true
}
const visible = function() {
let elem = dom()
if (elem==null || elem.length===0) return false
else return true
}
const close = function() {
if (visible()===false) return false
let container = document.querySelector('.basicContextContainer')
container.parentElement.removeChild(container)
// Reset overflow to its original value
if (overflow!=null) {
document.body.style.overflow = overflow
overflow = null
}
return true
}
return {
ITEM,
SEPARATOR,
show,
visible,
close
}
});
//[自定义] 解决basicContextContainer出现系统菜单的bug
document.addEventListener('click', function(e){
if(basicContext) basicContext.close();
});
document.addEventListener('contextmenu', function(e){
if(['basicContextContainer','basicContext'].indexOf(e.target.className)!==-1) {
e.preventDefault();
return false;
}
});
//使用示例
// const clicked = function(e) {
// console.log(e.target.innerHTML);
// }
// document.querySelector('.my-context-menu-btn').addEventListener('contextmenu', function(e){
// const items = [
// { title: '新标签打开链接', extAttr: "data-name='new-blank'", fn: clicked },
// { },
// { title: '复制链接地址', extAttr: "data-name='copy-link'", fn: clicked },
// { title: '复制选中的文本', extAttr: "data-name='copy-text'", fn: clicked, disabled: true },
// { title: '复制响应数据', extAttr: "data-name='copy-response'", fn: clicked},
// { },
// { title: '复制为cURL格式', extAttr: "data-name='copy-curl'", fn: clicked},
// { title: '复制为fetch格式', extAttr: "data-name='copy-fetch'", fn: clicked},
// { title: '复制为await格式', extAttr: "data-name='copy-await'", fn: clicked},
// { title: '复制为xhr格式', extAttr: "data-name='copy-xhr'", fn: clicked},
// { title: '复制为分享链接', extAttr: "data-name='copy-share'", fn: clicked},
// { },
// { title: '删除该请求', extAttr: "data-name='del-request'", fn: clicked},
// { title: '删除所有请求', extAttr: "data-name='del-all-request'", fn: clicked }
// ]
// basicContext.show(items, e);
// });