您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A html5 contextmenu library
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/33034/705360/GM_context.js
// ==UserScript== // @name GM_context // @version 0.2.1 // @description A html5 contextmenu library // @supportURL https://github.com/eight04/GM_context/issues // @license MIT // @author eight04 <[email protected]> (https://github.com/eight04) // @homepageURL https://github.com/eight04/GM_context // @compatible firefox >=8 // @grant none // @include * // ==/UserScript== var GM_context = (function (exports) { 'use strict'; const EDITABLE_INPUT = {text: true, number: true, email: true, search: true, tel: true, url: true}; const PROP_EXCLUDE = {parent: true, items: true, onclick: true, onchange: true}; let menus; let contextEvent; let contextSelection; let menuContainer; let isInit; let increaseNumber = 1; function objectAssign(target, ref, exclude = {}) { for (const key in ref) { if (!exclude[key]) { target[key] = ref[key]; } } return target; } function init() { isInit = true; menus = new Set; document.addEventListener("contextmenu", e => { contextEvent = e; contextSelection = document.getSelection() + ""; const context = getContext(e); const matchedMenus = [...menus] .filter(m => (!m.context || m.context.some(c => context.has(c))) && (!m.oncontext || m.oncontext(e) !== false) ); if (!matchedMenus.length) return; const {el: container, destroy: destroyContainer} = createContainer(e); const removeMenus = []; for (const menu of matchedMenus) { if (!menu.isBuilt) { buildMenu(menu); } if (!menu.static) { updateLabel(menu.items); } removeMenus.push(appendMenu(container, menu)); } setTimeout(() => { for (const removeMenu of removeMenus) { removeMenu(); } destroyContainer(); }); }); } function inc() { return increaseNumber++; } // check if there are dynamic label function checkStatic(menu) { return checkItems(menu.items); function checkItems(items) { for (const item of items) { if (item.label && item.label.includes("%s")) { return false; } if (item.items && checkItems(item.items)) { return false; } } return true; } } function updateLabel(items) { for (const item of items) { if (item.label && item.el) { item.el.label = buildLabel(item.label); } if (item.items) { updateLabel(item.items); } } } function createContainer(e) { let el = e.target; while (!el.contextMenu) { if (el == document.documentElement) { if (!menuContainer) { menuContainer = document.createElement("menu"); menuContainer.type = "context"; menuContainer.id = "gm-context-menu"; document.body.appendChild(menuContainer); } el.setAttribute("contextmenu", menuContainer.id); break; } el = el.parentNode; } return { el: el.contextMenu, destroy() { if (el.contextMenu == menuContainer) { el.removeAttribute("contextmenu"); } } }; } function getContext(e) { const el = e.target; const context = new Set; if (el.nodeName == "IMG") { context.add("image"); } if (el.closest("a")) { context.add("link"); } if (el.isContentEditable || el.nodeName == "INPUT" && EDITABLE_INPUT[el.type] || el.nodeName == "TEXTAREA" ) { context.add("editable"); } if (!document.getSelection().isCollapsed) { context.add("selection"); } if (!context.size) { context.add("page"); } return context; } function buildMenu(menu) { const el = buildItems(null, menu.items); menu.startEl = document.createComment(`<menu ${menu.id}>`); el.insertBefore(menu.startEl, el.childNodes[0]); menu.endEl = document.createComment("</menu>"); el.appendChild(menu.endEl); if (menu.static == null) { menu.static = checkStatic(menu); } menu.frag = el; menu.isBuilt = true; } function buildLabel(s) { return s.replace(/%s/g, contextSelection); } // build item's element function buildItem(parent, item) { let el; item.parent = parent; if (item.type == "submenu") { el = document.createElement("menu"); objectAssign(el, item, PROP_EXCLUDE); el.appendChild(buildItems(item, item.items)); } else if (item.type == "separator") { el = document.createElement("hr"); } else if (item.type == "checkbox") { el = document.createElement("menuitem"); objectAssign(el, item, PROP_EXCLUDE); } else if (item.type == "radiogroup") { el = document.createDocumentFragment(); item.id = `gm-context-radio-${inc()}`; item.startEl = document.createComment(`<radiogroup ${item.id}>`); el.appendChild(item.startEl); el.appendChild(buildItems(item, item.items)); item.endEl = document.createComment("</radiogroup>"); el.appendChild(item.endEl); } else if (parent && parent.type == "radiogroup") { el = document.createElement("menuitem"); item.type = "radio"; item.radiogroup = parent.id; objectAssign(el, item, PROP_EXCLUDE); } else { el = document.createElement("menuitem"); objectAssign(el, item, PROP_EXCLUDE); } if (item.type !== "radiogroup") { item.el = el; buildHandler(item); } item.isBuilt = true; return el; } function buildHandler(item) { if (item.type === "radiogroup") { if (item.onchange) { item.items.forEach(buildHandler); } } else if (item.type === "radio") { if (!item.el.onclick && (item.parent.onchange || item.onclick)) { item.el.onclick = () => { if (item.onclick) { item.onclick.call(item.el, contextEvent); } if (item.parent.onchange) { item.parent.onchange.call(item.el, contextEvent, item.value); } }; } } else if (item.type === "checkbox") { if (!item.el.onclick && item.onclick) { item.el.onclick = () => { if (item.onclick) { item.onclick.call(item.el, contextEvent, item.el.checked); } }; } } else { if (!item.el.onclick && item.onclick) { item.el.onclick = () => { if (item.onclick) { item.onclick.call(item.el, contextEvent); } }; } } } // build items' element function buildItems(parent, items) { const root = document.createDocumentFragment(); for (const item of items) { root.appendChild(buildItem(parent, item)); } return root; } // attach menu to DOM function appendMenu(container, menu) { container.appendChild(menu.frag); return () => { const range = document.createRange(); range.setStartBefore(menu.startEl); range.setEndAfter(menu.endEl); menu.frag = range.extractContents(); }; } // add a menu function add(menu) { if (!isInit) { init(); } menu.id = inc(); menus.add(menu); } // remove a menu function remove(menu) { menus.delete(menu); } // update item's properties. If @changes includes an `items` key, it would replace item's children. function update(item, changes) { if (changes.type) { throw new Error("item type is not changable"); } if (changes.items) { if (item.isBuilt) { item.items.forEach(removeElement); } item.items.length = 0; changes.items.forEach(i => addItem(item, i)); delete changes.items; } Object.assign(item, changes); if (item.el) { buildHandler(item); objectAssign(item.el, changes, PROP_EXCLUDE); } } // add an item to parent function addItem(parent, item, pos = parent.items.length) { if (parent.isBuilt) { const el = buildItem(parent, item); if (parent.el) { parent.el.insertBefore(el, parent.el.childNodes[pos]); } else { // search from end, so it would be faster to insert multiple item to end let ref = parent.endEl, i = pos < 0 ? -pos : parent.items.length - pos; while (i-- && ref) { ref = ref.previousSibling; } parent.startEl.parentNode.insertBefore(el, ref); } } parent.items.splice(pos, 0, item); } // remove an item from parent function removeItem(parent, item) { const pos = parent.items.indexOf(item); parent.items.splice(pos, 1); if (item.isBuilt) { removeElement(item); } } // remove item's element function removeElement(item) { if (item.el) { item.el.remove(); } else { while (item.startEl.nextSibling != item.endEl) { item.startEl.nextSibling.remove(); } item.startEl.remove(); item.endEl.remove(); } } exports.add = add; exports.addItem = addItem; exports.buildMenu = buildMenu; exports.remove = remove; exports.removeItem = removeItem; exports.update = update; return exports; }({}));