返回首頁 

Greasy Fork is available in English.

GM_context

A html5 contextmenu library

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/33034/705360/GM_context.js

  1. // ==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 <eight04@gmail.com> (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 labelfunction 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 elementfunction 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' elementfunction buildItems(parent, items) {const root = document.createDocumentFragment();for (const item of items) {root.appendChild(buildItem(parent, item));}return root;}// attach menu to DOMfunction 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 menufunction add(menu) {if (!isInit) {init();}menu.id = inc();menus.add(menu);}// remove a menufunction 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 parentfunction 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 endlet 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 parentfunction removeItem(parent, item) {const pos = parent.items.indexOf(item);parent.items.splice(pos, 1);if (item.isBuilt) {removeElement(item);}}// remove item's elementfunction 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;}({}));