GM_webextPref

A config library powered by webext-pref.

Tento skript by nemal byť nainštalovaný priamo. Je to knižnica pre ďalšie skripty, ktorú by mali používať cez meta príkaz // @require https://update.greasyfork.org/scripts/371339/961539/GM_webextPref.js

  1. // ==UserScript==
  2. // @name gm-webext-pref
  3. // @version 0.4.2
  4. // @description A config library powered by webext-pref.
  5. // @license MIT
  6. // @author eight04 <eight04@gmail.com>
  7. // @homepageURL https://github.com/eight04/GM_webextPref
  8. // @supportURL https://github.com/eight04/GM_webextPref/issues
  9. // @namespace eight04.blogspot.com
  10. // @grant GM_getValue
  11. // @grant GM.getValue
  12. // @grant GM_setValue
  13. // @grant GM.setValue
  14. // @grant GM_deleteValue
  15. // @grant GM.deleteValue
  16. // @grant GM_addValueChangeListener
  17. // @grant GM_registerMenuCommand
  18. // @grant GM.registerMenuCommand
  19. // @include *
  20. // ==/UserScript==
  21.  
  22. var GM_webextPref = (function () {
  23. 'use strict';
  24.  
  25. /**
  26. * event-lite.js - Light-weight EventEmitter (less than 1KB when gzipped)
  27. *
  28. * @copyright Yusuke Kawasaki
  29. * @license MIT
  30. * @constructor
  31. * @see https://github.com/kawanet/event-lite
  32. * @see http://kawanet.github.io/event-lite/EventLite.html
  33. * @example
  34. * var EventLite = require("event-lite");
  35. *
  36. * function MyClass() {...} // your class
  37. *
  38. * EventLite.mixin(MyClass.prototype); // import event methods
  39. *
  40. * var obj = new MyClass();
  41. * obj.on("foo", function() {...}); // add event listener
  42. * obj.once("bar", function() {...}); // add one-time event listener
  43. * obj.emit("foo"); // dispatch event
  44. * obj.emit("bar"); // dispatch another event
  45. * obj.off("foo"); // remove event listener
  46. */
  47.  
  48. function EventLite() {
  49. if (!(this instanceof EventLite)) return new EventLite();
  50. }
  51.  
  52. const _module_ = {exports: {}};
  53. (function(EventLite) {
  54. // export the class for node.js
  55. if ("undefined" !== typeof _module_) _module_.exports = EventLite;
  56.  
  57. // property name to hold listeners
  58. var LISTENERS = "listeners";
  59.  
  60. // methods to export
  61. var methods = {
  62. on: on,
  63. once: once,
  64. off: off,
  65. emit: emit
  66. };
  67.  
  68. // mixin to self
  69. mixin(EventLite.prototype);
  70.  
  71. // export mixin function
  72. EventLite.mixin = mixin;
  73.  
  74. /**
  75. * Import on(), once(), off() and emit() methods into target object.
  76. *
  77. * @function EventLite.mixin
  78. * @param target {Prototype}
  79. */
  80.  
  81. function mixin(target) {
  82. for (var key in methods) {
  83. target[key] = methods[key];
  84. }
  85. return target;
  86. }
  87.  
  88. /**
  89. * Add an event listener.
  90. *
  91. * @function EventLite.prototype.on
  92. * @param type {string}
  93. * @param func {Function}
  94. * @returns {EventLite} Self for method chaining
  95. */
  96.  
  97. function on(type, func) {
  98. getListeners(this, type).push(func);
  99. return this;
  100. }
  101.  
  102. /**
  103. * Add one-time event listener.
  104. *
  105. * @function EventLite.prototype.once
  106. * @param type {string}
  107. * @param func {Function}
  108. * @returns {EventLite} Self for method chaining
  109. */
  110.  
  111. function once(type, func) {
  112. var that = this;
  113. wrap.originalListener = func;
  114. getListeners(that, type).push(wrap);
  115. return that;
  116.  
  117. function wrap() {
  118. off.call(that, type, wrap);
  119. func.apply(this, arguments);
  120. }
  121. }
  122.  
  123. /**
  124. * Remove an event listener.
  125. *
  126. * @function EventLite.prototype.off
  127. * @param [type] {string}
  128. * @param [func] {Function}
  129. * @returns {EventLite} Self for method chaining
  130. */
  131.  
  132. function off(type, func) {
  133. var that = this;
  134. var listners;
  135. if (!arguments.length) {
  136. delete that[LISTENERS];
  137. } else if (!func) {
  138. listners = that[LISTENERS];
  139. if (listners) {
  140. delete listners[type];
  141. if (!Object.keys(listners).length) return off.call(that);
  142. }
  143. } else {
  144. listners = getListeners(that, type, true);
  145. if (listners) {
  146. listners = listners.filter(ne);
  147. if (!listners.length) return off.call(that, type);
  148. that[LISTENERS][type] = listners;
  149. }
  150. }
  151. return that;
  152.  
  153. function ne(test) {
  154. return test !== func && test.originalListener !== func;
  155. }
  156. }
  157.  
  158. /**
  159. * Dispatch (trigger) an event.
  160. *
  161. * @function EventLite.prototype.emit
  162. * @param type {string}
  163. * @param [value] {*}
  164. * @returns {boolean} True when a listener received the event
  165. */
  166.  
  167. function emit(type, value) {
  168. var that = this;
  169. var listeners = getListeners(that, type, true);
  170. if (!listeners) return false;
  171. var arglen = arguments.length;
  172. if (arglen === 1) {
  173. listeners.forEach(zeroarg);
  174. } else if (arglen === 2) {
  175. listeners.forEach(onearg);
  176. } else {
  177. var args = Array.prototype.slice.call(arguments, 1);
  178. listeners.forEach(moreargs);
  179. }
  180. return !!listeners.length;
  181.  
  182. function zeroarg(func) {
  183. func.call(that);
  184. }
  185.  
  186. function onearg(func) {
  187. func.call(that, value);
  188. }
  189.  
  190. function moreargs(func) {
  191. func.apply(that, args);
  192. }
  193. }
  194.  
  195. /**
  196. * @ignore
  197. */
  198.  
  199. function getListeners(that, type, readonly) {
  200. if (readonly && !that[LISTENERS]) return;
  201. var listeners = that[LISTENERS] || (that[LISTENERS] = {});
  202. return listeners[type] || (listeners[type] = []);
  203. }
  204.  
  205. })(EventLite);
  206. var EventLite$1 = _module_.exports;
  207.  
  208. function createPref(DEFAULT, sep = "/") {
  209. let storage;
  210. let currentScope = "global";
  211. let scopeList = ["global"];
  212. const events = new EventLite$1;
  213. const globalCache = {};
  214. let scopedCache = {};
  215. let currentCache = Object.assign({}, DEFAULT);
  216. let initializing;
  217. return Object.assign(events, {
  218. // storage,
  219. // ready,
  220. connect,
  221. disconnect,
  222. get,
  223. getAll,
  224. set,
  225. getCurrentScope,
  226. setCurrentScope,
  227. addScope,
  228. deleteScope,
  229. getScopeList,
  230. import: import_,
  231. export: export_,
  232. has
  233. });
  234. function import_(input) {
  235. const newScopeList = input.scopeList || scopeList.slice();
  236. const scopes = new Set(newScopeList);
  237. if (!scopes.has("global")) {
  238. throw new Error("invalid scopeList");
  239. }
  240. const changes = {
  241. scopeList: newScopeList
  242. };
  243. for (const [scopeName, scope] of Object.entries(input.scopes)) {
  244. if (!scopes.has(scopeName)) {
  245. continue;
  246. }
  247. for (const [key, value] of Object.entries(scope)) {
  248. if (DEFAULT[key] == undefined) {
  249. continue;
  250. }
  251. changes[`${scopeName}${sep}${key}`] = value;
  252. }
  253. }
  254. return storage.setMany(changes);
  255. }
  256. function export_() {
  257. const keys = [];
  258. for (const scope of scopeList) {
  259. keys.push(...Object.keys(DEFAULT).map(k => `${scope}${sep}${k}`));
  260. }
  261. keys.push("scopeList");
  262. return storage.getMany(keys)
  263. .then(changes => {
  264. const _scopeList = changes.scopeList || scopeList.slice();
  265. const scopes = new Set(_scopeList);
  266. const output = {
  267. scopeList: _scopeList,
  268. scopes: {}
  269. };
  270. for (const [key, value] of Object.entries(changes)) {
  271. const sepIndex = key.indexOf(sep);
  272. if (sepIndex < 0) {
  273. continue;
  274. }
  275. const scope = key.slice(0, sepIndex);
  276. const realKey = key.slice(sepIndex + sep.length);
  277. if (!scopes.has(scope)) {
  278. continue;
  279. }
  280. if (DEFAULT[realKey] == undefined) {
  281. continue;
  282. }
  283. if (!output.scopes[scope]) {
  284. output.scopes[scope] = {};
  285. }
  286. output.scopes[scope][realKey] = value;
  287. }
  288. return output;
  289. });
  290. }
  291. function connect(_storage) {
  292. storage = _storage;
  293. initializing = storage.getMany(
  294. Object.keys(DEFAULT).map(k => `global${sep}${k}`).concat(["scopeList"])
  295. )
  296. .then(updateCache);
  297. storage.on("change", updateCache);
  298. return initializing;
  299. }
  300. function disconnect() {
  301. storage.off("change", updateCache);
  302. storage = null;
  303. }
  304. function updateCache(changes, rebuildCache = false) {
  305. if (changes.scopeList) {
  306. scopeList = changes.scopeList;
  307. events.emit("scopeListChange", scopeList);
  308. if (!scopeList.includes(currentScope)) {
  309. return setCurrentScope("global");
  310. }
  311. }
  312. const changedKeys = new Set;
  313. for (const [key, value] of Object.entries(changes)) {
  314. const [scope, realKey] = key.startsWith(`global${sep}`) ? ["global", key.slice(6 + sep.length)] :
  315. key.startsWith(`${currentScope}${sep}`) ? [currentScope, key.slice(currentScope.length + sep.length)] :
  316. [null, null];
  317. if (!scope || DEFAULT[realKey] == null) {
  318. continue;
  319. }
  320. if (scope === "global") {
  321. changedKeys.add(realKey);
  322. globalCache[realKey] = value;
  323. }
  324. if (scope === currentScope) {
  325. changedKeys.add(realKey);
  326. scopedCache[realKey] = value;
  327. }
  328. }
  329. if (rebuildCache) {
  330. Object.keys(DEFAULT).forEach(k => changedKeys.add(k));
  331. }
  332. const realChanges = {};
  333. let isChanged = false;
  334. for (const key of changedKeys) {
  335. const value = scopedCache[key] != null ? scopedCache[key] :
  336. globalCache[key] != null ? globalCache[key] :
  337. DEFAULT[key];
  338. if (currentCache[key] !== value) {
  339. realChanges[key] = value;
  340. currentCache[key] = value;
  341. isChanged = true;
  342. }
  343. }
  344. if (isChanged) {
  345. events.emit("change", realChanges);
  346. }
  347. }
  348. function has(key) {
  349. return currentCache.hasOwnProperty(key);
  350. }
  351. function get(key) {
  352. return currentCache[key];
  353. }
  354. function getAll() {
  355. return Object.assign({}, currentCache);
  356. }
  357. function set(key, value) {
  358. return storage.setMany({
  359. [`${currentScope}${sep}${key}`]: value
  360. });
  361. }
  362. function getCurrentScope() {
  363. return currentScope;
  364. }
  365. function setCurrentScope(newScope) {
  366. if (currentScope === newScope) {
  367. return Promise.resolve(true);
  368. }
  369. if (!scopeList.includes(newScope)) {
  370. return Promise.resolve(false);
  371. }
  372. return storage.getMany(Object.keys(DEFAULT).map(k => `${newScope}${sep}${k}`))
  373. .then(changes => {
  374. currentScope = newScope;
  375. scopedCache = {};
  376. events.emit("scopeChange", currentScope);
  377. updateCache(changes, true);
  378. return true;
  379. });
  380. }
  381. function addScope(scope) {
  382. if (scopeList.includes(scope)) {
  383. return Promise.reject(new Error(`${scope} already exists`));
  384. }
  385. if (scope.includes(sep)) {
  386. return Promise.reject(new Error(`invalid word: ${sep}`));
  387. }
  388. return storage.setMany({
  389. scopeList: scopeList.concat([scope])
  390. });
  391. }
  392. function deleteScope(scope) {
  393. if (scope === "global") {
  394. return Promise.reject(new Error(`cannot delete global`));
  395. }
  396. return Promise.all([
  397. storage.setMany({
  398. scopeList: scopeList.filter(s => s != scope)
  399. }),
  400. storage.deleteMany(Object.keys(DEFAULT).map(k => `${scope}${sep}${k}`))
  401. ]);
  402. }
  403. function getScopeList() {
  404. return scopeList;
  405. }
  406. }
  407.  
  408. const keys = Object.keys;
  409. function isBoolean(val) {
  410. return typeof val === "boolean"
  411. }
  412. function isElement(val) {
  413. return val && typeof val.nodeType === "number"
  414. }
  415. function isString(val) {
  416. return typeof val === "string"
  417. }
  418. function isNumber(val) {
  419. return typeof val === "number"
  420. }
  421. function isObject(val) {
  422. return typeof val === "object" ? val !== null : isFunction(val)
  423. }
  424. function isFunction(val) {
  425. return typeof val === "function"
  426. }
  427. function isArrayLike(obj) {
  428. return isObject(obj) && typeof obj.length === "number" && typeof obj.nodeType !== "number"
  429. }
  430. function forEach(value, fn) {
  431. if (!value) return
  432.  
  433. for (const key of keys(value)) {
  434. fn(value[key], key);
  435. }
  436. }
  437. function isRef(maybeRef) {
  438. return isObject(maybeRef) && "current" in maybeRef
  439. }
  440.  
  441. function _objectWithoutPropertiesLoose(source, excluded) {
  442. if (source == null) return {}
  443. var target = {};
  444. var sourceKeys = Object.keys(source);
  445. var key, i;
  446.  
  447. for (i = 0; i < sourceKeys.length; i++) {
  448. key = sourceKeys[i];
  449. if (excluded.indexOf(key) >= 0) continue
  450. target[key] = source[key];
  451. }
  452.  
  453. return target
  454. }
  455.  
  456. const isUnitlessNumber = {
  457. animationIterationCount: 0,
  458. borderImageOutset: 0,
  459. borderImageSlice: 0,
  460. borderImageWidth: 0,
  461. boxFlex: 0,
  462. boxFlexGroup: 0,
  463. boxOrdinalGroup: 0,
  464. columnCount: 0,
  465. columns: 0,
  466. flex: 0,
  467. flexGrow: 0,
  468. flexPositive: 0,
  469. flexShrink: 0,
  470. flexNegative: 0,
  471. flexOrder: 0,
  472. gridArea: 0,
  473. gridRow: 0,
  474. gridRowEnd: 0,
  475. gridRowSpan: 0,
  476. gridRowStart: 0,
  477. gridColumn: 0,
  478. gridColumnEnd: 0,
  479. gridColumnSpan: 0,
  480. gridColumnStart: 0,
  481. fontWeight: 0,
  482. lineClamp: 0,
  483. lineHeight: 0,
  484. opacity: 0,
  485. order: 0,
  486. orphans: 0,
  487. tabSize: 0,
  488. widows: 0,
  489. zIndex: 0,
  490. zoom: 0,
  491. fillOpacity: 0,
  492. floodOpacity: 0,
  493. stopOpacity: 0,
  494. strokeDasharray: 0,
  495. strokeDashoffset: 0,
  496. strokeMiterlimit: 0,
  497. strokeOpacity: 0,
  498. strokeWidth: 0,
  499. };
  500.  
  501. function prefixKey(prefix, key) {
  502. return prefix + key.charAt(0).toUpperCase() + key.substring(1)
  503. }
  504.  
  505. const prefixes = ["Webkit", "ms", "Moz", "O"];
  506. keys(isUnitlessNumber).forEach((prop) => {
  507. prefixes.forEach((prefix) => {
  508. isUnitlessNumber[prefixKey(prefix, prop)] = 0;
  509. });
  510. });
  511.  
  512. const SVGNamespace = "http://www.w3.org/2000/svg";
  513. const XLinkNamespace = "http://www.w3.org/1999/xlink";
  514. const XMLNamespace = "http://www.w3.org/XML/1998/namespace";
  515.  
  516. function isVisibleChild(value) {
  517. return !isBoolean(value) && value != null
  518. }
  519.  
  520. function className(value) {
  521. if (Array.isArray(value)) {
  522. return value.map(className).filter(Boolean).join(" ")
  523. } else if (isObject(value)) {
  524. return keys(value)
  525. .filter((k) => value[k])
  526. .join(" ")
  527. } else if (isVisibleChild(value)) {
  528. return "" + value
  529. } else {
  530. return ""
  531. }
  532. }
  533. const svg = {
  534. animate: 0,
  535. circle: 0,
  536. clipPath: 0,
  537. defs: 0,
  538. desc: 0,
  539. ellipse: 0,
  540. feBlend: 0,
  541. feColorMatrix: 0,
  542. feComponentTransfer: 0,
  543. feComposite: 0,
  544. feConvolveMatrix: 0,
  545. feDiffuseLighting: 0,
  546. feDisplacementMap: 0,
  547. feDistantLight: 0,
  548. feFlood: 0,
  549. feFuncA: 0,
  550. feFuncB: 0,
  551. feFuncG: 0,
  552. feFuncR: 0,
  553. feGaussianBlur: 0,
  554. feImage: 0,
  555. feMerge: 0,
  556. feMergeNode: 0,
  557. feMorphology: 0,
  558. feOffset: 0,
  559. fePointLight: 0,
  560. feSpecularLighting: 0,
  561. feSpotLight: 0,
  562. feTile: 0,
  563. feTurbulence: 0,
  564. filter: 0,
  565. foreignObject: 0,
  566. g: 0,
  567. image: 0,
  568. line: 0,
  569. linearGradient: 0,
  570. marker: 0,
  571. mask: 0,
  572. metadata: 0,
  573. path: 0,
  574. pattern: 0,
  575. polygon: 0,
  576. polyline: 0,
  577. radialGradient: 0,
  578. rect: 0,
  579. stop: 0,
  580. svg: 0,
  581. switch: 0,
  582. symbol: 0,
  583. text: 0,
  584. textPath: 0,
  585. tspan: 0,
  586. use: 0,
  587. view: 0,
  588. };
  589. function createElement(tag, attr, ...children) {
  590. if (isString(attr) || Array.isArray(attr)) {
  591. children.unshift(attr);
  592. attr = {};
  593. }
  594.  
  595. attr = attr || {};
  596.  
  597. if (!attr.namespaceURI && svg[tag] === 0) {
  598. attr = Object.assign({}, attr, {
  599. namespaceURI: SVGNamespace,
  600. });
  601. }
  602.  
  603. if (attr.children != null && !children.length) {
  604. var _attr = attr
  605. ;({ children } = _attr);
  606. attr = _objectWithoutPropertiesLoose(_attr, ["children"]);
  607. }
  608.  
  609. let node;
  610.  
  611. if (isString(tag)) {
  612. node = attr.namespaceURI
  613. ? document.createElementNS(attr.namespaceURI, tag)
  614. : document.createElement(tag);
  615. attributes(attr, node);
  616. appendChild(children, node);
  617. } else if (isFunction(tag)) {
  618. if (isObject(tag.defaultProps)) {
  619. attr = Object.assign({}, tag.defaultProps, attr);
  620. }
  621.  
  622. node = tag(
  623. Object.assign({}, attr, {
  624. children,
  625. })
  626. );
  627. }
  628.  
  629. if (isRef(attr.ref)) {
  630. attr.ref.current = node;
  631. } else if (isFunction(attr.ref)) {
  632. attr.ref(node);
  633. }
  634.  
  635. return node
  636. }
  637.  
  638. function appendChild(child, node) {
  639. if (isArrayLike(child)) {
  640. appendChildren(child, node);
  641. } else if (isString(child) || isNumber(child)) {
  642. appendChildToNode(document.createTextNode(child), node);
  643. } else if (child === null) {
  644. appendChildToNode(document.createComment(""), node);
  645. } else if (isElement(child)) {
  646. appendChildToNode(child, node);
  647. }
  648. }
  649.  
  650. function appendChildren(children, node) {
  651. for (const child of children) {
  652. appendChild(child, node);
  653. }
  654.  
  655. return node
  656. }
  657.  
  658. function appendChildToNode(child, node) {
  659. if (node instanceof window.HTMLTemplateElement) {
  660. node.content.appendChild(child);
  661. } else {
  662. node.appendChild(child);
  663. }
  664. }
  665.  
  666. function normalizeAttribute(s) {
  667. return s.replace(/[A-Z\d]/g, (match) => ":" + match.toLowerCase())
  668. }
  669.  
  670. function attribute(key, value, node) {
  671. switch (key) {
  672. case "xlinkActuate":
  673. case "xlinkArcrole":
  674. case "xlinkHref":
  675. case "xlinkRole":
  676. case "xlinkShow":
  677. case "xlinkTitle":
  678. case "xlinkType":
  679. attrNS(node, XLinkNamespace, normalizeAttribute(key), value);
  680. return
  681.  
  682. case "xmlnsXlink":
  683. attr(node, normalizeAttribute(key), value);
  684. return
  685.  
  686. case "xmlBase":
  687. case "xmlLang":
  688. case "xmlSpace":
  689. attrNS(node, XMLNamespace, normalizeAttribute(key), value);
  690. return
  691. }
  692.  
  693. switch (key) {
  694. case "htmlFor":
  695. attr(node, "for", value);
  696. return
  697.  
  698. case "dataset":
  699. forEach(value, (dataValue, dataKey) => {
  700. if (dataValue != null) {
  701. node.dataset[dataKey] = dataValue;
  702. }
  703. });
  704. return
  705.  
  706. case "innerHTML":
  707. case "innerText":
  708. case "textContent":
  709. node[key] = value;
  710. return
  711.  
  712. case "spellCheck":
  713. node.spellcheck = value;
  714. return
  715.  
  716. case "class":
  717. case "className":
  718. if (isFunction(value)) {
  719. value(node);
  720. } else {
  721. attr(node, "class", className(value));
  722. }
  723.  
  724. return
  725.  
  726. case "ref":
  727. case "namespaceURI":
  728. return
  729.  
  730. case "style":
  731. if (isObject(value)) {
  732. forEach(value, (val, key) => {
  733. if (isNumber(val) && isUnitlessNumber[key] !== 0) {
  734. node.style[key] = val + "px";
  735. } else {
  736. node.style[key] = val;
  737. }
  738. });
  739. return
  740. }
  741. }
  742.  
  743. if (isFunction(value)) {
  744. if (key[0] === "o" && key[1] === "n") {
  745. node[key.toLowerCase()] = value;
  746. }
  747. } else if (value === true) {
  748. attr(node, key, "");
  749. } else if (value !== false && value != null) {
  750. attr(node, key, value);
  751. }
  752. }
  753.  
  754. function attr(node, key, value) {
  755. node.setAttribute(key, value);
  756. }
  757.  
  758. function attrNS(node, namespace, key, value) {
  759. node.setAttributeNS(namespace, key, value);
  760. }
  761.  
  762. function attributes(attr, node) {
  763. for (const key of keys(attr)) {
  764. attribute(key, attr[key], node);
  765. }
  766.  
  767. return node
  768. }
  769.  
  770. function messageGetter({
  771. getMessage,
  772. DEFAULT
  773. }) {
  774. return (key, params) => {
  775. const message = getMessage(key, params);
  776. if (message) return message;
  777. const defaultMessage = DEFAULT[key];
  778. if (!defaultMessage) return "";
  779. if (!params) return defaultMessage;
  780.  
  781. if (!Array.isArray(params)) {
  782. params = [params];
  783. }
  784.  
  785. return defaultMessage.replace(/\$(\d+)/g, (m, n) => params[n - 1]);
  786. };
  787. }
  788.  
  789. function fallback(getMessage) {
  790. return messageGetter({
  791. getMessage,
  792. DEFAULT: {
  793. currentScopeLabel: "Current scope",
  794. addScopeLabel: "Add new scope",
  795. deleteScopeLabel: "Delete current scope",
  796. learnMoreButton: "Learn more",
  797. importButton: "Import",
  798. exportButton: "Export",
  799. addScopePrompt: "Add new scope",
  800. deleteScopeConfirm: "Delete scope $1?",
  801. importPrompt: "Paste settings",
  802. exportPrompt: "Copy settings"
  803. }
  804. });
  805. }
  806.  
  807. const VALID_CONTROL = new Set(["import", "export", "scope-list", "add-scope", "delete-scope"]);
  808.  
  809. class DefaultMap extends Map {
  810. constructor(getDefault) {
  811. super();
  812. this.getDefault = getDefault;
  813. }
  814.  
  815. get(key) {
  816. let item = super.get(key);
  817.  
  818. if (!item) {
  819. item = this.getDefault();
  820. super.set(key, item);
  821. }
  822.  
  823. return item;
  824. }
  825.  
  826. }
  827.  
  828. function bindInputs(pref, inputs) {
  829. const bounds = [];
  830.  
  831. const onPrefChange = change => {
  832. for (const key in change) {
  833. if (!inputs.has(key)) {
  834. continue;
  835. }
  836.  
  837. for (const input of inputs.get(key)) {
  838. updateInput(input, change[key]);
  839. }
  840. }
  841. };
  842.  
  843. pref.on("change", onPrefChange);
  844. bounds.push(() => pref.off("change", onPrefChange));
  845.  
  846. for (const [key, list] of inputs.entries()) {
  847. for (const input of list) {
  848. const evt = input.hasAttribute("realtime") ? "input" : "change";
  849.  
  850. const onChange = () => updatePref(key, input);
  851.  
  852. input.addEventListener(evt, onChange);
  853. bounds.push(() => input.removeEventListener(evt, onChange));
  854. }
  855. }
  856.  
  857. onPrefChange(pref.getAll());
  858. return () => {
  859. for (const unbind of bounds) {
  860. unbind();
  861. }
  862. };
  863.  
  864. function updatePref(key, input) {
  865. if (!input.checkValidity()) {
  866. return;
  867. }
  868.  
  869. if (input.type === "checkbox") {
  870. pref.set(key, input.checked);
  871. return;
  872. }
  873.  
  874. if (input.type === "radio") {
  875. if (input.checked) {
  876. pref.set(key, input.value);
  877. }
  878.  
  879. return;
  880. }
  881.  
  882. if (input.nodeName === "SELECT" && input.multiple) {
  883. pref.set(key, [...input.options].filter(o => o.selected).map(o => o.value));
  884. return;
  885. }
  886.  
  887. if (input.type === "number" || input.type === "range") {
  888. pref.set(key, Number(input.value));
  889. return;
  890. }
  891.  
  892. pref.set(key, input.value);
  893. }
  894.  
  895. function updateInput(input, value) {
  896. if (input.nodeName === "INPUT" && input.type === "radio") {
  897. input.checked = input.value === value;
  898. return;
  899. }
  900.  
  901. if (input.type === "checkbox") {
  902. input.checked = value;
  903. return;
  904. }
  905.  
  906. if (input.nodeName === "SELECT" && input.multiple) {
  907. const checked = new Set(value);
  908.  
  909. for (const option of input.options) {
  910. option.selected = checked.has(option.value);
  911. }
  912.  
  913. return;
  914. }
  915.  
  916. input.value = value;
  917. }
  918. }
  919.  
  920. function bindFields(pref, fields) {
  921. const onPrefChange = change => {
  922. for (const key in change) {
  923. if (!fields.has(key)) {
  924. continue;
  925. }
  926.  
  927. for (const field of fields.get(key)) {
  928. field.disabled = field.dataset.bindToValue ? field.dataset.bindToValue !== change[key] : !change[key];
  929. }
  930. }
  931. };
  932.  
  933. pref.on("change", onPrefChange);
  934. onPrefChange(pref.getAll());
  935. return () => pref.off("change", onPrefChange);
  936. }
  937.  
  938. function bindControls({
  939. pref,
  940. controls,
  941. alert: _alert = alert,
  942. confirm: _confirm = confirm,
  943. prompt: _prompt = prompt,
  944. getMessage = () => {},
  945. getNewScope = () => ""
  946. }) {
  947. const CONTROL_METHODS = {
  948. "import": ["click", doImport],
  949. "export": ["click", doExport],
  950. "scope-list": ["change", updateCurrentScope],
  951. "add-scope": ["click", addScope],
  952. "delete-scope": ["click", deleteScope]
  953. };
  954.  
  955. for (const type in CONTROL_METHODS) {
  956. for (const el of controls.get(type)) {
  957. el.addEventListener(CONTROL_METHODS[type][0], CONTROL_METHODS[type][1]);
  958. }
  959. }
  960.  
  961. pref.on("scopeChange", updateCurrentScopeEl);
  962. pref.on("scopeListChange", updateScopeList);
  963. updateScopeList();
  964. updateCurrentScopeEl();
  965.  
  966. const _ = fallback(getMessage);
  967.  
  968. return unbind;
  969.  
  970. function unbind() {
  971. pref.off("scopeChange", updateCurrentScopeEl);
  972. pref.off("scopeListChange", updateScopeList);
  973.  
  974. for (const type in CONTROL_METHODS) {
  975. for (const el of controls.get(type)) {
  976. el.removeEventListener(CONTROL_METHODS[type][0], CONTROL_METHODS[type][1]);
  977. }
  978. }
  979. }
  980.  
  981. async function doImport() {
  982. try {
  983. const input = await _prompt(_("importPrompt"));
  984.  
  985. if (input == null) {
  986. return;
  987. }
  988.  
  989. const settings = JSON.parse(input);
  990. return pref.import(settings);
  991. } catch (err) {
  992. await _alert(err.message);
  993. }
  994. }
  995.  
  996. async function doExport() {
  997. try {
  998. const settings = await pref.export();
  999. await _prompt(_("exportPrompt"), JSON.stringify(settings));
  1000. } catch (err) {
  1001. await _alert(err.message);
  1002. }
  1003. }
  1004.  
  1005. function updateCurrentScope(e) {
  1006. pref.setCurrentScope(e.target.value);
  1007. }
  1008.  
  1009. async function addScope() {
  1010. try {
  1011. let scopeName = await _prompt(_("addScopePrompt"), getNewScope());
  1012.  
  1013. if (scopeName == null) {
  1014. return;
  1015. }
  1016.  
  1017. scopeName = scopeName.trim();
  1018.  
  1019. if (!scopeName) {
  1020. throw new Error("the value is empty");
  1021. }
  1022.  
  1023. await pref.addScope(scopeName);
  1024. pref.setCurrentScope(scopeName);
  1025. } catch (err) {
  1026. await _alert(err.message);
  1027. }
  1028. }
  1029.  
  1030. async function deleteScope() {
  1031. try {
  1032. const scopeName = pref.getCurrentScope();
  1033. const result = await _confirm(_("deleteScopeConfirm", scopeName));
  1034.  
  1035. if (result) {
  1036. return pref.deleteScope(scopeName);
  1037. }
  1038. } catch (err) {
  1039. await _alert(err.message);
  1040. }
  1041. }
  1042.  
  1043. function updateCurrentScopeEl() {
  1044. const scopeName = pref.getCurrentScope();
  1045.  
  1046. for (const el of controls.get("scope-list")) {
  1047. el.value = scopeName;
  1048. }
  1049. }
  1050.  
  1051. function updateScopeList() {
  1052. const scopeList = pref.getScopeList();
  1053.  
  1054. for (const el of controls.get("scope-list")) {
  1055. el.innerHTML = "";
  1056. el.append(...scopeList.map(scope => {
  1057. const option = document.createElement("option");
  1058. option.value = scope;
  1059. option.textContent = scope;
  1060. return option;
  1061. }));
  1062. }
  1063. }
  1064. }
  1065.  
  1066. function createBinding({
  1067. pref,
  1068. root,
  1069. elements = root.querySelectorAll("input, textarea, select, fieldset, button"),
  1070. keyPrefix = "pref-",
  1071. controlPrefix = "webext-pref-",
  1072. alert,
  1073. confirm,
  1074. prompt,
  1075. getMessage,
  1076. getNewScope
  1077. }) {
  1078. const inputs = new DefaultMap(() => []);
  1079. const fields = new DefaultMap(() => []);
  1080. const controls = new DefaultMap(() => []);
  1081.  
  1082. for (const element of elements) {
  1083. const id = element.id && stripPrefix(element.id, keyPrefix);
  1084.  
  1085. if (id && pref.has(id)) {
  1086. inputs.get(id).push(element);
  1087. continue;
  1088. }
  1089.  
  1090. if (element.nodeName === "INPUT" && element.type === "radio") {
  1091. const name = element.name && stripPrefix(element.name, keyPrefix);
  1092.  
  1093. if (name && pref.has(name)) {
  1094. inputs.get(name).push(element);
  1095. continue;
  1096. }
  1097. }
  1098.  
  1099. if (element.nodeName === "FIELDSET" && element.dataset.bindTo) {
  1100. fields.get(element.dataset.bindTo).push(element);
  1101. continue;
  1102. }
  1103.  
  1104. const controlType = findControlType(element.classList);
  1105.  
  1106. if (controlType) {
  1107. controls.get(controlType).push(element);
  1108. }
  1109. }
  1110.  
  1111. const bounds = [bindInputs(pref, inputs), bindFields(pref, fields), bindControls({
  1112. pref,
  1113. controls,
  1114. alert,
  1115. confirm,
  1116. prompt,
  1117. getMessage,
  1118. getNewScope
  1119. })];
  1120. return () => {
  1121. for (const unbind of bounds) {
  1122. unbind();
  1123. }
  1124. };
  1125.  
  1126. function stripPrefix(id, prefix) {
  1127. if (!prefix) {
  1128. return id;
  1129. }
  1130.  
  1131. return id.startsWith(prefix) ? id.slice(prefix.length) : "";
  1132. }
  1133.  
  1134. function findControlType(list) {
  1135. for (const name of list) {
  1136. const controlType = stripPrefix(name, controlPrefix);
  1137.  
  1138. if (VALID_CONTROL.has(controlType)) {
  1139. return controlType;
  1140. }
  1141. }
  1142. }
  1143. }
  1144.  
  1145. function createUI({
  1146. body,
  1147. getMessage = () => {},
  1148. toolbar = true,
  1149. navbar = true,
  1150. keyPrefix = "pref-",
  1151. controlPrefix = "webext-pref-"
  1152. }) {
  1153. const root = document.createDocumentFragment();
  1154.  
  1155. const _ = fallback(getMessage);
  1156.  
  1157. if (toolbar) {
  1158. root.append(createToolbar());
  1159. }
  1160.  
  1161. if (navbar) {
  1162. root.append(createNavbar());
  1163. }
  1164.  
  1165. root.append( /*#__PURE__*/createElement("div", {
  1166. class: controlPrefix + "body"
  1167. }, body.map(item => {
  1168. if (!item.hLevel) {
  1169. item.hLevel = 3;
  1170. }
  1171.  
  1172. return createItem(item);
  1173. })));
  1174. return root;
  1175.  
  1176. function createToolbar() {
  1177. return /*#__PURE__*/createElement("div", {
  1178. class: controlPrefix + "toolbar"
  1179. }, /*#__PURE__*/createElement("button", {
  1180. type: "button",
  1181. class: [controlPrefix + "import", "browser-style"]
  1182. }, _("importButton")), /*#__PURE__*/createElement("button", {
  1183. type: "button",
  1184. class: [controlPrefix + "export", "browser-style"]
  1185. }, _("exportButton")));
  1186. }
  1187.  
  1188. function createNavbar() {
  1189. return /*#__PURE__*/createElement("div", {
  1190. class: controlPrefix + "nav"
  1191. }, /*#__PURE__*/createElement("select", {
  1192. class: [controlPrefix + "scope-list", "browser-style"],
  1193. title: _("currentScopeLabel")
  1194. }), /*#__PURE__*/createElement("button", {
  1195. type: "button",
  1196. class: [controlPrefix + "delete-scope", "browser-style"],
  1197. title: _("deleteScopeLabel")
  1198. }, "\xD7"), /*#__PURE__*/createElement("button", {
  1199. type: "button",
  1200. class: [controlPrefix + "add-scope", "browser-style"],
  1201. title: _("addScopeLabel")
  1202. }, "+"));
  1203. }
  1204.  
  1205. function createItem(p) {
  1206. if (p.type === "section") {
  1207. return createSection(p);
  1208. }
  1209.  
  1210. if (p.type === "checkbox") {
  1211. return createCheckbox(p);
  1212. }
  1213.  
  1214. if (p.type === "radiogroup") {
  1215. return createRadioGroup(p);
  1216. }
  1217.  
  1218. return createInput(p);
  1219. }
  1220.  
  1221. function createInput(p) {
  1222. const key = keyPrefix + p.key;
  1223. let input;
  1224. const onChange = p.validate ? e => {
  1225. try {
  1226. p.validate(e.target.value);
  1227. e.target.setCustomValidity("");
  1228. } catch (err) {
  1229. e.target.setCustomValidity(err.message || String(err));
  1230. }
  1231. } : null;
  1232.  
  1233. if (p.type === "select") {
  1234. input = /*#__PURE__*/createElement("select", {
  1235. multiple: p.multiple,
  1236. class: "browser-style",
  1237. id: key,
  1238. onChange: onChange
  1239. }, Object.entries(p.options).map(([value, label]) => /*#__PURE__*/createElement("option", {
  1240. value: value
  1241. }, label)));
  1242. } else if (p.type === "textarea") {
  1243. input = /*#__PURE__*/createElement("textarea", {
  1244. rows: "8",
  1245. class: "browser-style",
  1246. id: key,
  1247. onChange: onChange
  1248. });
  1249. } else {
  1250. input = /*#__PURE__*/createElement("input", {
  1251. type: p.type,
  1252. id: key,
  1253. onChange: onChange
  1254. });
  1255. }
  1256.  
  1257. return /*#__PURE__*/createElement("div", {
  1258. class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
  1259. }, /*#__PURE__*/createElement("label", {
  1260. htmlFor: key
  1261. }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
  1262. url: p.learnMore
  1263. }), input, p.help && /*#__PURE__*/createElement(Help, {
  1264. content: p.help
  1265. }));
  1266. }
  1267.  
  1268. function createRadioGroup(p) {
  1269. return /*#__PURE__*/createElement("div", {
  1270. class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
  1271. }, /*#__PURE__*/createElement("div", {
  1272. class: controlPrefix + "radio-title"
  1273. }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
  1274. url: p.learnMore
  1275. }), p.help && /*#__PURE__*/createElement(Help, {
  1276. content: p.help
  1277. }), p.children.map(c => {
  1278. c.parentKey = p.key;
  1279. return createCheckbox(inheritProp(p, c));
  1280. }));
  1281. }
  1282.  
  1283. function Help({
  1284. content
  1285. }) {
  1286. return /*#__PURE__*/createElement("p", {
  1287. class: controlPrefix + "help"
  1288. }, content);
  1289. }
  1290.  
  1291. function LearnMore({
  1292. url
  1293. }) {
  1294. return /*#__PURE__*/createElement("a", {
  1295. href: url,
  1296. class: controlPrefix + "learn-more",
  1297. target: "_blank",
  1298. rel: "noopener noreferrer"
  1299. }, _("learnMoreButton"));
  1300. }
  1301.  
  1302. function createCheckbox(p) {
  1303. const id = p.parentKey ? `${keyPrefix}${p.parentKey}-${p.value}` : keyPrefix + p.key;
  1304. return /*#__PURE__*/createElement("div", {
  1305. class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
  1306. }, /*#__PURE__*/createElement("input", {
  1307. type: p.type,
  1308. id: id,
  1309. name: p.parentKey ? keyPrefix + p.parentKey : null,
  1310. value: p.value
  1311. }), /*#__PURE__*/createElement("label", {
  1312. htmlFor: id
  1313. }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
  1314. url: p.learnMore
  1315. }), p.help && /*#__PURE__*/createElement(Help, {
  1316. content: p.help
  1317. }), p.children && /*#__PURE__*/createElement("fieldset", {
  1318. class: controlPrefix + "checkbox-children",
  1319. dataset: {
  1320. bindTo: p.parentKey || p.key,
  1321. bindToValue: p.value
  1322. }
  1323. }, p.children.map(c => createItem(inheritProp(p, c)))));
  1324. }
  1325.  
  1326. function createSection(p) {
  1327. const Header = `h${p.hLevel}`;
  1328. p.hLevel++;
  1329. return (
  1330. /*#__PURE__*/
  1331. // FIXME: do we need browser-style for section?
  1332. createElement("div", {
  1333. class: [controlPrefix + p.type, p.className]
  1334. }, /*#__PURE__*/createElement(Header, {
  1335. class: controlPrefix + "header"
  1336. }, p.label), p.help && /*#__PURE__*/createElement(Help, {
  1337. content: p.help
  1338. }), p.children && p.children.map(c => createItem(inheritProp(p, c))))
  1339. );
  1340. }
  1341.  
  1342. function inheritProp(parent, child) {
  1343. child.hLevel = parent.hLevel;
  1344. return child;
  1345. }
  1346. }
  1347.  
  1348. /* eslint-env greasemonkey */
  1349.  
  1350. function createGMStorage() {
  1351. const setValue = typeof GM_setValue === "function" ?
  1352. promisify(GM_setValue) : GM.setValue.bind(GM);
  1353. const getValue = typeof GM_getValue === "function" ?
  1354. promisify(GM_getValue) : GM.getValue.bind(GM);
  1355. const deleteValue = typeof GM_deleteValue === "function" ?
  1356. promisify(GM_deleteValue) : GM.deleteValue.bind(GM);
  1357. const events = new EventLite$1;
  1358. if (typeof GM_addValueChangeListener === "function") {
  1359. GM_addValueChangeListener("webext-pref-message", (name, oldValue, newValue) => {
  1360. const changes = JSON.parse(newValue);
  1361. for (const key of Object.keys(changes)) {
  1362. if (typeof changes[key] === "object" && changes[key].$undefined) {
  1363. changes[key] = undefined;
  1364. }
  1365. }
  1366. events.emit("change", changes);
  1367. });
  1368. }
  1369. return Object.assign(events, {getMany, setMany, deleteMany});
  1370. function getMany(keys) {
  1371. return Promise.all(keys.map(k =>
  1372. getValue(`webext-pref/${k}`)
  1373. .then(value => [k, typeof value === "string" ? JSON.parse(value) : value])
  1374. ))
  1375. .then(entries => {
  1376. const output = {};
  1377. for (const [key, value] of entries) {
  1378. output[key] = value;
  1379. }
  1380. return output;
  1381. });
  1382. }
  1383. function setMany(changes) {
  1384. return Promise.all(Object.entries(changes).map(([key, value]) =>
  1385. setValue(`webext-pref/${key}`, JSON.stringify(value))
  1386. ))
  1387. .then(() => {
  1388. if (typeof GM_addValueChangeListener === "function") {
  1389. return setValue("webext-pref-message", JSON.stringify(changes));
  1390. }
  1391. events.emit("change", changes);
  1392. });
  1393. }
  1394. function deleteMany(keys) {
  1395. return Promise.all(keys.map(k => deleteValue(`webext-pref/${k}`)))
  1396. .then(() => {
  1397. if (typeof GM_addValueChangeListener === "function") {
  1398. const changes = {};
  1399. for (const key of keys) {
  1400. changes[key] = {
  1401. $undefined: true
  1402. };
  1403. }
  1404. return setValue("webext-pref-message", JSON.stringify(changes));
  1405. }
  1406. const changes = {};
  1407. for (const key of keys) {
  1408. changes[key] = undefined;
  1409. }
  1410. events.emit("change", changes);
  1411. });
  1412. }
  1413. function promisify(fn) {
  1414. return (...args) => {
  1415. try {
  1416. return Promise.resolve(fn(...args));
  1417. } catch (err) {
  1418. return Promise.reject(err);
  1419. }
  1420. };
  1421. }
  1422. }
  1423.  
  1424. /* eslint-env greasemonkey */
  1425.  
  1426. function GM_webextPref({
  1427. default: default_,
  1428. separator,
  1429. css = "",
  1430. ...options
  1431. }) {
  1432. const pref = createPref(default_, separator);
  1433. const initializing = pref.connect(createGMStorage());
  1434. let isOpen = false;
  1435. const registerMenu =
  1436. typeof GM_registerMenuCommand === "function" ? GM_registerMenuCommand :
  1437. typeof GM !== "undefined" && GM && GM.registerMenuCommand ? GM.registerMenuCommand.bind(GM) :
  1438. undefined;
  1439. if (registerMenu) {
  1440. registerMenu(`${getTitle()} - Configure`, openDialog);
  1441. }
  1442. return Object.assign(pref, {
  1443. ready: () => initializing,
  1444. openDialog
  1445. });
  1446. function openDialog() {
  1447. if (isOpen) {
  1448. return;
  1449. }
  1450. isOpen = true;
  1451. let destroyView;
  1452. const modal = document.createElement("div");
  1453. modal.className = "webext-pref-modal";
  1454. modal.onclick = () => {
  1455. modal.classList.remove("webext-pref-modal-open");
  1456. modal.addEventListener("transitionend", () => {
  1457. if (destroyView) {
  1458. destroyView();
  1459. }
  1460. modal.remove();
  1461. isOpen = false;
  1462. });
  1463. };
  1464. const style = document.createElement("style");
  1465. 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}" + `
  1466. body {
  1467. padding-right: ${window.innerWidth - document.documentElement.offsetWidth}px;
  1468. }
  1469. `;
  1470. const iframe = document.createElement("iframe");
  1471. iframe.className = "webext-pref-iframe";
  1472. iframe.srcdoc = `
  1473. <html>
  1474. <head>
  1475. <style class="dialog-style"></style>
  1476. </head>
  1477. <body>
  1478. <div class="dialog-body"></div>
  1479. </body>
  1480. </html>
  1481. `;
  1482. const wrap = document.createElement("div");
  1483. wrap.className = "webext-pref-iframe-wrap";
  1484. wrap.append(iframe);
  1485. modal.append(style, wrap);
  1486. document.body.appendChild(modal);
  1487. iframe.onload = () => {
  1488. iframe.onload = null;
  1489. 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;
  1490. const root = iframe.contentDocument.querySelector(".dialog-body");
  1491. root.append(createUI(options));
  1492. destroyView = createBinding({
  1493. pref,
  1494. root,
  1495. ...options
  1496. });
  1497. const title = document.createElement("h2");
  1498. title.className = "dialog-title";
  1499. title.textContent = getTitle();
  1500. iframe.contentDocument.querySelector(".webext-pref-toolbar").prepend(title);
  1501. if (iframe.contentDocument.body.offsetWidth > modal.offsetWidth) {
  1502. iframe.contentDocument.body.classList.add("responsive");
  1503. }
  1504. // calc iframe size
  1505. iframe.style = `
  1506. width: ${iframe.contentDocument.body.offsetWidth}px;
  1507. height: ${iframe.contentDocument.body.scrollHeight}px;
  1508. `;
  1509. modal.classList.add("webext-pref-modal-open");
  1510. };
  1511. }
  1512. function getTitle() {
  1513. return typeof GM_info === "object" ?
  1514. GM_info.script.name : GM.info.script.name;
  1515. }
  1516. }
  1517.  
  1518. return GM_webextPref;
  1519.  
  1520. }());