Greasy Fork is available in English.
A config library powered by webext-pref.
สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/371339/961539/GM_webextPref.js
// ==UserScript== // @name gm-webext-pref // @version 0.4.2 // @description A config library powered by webext-pref. // @license MIT // @author eight04 <[email protected]> // @homepageURL https://github.com/eight04/GM_webextPref // @supportURL https://github.com/eight04/GM_webextPref/issues // @namespace eight04.blogspot.com // @grant GM_getValue // @grant GM.getValue // @grant GM_setValue // @grant GM.setValue // @grant GM_deleteValue // @grant GM.deleteValue // @grant GM_addValueChangeListener // @grant GM_registerMenuCommand // @grant GM.registerMenuCommand // @include * // ==/UserScript== var GM_webextPref = (function () { 'use strict'; /** * event-lite.js - Light-weight EventEmitter (less than 1KB when gzipped) * * @copyright Yusuke Kawasaki * @license MIT * @constructor * @see https://github.com/kawanet/event-lite * @see http://kawanet.github.io/event-lite/EventLite.html * @example * var EventLite = require("event-lite"); * * function MyClass() {...} // your class * * EventLite.mixin(MyClass.prototype); // import event methods * * var obj = new MyClass(); * obj.on("foo", function() {...}); // add event listener * obj.once("bar", function() {...}); // add one-time event listener * obj.emit("foo"); // dispatch event * obj.emit("bar"); // dispatch another event * obj.off("foo"); // remove event listener */ function EventLite() { if (!(this instanceof EventLite)) return new EventLite(); } const _module_ = {exports: {}}; (function(EventLite) { // export the class for node.js if ("undefined" !== typeof _module_) _module_.exports = EventLite; // property name to hold listeners var LISTENERS = "listeners"; // methods to export var methods = { on: on, once: once, off: off, emit: emit }; // mixin to self mixin(EventLite.prototype); // export mixin function EventLite.mixin = mixin; /** * Import on(), once(), off() and emit() methods into target object. * * @function EventLite.mixin * @param target {Prototype} */ function mixin(target) { for (var key in methods) { target[key] = methods[key]; } return target; } /** * Add an event listener. * * @function EventLite.prototype.on * @param type {string} * @param func {Function} * @returns {EventLite} Self for method chaining */ function on(type, func) { getListeners(this, type).push(func); return this; } /** * Add one-time event listener. * * @function EventLite.prototype.once * @param type {string} * @param func {Function} * @returns {EventLite} Self for method chaining */ function once(type, func) { var that = this; wrap.originalListener = func; getListeners(that, type).push(wrap); return that; function wrap() { off.call(that, type, wrap); func.apply(this, arguments); } } /** * Remove an event listener. * * @function EventLite.prototype.off * @param [type] {string} * @param [func] {Function} * @returns {EventLite} Self for method chaining */ function off(type, func) { var that = this; var listners; if (!arguments.length) { delete that[LISTENERS]; } else if (!func) { listners = that[LISTENERS]; if (listners) { delete listners[type]; if (!Object.keys(listners).length) return off.call(that); } } else { listners = getListeners(that, type, true); if (listners) { listners = listners.filter(ne); if (!listners.length) return off.call(that, type); that[LISTENERS][type] = listners; } } return that; function ne(test) { return test !== func && test.originalListener !== func; } } /** * Dispatch (trigger) an event. * * @function EventLite.prototype.emit * @param type {string} * @param [value] {*} * @returns {boolean} True when a listener received the event */ function emit(type, value) { var that = this; var listeners = getListeners(that, type, true); if (!listeners) return false; var arglen = arguments.length; if (arglen === 1) { listeners.forEach(zeroarg); } else if (arglen === 2) { listeners.forEach(onearg); } else { var args = Array.prototype.slice.call(arguments, 1); listeners.forEach(moreargs); } return !!listeners.length; function zeroarg(func) { func.call(that); } function onearg(func) { func.call(that, value); } function moreargs(func) { func.apply(that, args); } } /** * @ignore */ function getListeners(that, type, readonly) { if (readonly && !that[LISTENERS]) return; var listeners = that[LISTENERS] || (that[LISTENERS] = {}); return listeners[type] || (listeners[type] = []); } })(EventLite); var EventLite$1 = _module_.exports; function createPref(DEFAULT, sep = "/") { let storage; let currentScope = "global"; let scopeList = ["global"]; const events = new EventLite$1; const globalCache = {}; let scopedCache = {}; let currentCache = Object.assign({}, DEFAULT); let initializing; return Object.assign(events, { // storage, // ready, connect, disconnect, get, getAll, set, getCurrentScope, setCurrentScope, addScope, deleteScope, getScopeList, import: import_, export: export_, has }); function import_(input) { const newScopeList = input.scopeList || scopeList.slice(); const scopes = new Set(newScopeList); if (!scopes.has("global")) { throw new Error("invalid scopeList"); } const changes = { scopeList: newScopeList }; for (const [scopeName, scope] of Object.entries(input.scopes)) { if (!scopes.has(scopeName)) { continue; } for (const [key, value] of Object.entries(scope)) { if (DEFAULT[key] == undefined) { continue; } changes[`${scopeName}${sep}${key}`] = value; } } return storage.setMany(changes); } function export_() { const keys = []; for (const scope of scopeList) { keys.push(...Object.keys(DEFAULT).map(k => `${scope}${sep}${k}`)); } keys.push("scopeList"); return storage.getMany(keys) .then(changes => { const _scopeList = changes.scopeList || scopeList.slice(); const scopes = new Set(_scopeList); const output = { scopeList: _scopeList, scopes: {} }; for (const [key, value] of Object.entries(changes)) { const sepIndex = key.indexOf(sep); if (sepIndex < 0) { continue; } const scope = key.slice(0, sepIndex); const realKey = key.slice(sepIndex + sep.length); if (!scopes.has(scope)) { continue; } if (DEFAULT[realKey] == undefined) { continue; } if (!output.scopes[scope]) { output.scopes[scope] = {}; } output.scopes[scope][realKey] = value; } return output; }); } function connect(_storage) { storage = _storage; initializing = storage.getMany( Object.keys(DEFAULT).map(k => `global${sep}${k}`).concat(["scopeList"]) ) .then(updateCache); storage.on("change", updateCache); return initializing; } function disconnect() { storage.off("change", updateCache); storage = null; } function updateCache(changes, rebuildCache = false) { if (changes.scopeList) { scopeList = changes.scopeList; events.emit("scopeListChange", scopeList); if (!scopeList.includes(currentScope)) { return setCurrentScope("global"); } } const changedKeys = new Set; for (const [key, value] of Object.entries(changes)) { const [scope, realKey] = key.startsWith(`global${sep}`) ? ["global", key.slice(6 + sep.length)] : key.startsWith(`${currentScope}${sep}`) ? [currentScope, key.slice(currentScope.length + sep.length)] : [null, null]; if (!scope || DEFAULT[realKey] == null) { continue; } if (scope === "global") { changedKeys.add(realKey); globalCache[realKey] = value; } if (scope === currentScope) { changedKeys.add(realKey); scopedCache[realKey] = value; } } if (rebuildCache) { Object.keys(DEFAULT).forEach(k => changedKeys.add(k)); } const realChanges = {}; let isChanged = false; for (const key of changedKeys) { const value = scopedCache[key] != null ? scopedCache[key] : globalCache[key] != null ? globalCache[key] : DEFAULT[key]; if (currentCache[key] !== value) { realChanges[key] = value; currentCache[key] = value; isChanged = true; } } if (isChanged) { events.emit("change", realChanges); } } function has(key) { return currentCache.hasOwnProperty(key); } function get(key) { return currentCache[key]; } function getAll() { return Object.assign({}, currentCache); } function set(key, value) { return storage.setMany({ [`${currentScope}${sep}${key}`]: value }); } function getCurrentScope() { return currentScope; } function setCurrentScope(newScope) { if (currentScope === newScope) { return Promise.resolve(true); } if (!scopeList.includes(newScope)) { return Promise.resolve(false); } return storage.getMany(Object.keys(DEFAULT).map(k => `${newScope}${sep}${k}`)) .then(changes => { currentScope = newScope; scopedCache = {}; events.emit("scopeChange", currentScope); updateCache(changes, true); return true; }); } function addScope(scope) { if (scopeList.includes(scope)) { return Promise.reject(new Error(`${scope} already exists`)); } if (scope.includes(sep)) { return Promise.reject(new Error(`invalid word: ${sep}`)); } return storage.setMany({ scopeList: scopeList.concat([scope]) }); } function deleteScope(scope) { if (scope === "global") { return Promise.reject(new Error(`cannot delete global`)); } return Promise.all([ storage.setMany({ scopeList: scopeList.filter(s => s != scope) }), storage.deleteMany(Object.keys(DEFAULT).map(k => `${scope}${sep}${k}`)) ]); } function getScopeList() { return scopeList; } } const keys = Object.keys; function isBoolean(val) { return typeof val === "boolean" } function isElement(val) { return val && typeof val.nodeType === "number" } function isString(val) { return typeof val === "string" } function isNumber(val) { return typeof val === "number" } function isObject(val) { return typeof val === "object" ? val !== null : isFunction(val) } function isFunction(val) { return typeof val === "function" } function isArrayLike(obj) { return isObject(obj) && typeof obj.length === "number" && typeof obj.nodeType !== "number" } function forEach(value, fn) { if (!value) return for (const key of keys(value)) { fn(value[key], key); } } function isRef(maybeRef) { return isObject(maybeRef) && "current" in maybeRef } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {} var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue target[key] = source[key]; } return target } const isUnitlessNumber = { animationIterationCount: 0, borderImageOutset: 0, borderImageSlice: 0, borderImageWidth: 0, boxFlex: 0, boxFlexGroup: 0, boxOrdinalGroup: 0, columnCount: 0, columns: 0, flex: 0, flexGrow: 0, flexPositive: 0, flexShrink: 0, flexNegative: 0, flexOrder: 0, gridArea: 0, gridRow: 0, gridRowEnd: 0, gridRowSpan: 0, gridRowStart: 0, gridColumn: 0, gridColumnEnd: 0, gridColumnSpan: 0, gridColumnStart: 0, fontWeight: 0, lineClamp: 0, lineHeight: 0, opacity: 0, order: 0, orphans: 0, tabSize: 0, widows: 0, zIndex: 0, zoom: 0, fillOpacity: 0, floodOpacity: 0, stopOpacity: 0, strokeDasharray: 0, strokeDashoffset: 0, strokeMiterlimit: 0, strokeOpacity: 0, strokeWidth: 0, }; function prefixKey(prefix, key) { return prefix + key.charAt(0).toUpperCase() + key.substring(1) } const prefixes = ["Webkit", "ms", "Moz", "O"]; keys(isUnitlessNumber).forEach((prop) => { prefixes.forEach((prefix) => { isUnitlessNumber[prefixKey(prefix, prop)] = 0; }); }); const SVGNamespace = "http://www.w3.org/2000/svg"; const XLinkNamespace = "http://www.w3.org/1999/xlink"; const XMLNamespace = "http://www.w3.org/XML/1998/namespace"; function isVisibleChild(value) { return !isBoolean(value) && value != null } function className(value) { if (Array.isArray(value)) { return value.map(className).filter(Boolean).join(" ") } else if (isObject(value)) { return keys(value) .filter((k) => value[k]) .join(" ") } else if (isVisibleChild(value)) { return "" + value } else { return "" } } const svg = { animate: 0, circle: 0, clipPath: 0, defs: 0, desc: 0, ellipse: 0, feBlend: 0, feColorMatrix: 0, feComponentTransfer: 0, feComposite: 0, feConvolveMatrix: 0, feDiffuseLighting: 0, feDisplacementMap: 0, feDistantLight: 0, feFlood: 0, feFuncA: 0, feFuncB: 0, feFuncG: 0, feFuncR: 0, feGaussianBlur: 0, feImage: 0, feMerge: 0, feMergeNode: 0, feMorphology: 0, feOffset: 0, fePointLight: 0, feSpecularLighting: 0, feSpotLight: 0, feTile: 0, feTurbulence: 0, filter: 0, foreignObject: 0, g: 0, image: 0, line: 0, linearGradient: 0, marker: 0, mask: 0, metadata: 0, path: 0, pattern: 0, polygon: 0, polyline: 0, radialGradient: 0, rect: 0, stop: 0, svg: 0, switch: 0, symbol: 0, text: 0, textPath: 0, tspan: 0, use: 0, view: 0, }; function createElement(tag, attr, ...children) { if (isString(attr) || Array.isArray(attr)) { children.unshift(attr); attr = {}; } attr = attr || {}; if (!attr.namespaceURI && svg[tag] === 0) { attr = Object.assign({}, attr, { namespaceURI: SVGNamespace, }); } if (attr.children != null && !children.length) { var _attr = attr ;({ children } = _attr); attr = _objectWithoutPropertiesLoose(_attr, ["children"]); } let node; if (isString(tag)) { node = attr.namespaceURI ? document.createElementNS(attr.namespaceURI, tag) : document.createElement(tag); attributes(attr, node); appendChild(children, node); } else if (isFunction(tag)) { if (isObject(tag.defaultProps)) { attr = Object.assign({}, tag.defaultProps, attr); } node = tag( Object.assign({}, attr, { children, }) ); } if (isRef(attr.ref)) { attr.ref.current = node; } else if (isFunction(attr.ref)) { attr.ref(node); } return node } function appendChild(child, node) { if (isArrayLike(child)) { appendChildren(child, node); } else if (isString(child) || isNumber(child)) { appendChildToNode(document.createTextNode(child), node); } else if (child === null) { appendChildToNode(document.createComment(""), node); } else if (isElement(child)) { appendChildToNode(child, node); } } function appendChildren(children, node) { for (const child of children) { appendChild(child, node); } return node } function appendChildToNode(child, node) { if (node instanceof window.HTMLTemplateElement) { node.content.appendChild(child); } else { node.appendChild(child); } } function normalizeAttribute(s) { return s.replace(/[A-Z\d]/g, (match) => ":" + match.toLowerCase()) } function attribute(key, value, node) { switch (key) { case "xlinkActuate": case "xlinkArcrole": case "xlinkHref": case "xlinkRole": case "xlinkShow": case "xlinkTitle": case "xlinkType": attrNS(node, XLinkNamespace, normalizeAttribute(key), value); return case "xmlnsXlink": attr(node, normalizeAttribute(key), value); return case "xmlBase": case "xmlLang": case "xmlSpace": attrNS(node, XMLNamespace, normalizeAttribute(key), value); return } switch (key) { case "htmlFor": attr(node, "for", value); return case "dataset": forEach(value, (dataValue, dataKey) => { if (dataValue != null) { node.dataset[dataKey] = dataValue; } }); return case "innerHTML": case "innerText": case "textContent": node[key] = value; return case "spellCheck": node.spellcheck = value; return case "class": case "className": if (isFunction(value)) { value(node); } else { attr(node, "class", className(value)); } return case "ref": case "namespaceURI": return case "style": if (isObject(value)) { forEach(value, (val, key) => { if (isNumber(val) && isUnitlessNumber[key] !== 0) { node.style[key] = val + "px"; } else { node.style[key] = val; } }); return } } if (isFunction(value)) { if (key[0] === "o" && key[1] === "n") { node[key.toLowerCase()] = value; } } else if (value === true) { attr(node, key, ""); } else if (value !== false && value != null) { attr(node, key, value); } } function attr(node, key, value) { node.setAttribute(key, value); } function attrNS(node, namespace, key, value) { node.setAttributeNS(namespace, key, value); } function attributes(attr, node) { for (const key of keys(attr)) { attribute(key, attr[key], node); } return node } function messageGetter({ getMessage, DEFAULT }) { return (key, params) => { const message = getMessage(key, params); if (message) return message; const defaultMessage = DEFAULT[key]; if (!defaultMessage) return ""; if (!params) return defaultMessage; if (!Array.isArray(params)) { params = [params]; } return defaultMessage.replace(/\$(\d+)/g, (m, n) => params[n - 1]); }; } function fallback(getMessage) { return messageGetter({ getMessage, DEFAULT: { currentScopeLabel: "Current scope", addScopeLabel: "Add new scope", deleteScopeLabel: "Delete current scope", learnMoreButton: "Learn more", importButton: "Import", exportButton: "Export", addScopePrompt: "Add new scope", deleteScopeConfirm: "Delete scope $1?", importPrompt: "Paste settings", exportPrompt: "Copy settings" } }); } const VALID_CONTROL = new Set(["import", "export", "scope-list", "add-scope", "delete-scope"]); class DefaultMap extends Map { constructor(getDefault) { super(); this.getDefault = getDefault; } get(key) { let item = super.get(key); if (!item) { item = this.getDefault(); super.set(key, item); } return item; } } function bindInputs(pref, inputs) { const bounds = []; const onPrefChange = change => { for (const key in change) { if (!inputs.has(key)) { continue; } for (const input of inputs.get(key)) { updateInput(input, change[key]); } } }; pref.on("change", onPrefChange); bounds.push(() => pref.off("change", onPrefChange)); for (const [key, list] of inputs.entries()) { for (const input of list) { const evt = input.hasAttribute("realtime") ? "input" : "change"; const onChange = () => updatePref(key, input); input.addEventListener(evt, onChange); bounds.push(() => input.removeEventListener(evt, onChange)); } } onPrefChange(pref.getAll()); return () => { for (const unbind of bounds) { unbind(); } }; function updatePref(key, input) { if (!input.checkValidity()) { return; } if (input.type === "checkbox") { pref.set(key, input.checked); return; } if (input.type === "radio") { if (input.checked) { pref.set(key, input.value); } return; } if (input.nodeName === "SELECT" && input.multiple) { pref.set(key, [...input.options].filter(o => o.selected).map(o => o.value)); return; } if (input.type === "number" || input.type === "range") { pref.set(key, Number(input.value)); return; } pref.set(key, input.value); } function updateInput(input, value) { if (input.nodeName === "INPUT" && input.type === "radio") { input.checked = input.value === value; return; } if (input.type === "checkbox") { input.checked = value; return; } if (input.nodeName === "SELECT" && input.multiple) { const checked = new Set(value); for (const option of input.options) { option.selected = checked.has(option.value); } return; } input.value = value; } } function bindFields(pref, fields) { const onPrefChange = change => { for (const key in change) { if (!fields.has(key)) { continue; } for (const field of fields.get(key)) { field.disabled = field.dataset.bindToValue ? field.dataset.bindToValue !== change[key] : !change[key]; } } }; pref.on("change", onPrefChange); onPrefChange(pref.getAll()); return () => pref.off("change", onPrefChange); } function bindControls({ pref, controls, alert: _alert = alert, confirm: _confirm = confirm, prompt: _prompt = prompt, getMessage = () => {}, getNewScope = () => "" }) { const CONTROL_METHODS = { "import": ["click", doImport], "export": ["click", doExport], "scope-list": ["change", updateCurrentScope], "add-scope": ["click", addScope], "delete-scope": ["click", deleteScope] }; for (const type in CONTROL_METHODS) { for (const el of controls.get(type)) { el.addEventListener(CONTROL_METHODS[type][0], CONTROL_METHODS[type][1]); } } pref.on("scopeChange", updateCurrentScopeEl); pref.on("scopeListChange", updateScopeList); updateScopeList(); updateCurrentScopeEl(); const _ = fallback(getMessage); return unbind; function unbind() { pref.off("scopeChange", updateCurrentScopeEl); pref.off("scopeListChange", updateScopeList); for (const type in CONTROL_METHODS) { for (const el of controls.get(type)) { el.removeEventListener(CONTROL_METHODS[type][0], CONTROL_METHODS[type][1]); } } } async function doImport() { try { const input = await _prompt(_("importPrompt")); if (input == null) { return; } const settings = JSON.parse(input); return pref.import(settings); } catch (err) { await _alert(err.message); } } async function doExport() { try { const settings = await pref.export(); await _prompt(_("exportPrompt"), JSON.stringify(settings)); } catch (err) { await _alert(err.message); } } function updateCurrentScope(e) { pref.setCurrentScope(e.target.value); } async function addScope() { try { let scopeName = await _prompt(_("addScopePrompt"), getNewScope()); if (scopeName == null) { return; } scopeName = scopeName.trim(); if (!scopeName) { throw new Error("the value is empty"); } await pref.addScope(scopeName); pref.setCurrentScope(scopeName); } catch (err) { await _alert(err.message); } } async function deleteScope() { try { const scopeName = pref.getCurrentScope(); const r###lt = await _confirm(_("deleteScopeConfirm", scopeName)); if (r###lt) { return pref.deleteScope(scopeName); } } catch (err) { await _alert(err.message); } } function updateCurrentScopeEl() { const scopeName = pref.getCurrentScope(); for (const el of controls.get("scope-list")) { el.value = scopeName; } } function updateScopeList() { const scopeList = pref.getScopeList(); for (const el of controls.get("scope-list")) { el.innerHTML = ""; el.append(...scopeList.map(scope => { const option = document.createElement("option"); option.value = scope; option.textContent = scope; return option; })); } } } function createBinding({ pref, root, elements = root.querySelectorAll("input, textarea, select, fieldset, button"), keyPrefix = "pref-", controlPrefix = "webext-pref-", alert, confirm, prompt, getMessage, getNewScope }) { const inputs = new DefaultMap(() => []); const fields = new DefaultMap(() => []); const controls = new DefaultMap(() => []); for (const element of elements) { const id = element.id && stripPrefix(element.id, keyPrefix); if (id && pref.has(id)) { inputs.get(id).push(element); continue; } if (element.nodeName === "INPUT" && element.type === "radio") { const name = element.name && stripPrefix(element.name, keyPrefix); if (name && pref.has(name)) { inputs.get(name).push(element); continue; } } if (element.nodeName === "FIELDSET" && element.dataset.bindTo) { fields.get(element.dataset.bindTo).push(element); continue; } const controlType = findControlType(element.classList); if (controlType) { controls.get(controlType).push(element); } } const bounds = [bindInputs(pref, inputs), bindFields(pref, fields), bindControls({ pref, controls, alert, confirm, prompt, getMessage, getNewScope })]; return () => { for (const unbind of bounds) { unbind(); } }; function stripPrefix(id, prefix) { if (!prefix) { return id; } return id.startsWith(prefix) ? id.slice(prefix.length) : ""; } function findControlType(list) { for (const name of list) { const controlType = stripPrefix(name, controlPrefix); if (VALID_CONTROL.has(controlType)) { return controlType; } } } } function createUI({ body, getMessage = () => {}, toolbar = true, navbar = true, keyPrefix = "pref-", controlPrefix = "webext-pref-" }) { const root = document.createDocumentFragment(); const _ = fallback(getMessage); if (toolbar) { root.append(createToolbar()); } if (navbar) { root.append(createNavbar()); } root.append( /*#__PURE__*/createElement("div", { class: controlPrefix + "body" }, body.map(item => { if (!item.hLevel) { item.hLevel = 3; } return createItem(item); }))); return root; function createToolbar() { return /*#__PURE__*/createElement("div", { class: controlPrefix + "toolbar" }, /*#__PURE__*/createElement("button", { type: "button", class: [controlPrefix + "import", "browser-style"] }, _("importButton")), /*#__PURE__*/createElement("button", { type: "button", class: [controlPrefix + "export", "browser-style"] }, _("exportButton"))); } function createNavbar() { return /*#__PURE__*/createElement("div", { class: controlPrefix + "nav" }, /*#__PURE__*/createElement("select", { class: [controlPrefix + "scope-list", "browser-style"], title: _("currentScopeLabel") }), /*#__PURE__*/createElement("button", { type: "button", class: [controlPrefix + "delete-scope", "browser-style"], title: _("deleteScopeLabel") }, "\xD7"), /*#__PURE__*/createElement("button", { type: "button", class: [controlPrefix + "add-scope", "browser-style"], title: _("addScopeLabel") }, "+")); } function createItem(p) { if (p.type === "section") { return createSection(p); } if (p.type === "checkbox") { return createCheckbox(p); } if (p.type === "radiogroup") { return createRadioGroup(p); } return createInput(p); } function createInput(p) { const key = keyPrefix + p.key; let input; const onChange = p.validate ? e => { try { p.validate(e.target.value); e.target.setCustomValidity(""); } catch (err) { e.target.setCustomValidity(err.message || String(err)); } } : null; if (p.type === "select") { input = /*#__PURE__*/createElement("select", { multiple: p.multiple, class: "browser-style", id: key, onChange: onChange }, Object.entries(p.options).map(([value, label]) => /*#__PURE__*/createElement("option", { value: value }, label))); } else if (p.type === "textarea") { input = /*#__PURE__*/createElement("textarea", { rows: "8", class: "browser-style", id: key, onChange: onChange }); } else { input = /*#__PURE__*/createElement("input", { type: p.type, id: key, onChange: onChange }); } return /*#__PURE__*/createElement("div", { class: [`${controlPrefix}${p.type}`, "browser-style", p.className] }, /*#__PURE__*/createElement("label", { htmlFor: key }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, { url: p.learnMore }), input, p.help && /*#__PURE__*/createElement(Help, { content: p.help })); } function createRadioGroup(p) { return /*#__PURE__*/createElement("div", { class: [`${controlPrefix}${p.type}`, "browser-style", p.className] }, /*#__PURE__*/createElement("div", { class: controlPrefix + "radio-title" }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, { url: p.learnMore }), p.help && /*#__PURE__*/createElement(Help, { content: p.help }), p.children.map(c => { c.parentKey = p.key; return createCheckbox(inheritProp(p, c)); })); } function Help({ content }) { return /*#__PURE__*/createElement("p", { class: controlPrefix + "help" }, content); } function LearnMore({ url }) { return /*#__PURE__*/createElement("a", { href: url, class: controlPrefix + "learn-more", target: "_blank", rel: "noopener noreferrer" }, _("learnMoreButton")); } function createCheckbox(p) { const id = p.parentKey ? `${keyPrefix}${p.parentKey}-${p.value}` : keyPrefix + p.key; return /*#__PURE__*/createElement("div", { class: [`${controlPrefix}${p.type}`, "browser-style", p.className] }, /*#__PURE__*/createElement("input", { type: p.type, id: id, name: p.parentKey ? keyPrefix + p.parentKey : null, value: p.value }), /*#__PURE__*/createElement("label", { htmlFor: id }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, { url: p.learnMore }), p.help && /*#__PURE__*/createElement(Help, { content: p.help }), p.children && /*#__PURE__*/createElement("fieldset", { class: controlPrefix + "checkbox-children", dataset: { bindTo: p.parentKey || p.key, bindToValue: p.value } }, p.children.map(c => createItem(inheritProp(p, c))))); } function createSection(p) { const Header = `h${p.hLevel}`; p.hLevel++; return ( /*#__PURE__*/ // FIXME: do we need browser-style for section? createElement("div", { class: [controlPrefix + p.type, p.className] }, /*#__PURE__*/createElement(Header, { class: controlPrefix + "header" }, p.label), p.help && /*#__PURE__*/createElement(Help, { content: p.help }), p.children && p.children.map(c => createItem(inheritProp(p, c)))) ); } function inheritProp(parent, child) { child.hLevel = parent.hLevel; return child; } } /* eslint-env greasemonkey */ function createGMStorage() { const setValue = typeof GM_setValue === "function" ? promisify(GM_setValue) : GM.setValue.bind(GM); const getValue = typeof GM_getValue === "function" ? promisify(GM_getValue) : GM.getValue.bind(GM); const deleteValue = typeof GM_deleteValue === "function" ? promisify(GM_deleteValue) : GM.deleteValue.bind(GM); const events = new EventLite$1; if (typeof GM_addValueChangeListener === "function") { GM_addValueChangeListener("webext-pref-message", (name, oldValue, newValue) => { const changes = JSON.parse(newValue); for (const key of Object.keys(changes)) { if (typeof changes[key] === "object" && changes[key].$undefined) { changes[key] = undefined; } } events.emit("change", changes); }); } return Object.assign(events, {getMany, setMany, deleteMany}); function getMany(keys) { return Promise.all(keys.map(k => getValue(`webext-pref/${k}`) .then(value => [k, typeof value === "string" ? JSON.parse(value) : value]) )) .then(entries => { const output = {}; for (const [key, value] of entries) { output[key] = value; } return output; }); } function setMany(changes) { return Promise.all(Object.entries(changes).map(([key, value]) => setValue(`webext-pref/${key}`, JSON.stringify(value)) )) .then(() => { if (typeof GM_addValueChangeListener === "function") { return setValue("webext-pref-message", JSON.stringify(changes)); } events.emit("change", changes); }); } function deleteMany(keys) { return Promise.all(keys.map(k => deleteValue(`webext-pref/${k}`))) .then(() => { if (typeof GM_addValueChangeListener === "function") { const changes = {}; for (const key of keys) { changes[key] = { $undefined: true }; } return setValue("webext-pref-message", JSON.stringify(changes)); } const changes = {}; for (const key of keys) { changes[key] = undefined; } events.emit("change", changes); }); } function promisify(fn) { return (...args) => { try { return Promise.resolve(fn(...args)); } catch (err) { return Promise.reject(err); } }; } } /* eslint-env greasemonkey */ function GM_webextPref({ default: default_, separator, css = "", ...options }) { const pref = createPref(default_, separator); const initializing = pref.connect(createGMStorage()); let isOpen = false; const registerMenu = typeof GM_registerMenuCommand === "function" ? GM_registerMenuCommand : typeof GM !== "undefined" && GM && GM.registerMenuCommand ? GM.registerMenuCommand.bind(GM) : undefined; if (registerMenu) { registerMenu(`${getTitle()} - Configure`, openDialog); } return Object.assign(pref, { ready: () => initializing, openDialog }); function openDialog() { if (isOpen) { return; } isOpen = true; let destroyView; const modal = document.createElement("div"); modal.className = "webext-pref-modal"; modal.onclick = () => { modal.classList.remove("webext-pref-modal-open"); modal.addEventListener("transitionend", () => { if (destroyView) { destroyView(); } modal.remove(); isOpen = false; }); }; const style = document.createElement("style"); style.textContent = "body{overflow:hidden}.webext-pref-modal{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.5);overflow:auto;z-index:999999;opacity:0;transition:opacity .2s linear;display:flex}.webext-pref-modal-open{opacity:1}.webext-pref-modal::after,.webext-pref-modal::before{content:\"\";display:block;height:30px;visibility:hidden}.webext-pref-iframe-wrap{margin:auto}.webext-pref-iframe{margin:30px 0;display:inline-block;width:100%;max-width:100%;background:#fff;border-width:0;box-shadow:0 0 30px #000;transform:translateY(-20px);transition:transform .2s linear}.webext-pref-modal-open .webext-pref-iframe{transform:none}" + ` body { padding-right: ${window.innerWidth - document.documentElement.offsetWidth}px; } `; const iframe = document.createElement("iframe"); iframe.className = "webext-pref-iframe"; iframe.srcdoc = ` <html> <head> <style class="dialog-style"></style> </head> <body> <div class="dialog-body"></div> </body> </html> `; const wrap = document.createElement("div"); wrap.className = "webext-pref-iframe-wrap"; wrap.append(iframe); modal.append(style, wrap); document.body.appendChild(modal); iframe.onload = () => { iframe.onload = null; iframe.contentDocument.querySelector(".dialog-style").textContent = "body{display:inline-block;font-size:16px;font-family:sans-serif;white-space:nowrap;overflow:hidden;margin:0;color:#3d3d3d;line-height:1}input[type=number],input[type=text],select,textarea{display:block;width:100%;box-sizing:border-box;height:2em;font:inherit;padding:0 .3em;border:1px solid #9e9e9e;cursor:pointer}select[multiple],textarea{height:6em}input[type=number]:hover,input[type=text]:hover,select:hover,textarea:hover{border-color:#d5d5d5}input[type=number]:focus,input[type=text]:focus,select:focus,textarea:focus{cursor:auto;border-color:#3a93ee}textarea{line-height:1.5}input[type=checkbox],input[type=radio]{display:inline-block;width:1em;height:1em;font:inherit;margin:0}button{box-sizing:border-box;height:2em;font:inherit;border:1px solid #9e9e9e;cursor:pointer;background:0 0}button:hover{border-color:#d5d5d5}button:focus{border-color:#3a93ee}.dialog-body{margin:2em}.webext-pref-toolbar{display:flex;align-items:center;margin-bottom:1em}.dialog-title{font-size:1.34em;margin:0 2em 0 0;flex-grow:1}.webext-pref-toolbar button{font-size:.7em;margin-left:.5em}.webext-pref-nav{display:flex;margin-bottom:1em}.webext-pref-nav select{text-align:center;text-align-last:center}.webext-pref-nav button{width:2em}.webext-pref-number,.webext-pref-radiogroup,.webext-pref-select,.webext-pref-text,.webext-pref-textarea{margin:1em 0}.webext-pref-body>:first-child{margin-top:0}.webext-pref-body>:last-child{margin-bottom:0}.webext-pref-number>input,.webext-pref-select>select,.webext-pref-text>input,.webext-pref-textarea>textarea{margin:.3em 0}.webext-pref-checkbox,.webext-pref-radio{margin:.5em 0;padding-left:1.5em}.webext-pref-checkbox>input,.webext-pref-radio>input{margin-left:-1.5em;margin-right:.5em;vertical-align:middle}.webext-pref-checkbox>label,.webext-pref-radio>label{cursor:pointer;vertical-align:middle}.webext-pref-checkbox>label:hover,.webext-pref-radio>label:hover{color:#707070}.webext-pref-checkbox-children,.webext-pref-radio-children{margin:.7em 0 0;padding:0;border-width:0}.webext-pref-checkbox-children[disabled],.webext-pref-radio-children[disabled]{opacity:.5}.webext-pref-checkbox-children>:first-child,.webext-pref-radio-children>:first-child{margin-top:0}.webext-pref-checkbox-children>:last-child,.webext-pref-radio-children>:last-child{margin-bottom:0}.webext-pref-checkbox-children>:last-child>:last-child,.webext-pref-radio-children>:last-child>:last-child{margin-bottom:0}.webext-pref-help{color:#969696}.responsive{white-space:normal}.responsive .dialog-body{margin:1em}.responsive .webext-pref-toolbar{display:block}.responsive .dialog-title{margin:0 0 1em 0}.responsive .webext-pref-toolbar button{font-size:1em}.responsive .webext-pref-nav{display:block}" + css; const root = iframe.contentDocument.querySelector(".dialog-body"); root.append(createUI(options)); destroyView = createBinding({ pref, root, ...options }); const title = document.createElement("h2"); title.className = "dialog-title"; title.textContent = getTitle(); iframe.contentDocument.querySelector(".webext-pref-toolbar").prepend(title); if (iframe.contentDocument.body.offsetWidth > modal.offsetWidth) { iframe.contentDocument.body.classList.add("responsive"); } // calc iframe size iframe.style = ` width: ${iframe.contentDocument.body.offsetWidth}px; height: ${iframe.contentDocument.body.scrollHeight}px; `; modal.classList.add("webext-pref-modal-open"); }; } function getTitle() { return typeof GM_info === "object" ? GM_info.script.name : GM.info.script.name; } } return GM_webextPref; }());