Linkify Plus Plus

Based on Linkify Plus. Turn plain text URLs into links.

  1. // ==UserScript==
  2. // @name Linkify Plus Plus
  3. // @version 12.0.1
  4. // @description Based on Linkify Plus. Turn plain text URLs into links.
  5. // @license BSD-3-Clause
  6. // @author eight04 <eight04@gmail.com>
  7. // @homepageURL https://github.com/eight04/linkify-plus-plus
  8. // @supportURL https://github.com/eight04/linkify-plus-plus/issues
  9. // @namespace eight04.blogspot.com
  10. // @include *
  11. // @exclude https://www.google.*/search*
  12. // @exclude https://www.google.*/webhp*
  13. // @exclude https://music.google.com/*
  14. // @exclude https://mail.google.com/*
  15. // @exclude https://docs.google.com/*
  16. // @exclude https://encrypted.google.com/*
  17. // @exclude https://*101weiqi.com/*
  18. // @exclude https://w3c*.github.io/*
  19. // @exclude https://www.paypal.com/*
  20. // @exclude https://term.ptt.cc/*
  21. // @exclude https://mastodon.social/*
  22. // @grant GM.getValue
  23. // @grant GM.setValue
  24. // @grant GM.deleteValue
  25. // @grant GM_addStyle
  26. // @grant GM_registerMenuCommand
  27. // @grant GM_getValue
  28. // @grant GM_setValue
  29. // @grant GM_deleteValue
  30. // @grant GM_addValueChangeListener
  31. // @grant unsafeWindow
  32. // @compatible firefox Tampermonkey latest
  33. // @compatible chrome Tampermonkey latest
  34. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDE2IDE2Ij4gPHBhdGggZmlsbD0iIzRjNGM0ZCIgZD0iTTMuNSAxYS41LjUgMCAxIDAgMCAxSDR2OWgtLjVhLjUuNSAwIDAgMCAwIDFoNy44NTVhLjUuNSAwIDAgMCAuNDc1LS4xODQuNS41IDAgMCAwIC4xMDYtLjM5OFYxMC41YS41LjUgMCAxIDAtMSAwdi41SDZWMmguNWEuNS41IDAgMSAwIDAtMWgtM3oiLz4gPHBhdGggZmlsbD0iIzQ1YTFmZiIgZD0iTTIuNSAxNGExIDEgMCAxIDAgMCAyaDExYTEgMSAwIDEgMCAwLTJoLTExeiIvPiA8L3N2Zz4=
  35. // ==/UserScript==
  36.  
  37. var optionsFuzzyIpLabel = "Match ambiguous IP addresses.";
  38. var optionsIgnoreMustacheLabel = "Ignore URLs inside mustaches e.g. {{ ... }}.";
  39. var optionsEmbedImageLabel = "Create an image element if the URL looks like an image file.";
  40. var optionsEmbedImageExcludeElementLabel = "Exclude following elements. (CSS selector)";
  41. var optionsUnicodeLabel = "Match unicode characters.";
  42. var optionsMailLabel = "Match email address.";
  43. var optionsNewTabLabel = "Open links in new tabs.";
  44. var optionsStandaloneLabel = "The URL must be surrounded by whitespaces.";
  45. var optionsLinkifierLabel = "Linkifier";
  46. var optionsTriggerByPageLoadLabel = "Trigger on page load.";
  47. var optionsTriggerByNewNodeLabel = "Trigger on dynamically created elements.";
  48. var optionsTriggerByHoverLabel = "Trigger on mouse over.";
  49. var optionsTriggerByClickLabel = "Trigger on mouse click.";
  50. var optionsBoundaryLeftLabel = "Allowed characters between the whitespace and the link. (left side)";
  51. var optionsBoundaryRightLabel = "Allowed characters between the whitespace and the link. (right side)";
  52. var optionsExcludeElementLabel = "Do not linkify following elements. (CSS selector)";
  53. var optionsIncludeElementLabel = "Always linkify following elements. Override above. (CSS selector)";
  54. var optionsTimeoutLabel = "Max execution time. (ms)";
  55. var optionsTimeoutHelp = "The script will terminate if it takes too long to convert the entire page.";
  56. var optionsMaxRunTimeLabel = "Max script run time. (ms)";
  57. var optionsMaxRunTimeHelp = "If the script takes too long to run, the process would be splitted into small chunks to avoid browser freeze.";
  58. var optionsUrlMatcherLabel = "URL matcher";
  59. var optionsCustomRulesLabel = "Custom rules. (RegExp per line)";
  60. var currentScopeLabel = "Current domain";
  61. var addScopeLabel = "Add new domain";
  62. var addScopePrompt = "Add new domain";
  63. var deleteScopeLabel = "Delete current domain";
  64. var deleteScopeConfirm = "Delete domain $1?";
  65. var learnMoreButton = "Learn more";
  66. var importButton = "Import";
  67. var importPrompt = "Paste settings";
  68. var exportButton = "Export";
  69. var exportPrompt = "Copy settings";
  70. var translate = {
  71. optionsFuzzyIpLabel: optionsFuzzyIpLabel,
  72. optionsIgnoreMustacheLabel: optionsIgnoreMustacheLabel,
  73. optionsEmbedImageLabel: optionsEmbedImageLabel,
  74. optionsEmbedImageExcludeElementLabel: optionsEmbedImageExcludeElementLabel,
  75. optionsUnicodeLabel: optionsUnicodeLabel,
  76. optionsMailLabel: optionsMailLabel,
  77. optionsNewTabLabel: optionsNewTabLabel,
  78. optionsStandaloneLabel: optionsStandaloneLabel,
  79. optionsLinkifierLabel: optionsLinkifierLabel,
  80. optionsTriggerByPageLoadLabel: optionsTriggerByPageLoadLabel,
  81. optionsTriggerByNewNodeLabel: optionsTriggerByNewNodeLabel,
  82. optionsTriggerByHoverLabel: optionsTriggerByHoverLabel,
  83. optionsTriggerByClickLabel: optionsTriggerByClickLabel,
  84. optionsBoundaryLeftLabel: optionsBoundaryLeftLabel,
  85. optionsBoundaryRightLabel: optionsBoundaryRightLabel,
  86. optionsExcludeElementLabel: optionsExcludeElementLabel,
  87. optionsIncludeElementLabel: optionsIncludeElementLabel,
  88. optionsTimeoutLabel: optionsTimeoutLabel,
  89. optionsTimeoutHelp: optionsTimeoutHelp,
  90. optionsMaxRunTimeLabel: optionsMaxRunTimeLabel,
  91. optionsMaxRunTimeHelp: optionsMaxRunTimeHelp,
  92. optionsUrlMatcherLabel: optionsUrlMatcherLabel,
  93. optionsCustomRulesLabel: optionsCustomRulesLabel,
  94. currentScopeLabel: currentScopeLabel,
  95. addScopeLabel: addScopeLabel,
  96. addScopePrompt: addScopePrompt,
  97. deleteScopeLabel: deleteScopeLabel,
  98. deleteScopeConfirm: deleteScopeConfirm,
  99. learnMoreButton: learnMoreButton,
  100. importButton: importButton,
  101. importPrompt: importPrompt,
  102. exportButton: exportButton,
  103. exportPrompt: exportPrompt
  104. };
  105.  
  106. /**
  107. * event-lite.js - Light-weight EventEmitter (less than 1KB when gzipped)
  108. *
  109. * @copyright Yusuke Kawasaki
  110. * @license MIT
  111. * @constructor
  112. * @see https://github.com/kawanet/event-lite
  113. * @see http://kawanet.github.io/event-lite/EventLite.html
  114. * @example
  115. * var EventLite = require("event-lite");
  116. *
  117. * function MyClass() {...} // your class
  118. *
  119. * EventLite.mixin(MyClass.prototype); // import event methods
  120. *
  121. * var obj = new MyClass();
  122. * obj.on("foo", function() {...}); // add event listener
  123. * obj.once("bar", function() {...}); // add one-time event listener
  124. * obj.emit("foo"); // dispatch event
  125. * obj.emit("bar"); // dispatch another event
  126. * obj.off("foo"); // remove event listener
  127. */
  128.  
  129. function EventLite() {
  130. if (!(this instanceof EventLite)) return new EventLite();
  131. }
  132.  
  133. // (function(EventLite) {
  134. // export the class for node.js
  135. // if ("undefined" !== typeof module) module.exports = EventLite;
  136.  
  137. // property name to hold listeners
  138. var LISTENERS = "listeners";
  139.  
  140. // methods to export
  141. var methods = {
  142. on: on,
  143. once: once,
  144. off: off,
  145. emit: emit
  146. };
  147.  
  148. // mixin to self
  149. mixin(EventLite.prototype);
  150.  
  151. // export mixin function
  152. EventLite.mixin = mixin;
  153.  
  154. /**
  155. * Import on(), once(), off() and emit() methods into target object.
  156. *
  157. * @function EventLite.mixin
  158. * @param target {Prototype}
  159. */
  160.  
  161. function mixin(target) {
  162. for (var key in methods) {
  163. target[key] = methods[key];
  164. }
  165. return target;
  166. }
  167.  
  168. /**
  169. * Add an event listener.
  170. *
  171. * @function EventLite.prototype.on
  172. * @param type {string}
  173. * @param func {Function}
  174. * @returns {EventLite} Self for method chaining
  175. */
  176.  
  177. function on(type, func) {
  178. getListeners(this, type).push(func);
  179. return this;
  180. }
  181.  
  182. /**
  183. * Add one-time event listener.
  184. *
  185. * @function EventLite.prototype.once
  186. * @param type {string}
  187. * @param func {Function}
  188. * @returns {EventLite} Self for method chaining
  189. */
  190.  
  191. function once(type, func) {
  192. var that = this;
  193. wrap.originalListener = func;
  194. getListeners(that, type).push(wrap);
  195. return that;
  196.  
  197. function wrap() {
  198. off.call(that, type, wrap);
  199. func.apply(this, arguments);
  200. }
  201. }
  202.  
  203. /**
  204. * Remove an event listener.
  205. *
  206. * @function EventLite.prototype.off
  207. * @param [type] {string}
  208. * @param [func] {Function}
  209. * @returns {EventLite} Self for method chaining
  210. */
  211.  
  212. function off(type, func) {
  213. var that = this;
  214. var listners;
  215. if (!arguments.length) {
  216. delete that[LISTENERS];
  217. } else if (!func) {
  218. listners = that[LISTENERS];
  219. if (listners) {
  220. delete listners[type];
  221. if (!Object.keys(listners).length) return off.call(that);
  222. }
  223. } else {
  224. listners = getListeners(that, type, true);
  225. if (listners) {
  226. listners = listners.filter(ne);
  227. if (!listners.length) return off.call(that, type);
  228. that[LISTENERS][type] = listners;
  229. }
  230. }
  231. return that;
  232.  
  233. function ne(test) {
  234. return test !== func && test.originalListener !== func;
  235. }
  236. }
  237.  
  238. /**
  239. * Dispatch (trigger) an event.
  240. *
  241. * @function EventLite.prototype.emit
  242. * @param type {string}
  243. * @param [value] {*}
  244. * @returns {boolean} True when a listener received the event
  245. */
  246.  
  247. function emit(type, value) {
  248. var that = this;
  249. var listeners = getListeners(that, type, true);
  250. if (!listeners) return false;
  251. var arglen = arguments.length;
  252. if (arglen === 1) {
  253. listeners.forEach(zeroarg);
  254. } else if (arglen === 2) {
  255. listeners.forEach(onearg);
  256. } else {
  257. var args = Array.prototype.slice.call(arguments, 1);
  258. listeners.forEach(moreargs);
  259. }
  260. return !!listeners.length;
  261.  
  262. function zeroarg(func) {
  263. func.call(that);
  264. }
  265.  
  266. function onearg(func) {
  267. func.call(that, value);
  268. }
  269.  
  270. function moreargs(func) {
  271. func.apply(that, args);
  272. }
  273. }
  274.  
  275. /**
  276. * @ignore
  277. */
  278.  
  279. function getListeners(that, type, readonly) {
  280. if (readonly && !that[LISTENERS]) return;
  281. var listeners = that[LISTENERS] || (that[LISTENERS] = {});
  282. return listeners[type] || (listeners[type] = []);
  283. }
  284.  
  285. // })(EventLite);
  286.  
  287. function createPref(DEFAULT, sep = "/") {
  288. let storage;
  289. let currentScope = "global";
  290. let scopeList = ["global"];
  291. const events = new EventLite;
  292. const globalCache = {};
  293. let scopedCache = {};
  294. let currentCache = Object.assign({}, DEFAULT);
  295. let initializing;
  296. return Object.assign(events, {
  297. // storage,
  298. // ready,
  299. connect,
  300. disconnect,
  301. get,
  302. getAll,
  303. set,
  304. getCurrentScope,
  305. setCurrentScope,
  306. addScope,
  307. deleteScope,
  308. getScopeList,
  309. import: import_,
  310. export: export_,
  311. has
  312. });
  313. function import_(input) {
  314. const newScopeList = input.scopeList || scopeList.slice();
  315. const scopes = new Set(newScopeList);
  316. if (!scopes.has("global")) {
  317. throw new Error("invalid scopeList");
  318. }
  319. const changes = {
  320. scopeList: newScopeList
  321. };
  322. for (const [scopeName, scope] of Object.entries(input.scopes)) {
  323. if (!scopes.has(scopeName)) {
  324. continue;
  325. }
  326. for (const [key, value] of Object.entries(scope)) {
  327. if (DEFAULT[key] == undefined) {
  328. continue;
  329. }
  330. changes[`${scopeName}${sep}${key}`] = value;
  331. }
  332. }
  333. return storage.setMany(changes);
  334. }
  335. function export_() {
  336. const keys = [];
  337. for (const scope of scopeList) {
  338. keys.push(...Object.keys(DEFAULT).map(k => `${scope}${sep}${k}`));
  339. }
  340. keys.push("scopeList");
  341. return storage.getMany(keys)
  342. .then(changes => {
  343. const _scopeList = changes.scopeList || scopeList.slice();
  344. const scopes = new Set(_scopeList);
  345. const output = {
  346. scopeList: _scopeList,
  347. scopes: {}
  348. };
  349. for (const [key, value] of Object.entries(changes)) {
  350. const sepIndex = key.indexOf(sep);
  351. if (sepIndex < 0) {
  352. continue;
  353. }
  354. const scope = key.slice(0, sepIndex);
  355. const realKey = key.slice(sepIndex + sep.length);
  356. if (!scopes.has(scope)) {
  357. continue;
  358. }
  359. if (DEFAULT[realKey] == undefined) {
  360. continue;
  361. }
  362. if (!output.scopes[scope]) {
  363. output.scopes[scope] = {};
  364. }
  365. output.scopes[scope][realKey] = value;
  366. }
  367. return output;
  368. });
  369. }
  370. function connect(_storage) {
  371. storage = _storage;
  372. initializing = storage.getMany(
  373. Object.keys(DEFAULT).map(k => `global${sep}${k}`).concat(["scopeList"])
  374. )
  375. .then(updateCache);
  376. storage.on("change", updateCache);
  377. return initializing;
  378. }
  379. function disconnect() {
  380. storage.off("change", updateCache);
  381. storage = null;
  382. }
  383. function updateCache(changes, rebuildCache = false) {
  384. if (changes.scopeList) {
  385. scopeList = changes.scopeList;
  386. events.emit("scopeListChange", scopeList);
  387. if (!scopeList.includes(currentScope)) {
  388. return setCurrentScope("global");
  389. }
  390. }
  391. const changedKeys = new Set;
  392. for (const [key, value] of Object.entries(changes)) {
  393. const [scope, realKey] = key.startsWith(`global${sep}`) ? ["global", key.slice(6 + sep.length)] :
  394. key.startsWith(`${currentScope}${sep}`) ? [currentScope, key.slice(currentScope.length + sep.length)] :
  395. [null, null];
  396. if (!scope || DEFAULT[realKey] == null) {
  397. continue;
  398. }
  399. if (scope === "global") {
  400. changedKeys.add(realKey);
  401. globalCache[realKey] = value;
  402. }
  403. if (scope === currentScope) {
  404. changedKeys.add(realKey);
  405. scopedCache[realKey] = value;
  406. }
  407. }
  408. if (rebuildCache) {
  409. Object.keys(DEFAULT).forEach(k => changedKeys.add(k));
  410. }
  411. const realChanges = {};
  412. let isChanged = false;
  413. for (const key of changedKeys) {
  414. const value = scopedCache[key] != null ? scopedCache[key] :
  415. globalCache[key] != null ? globalCache[key] :
  416. DEFAULT[key];
  417. if (currentCache[key] !== value) {
  418. realChanges[key] = value;
  419. currentCache[key] = value;
  420. isChanged = true;
  421. }
  422. }
  423. if (isChanged) {
  424. events.emit("change", realChanges);
  425. }
  426. }
  427. function has(key) {
  428. return currentCache.hasOwnProperty(key);
  429. }
  430. function get(key) {
  431. return currentCache[key];
  432. }
  433. function getAll() {
  434. return Object.assign({}, currentCache);
  435. }
  436. function set(key, value) {
  437. return storage.setMany({
  438. [`${currentScope}${sep}${key}`]: value
  439. });
  440. }
  441. function getCurrentScope() {
  442. return currentScope;
  443. }
  444. function setCurrentScope(newScope) {
  445. if (currentScope === newScope) {
  446. return Promise.resolve(true);
  447. }
  448. if (!scopeList.includes(newScope)) {
  449. return Promise.resolve(false);
  450. }
  451. return storage.getMany(Object.keys(DEFAULT).map(k => `${newScope}${sep}${k}`))
  452. .then(changes => {
  453. currentScope = newScope;
  454. scopedCache = {};
  455. events.emit("scopeChange", currentScope);
  456. updateCache(changes, true);
  457. return true;
  458. });
  459. }
  460. function addScope(scope) {
  461. if (scopeList.includes(scope)) {
  462. return Promise.reject(new Error(`${scope} already exists`));
  463. }
  464. if (scope.includes(sep)) {
  465. return Promise.reject(new Error(`invalid word: ${sep}`));
  466. }
  467. return storage.setMany({
  468. scopeList: scopeList.concat([scope])
  469. });
  470. }
  471. function deleteScope(scope) {
  472. if (scope === "global") {
  473. return Promise.reject(new Error(`cannot delete global`));
  474. }
  475. return Promise.all([
  476. storage.setMany({
  477. scopeList: scopeList.filter(s => s != scope)
  478. }),
  479. storage.deleteMany(Object.keys(DEFAULT).map(k => `${scope}${sep}${k}`))
  480. ]);
  481. }
  482. function getScopeList() {
  483. return scopeList;
  484. }
  485. }
  486.  
  487. const keys = Object.keys;
  488. function isBoolean(val) {
  489. return typeof val === "boolean"
  490. }
  491. function isElement(val) {
  492. return val && typeof val.nodeType === "number"
  493. }
  494. function isString(val) {
  495. return typeof val === "string"
  496. }
  497. function isNumber(val) {
  498. return typeof val === "number"
  499. }
  500. function isObject(val) {
  501. return typeof val === "object" ? val !== null : isFunction(val)
  502. }
  503. function isFunction(val) {
  504. return typeof val === "function"
  505. }
  506. function isArrayLike(obj) {
  507. return isObject(obj) && typeof obj.length === "number" && typeof obj.nodeType !== "number"
  508. }
  509. function forEach(value, fn) {
  510. if (!value) return
  511.  
  512. for (const key of keys(value)) {
  513. fn(value[key], key);
  514. }
  515. }
  516. function isRef(maybeRef) {
  517. return isObject(maybeRef) && "current" in maybeRef
  518. }
  519.  
  520. const isUnitlessNumber = {
  521. animationIterationCount: 0,
  522. borderImageOutset: 0,
  523. borderImageSlice: 0,
  524. borderImageWidth: 0,
  525. boxFlex: 0,
  526. boxFlexGroup: 0,
  527. boxOrdinalGroup: 0,
  528. columnCount: 0,
  529. columns: 0,
  530. flex: 0,
  531. flexGrow: 0,
  532. flexPositive: 0,
  533. flexShrink: 0,
  534. flexNegative: 0,
  535. flexOrder: 0,
  536. gridArea: 0,
  537. gridRow: 0,
  538. gridRowEnd: 0,
  539. gridRowSpan: 0,
  540. gridRowStart: 0,
  541. gridColumn: 0,
  542. gridColumnEnd: 0,
  543. gridColumnSpan: 0,
  544. gridColumnStart: 0,
  545. fontWeight: 0,
  546. lineClamp: 0,
  547. lineHeight: 0,
  548. opacity: 0,
  549. order: 0,
  550. orphans: 0,
  551. tabSize: 0,
  552. widows: 0,
  553. zIndex: 0,
  554. zoom: 0,
  555. fillOpacity: 0,
  556. floodOpacity: 0,
  557. stopOpacity: 0,
  558. strokeDasharray: 0,
  559. strokeDashoffset: 0,
  560. strokeMiterlimit: 0,
  561. strokeOpacity: 0,
  562. strokeWidth: 0,
  563. };
  564.  
  565. function prefixKey(prefix, key) {
  566. return prefix + key.charAt(0).toUpperCase() + key.substring(1)
  567. }
  568.  
  569. const prefixes = ["Webkit", "ms", "Moz", "O"];
  570. keys(isUnitlessNumber).forEach((prop) => {
  571. prefixes.forEach((prefix) => {
  572. isUnitlessNumber[prefixKey(prefix, prop)] = 0;
  573. });
  574. });
  575.  
  576. const SVGNamespace = "http://www.w3.org/2000/svg";
  577. const XLinkNamespace = "http://www.w3.org/1999/xlink";
  578. const XMLNamespace = "http://www.w3.org/XML/1998/namespace";
  579.  
  580. function isVisibleChild(value) {
  581. return !isBoolean(value) && value != null
  582. }
  583.  
  584. function className(value) {
  585. if (Array.isArray(value)) {
  586. return value.map(className).filter(Boolean).join(" ")
  587. } else if (isObject(value)) {
  588. return keys(value)
  589. .filter((k) => value[k])
  590. .join(" ")
  591. } else if (isVisibleChild(value)) {
  592. return "" + value
  593. } else {
  594. return ""
  595. }
  596. }
  597. const svg = {
  598. animate: 0,
  599. circle: 0,
  600. clipPath: 0,
  601. defs: 0,
  602. desc: 0,
  603. ellipse: 0,
  604. feBlend: 0,
  605. feColorMatrix: 0,
  606. feComponentTransfer: 0,
  607. feComposite: 0,
  608. feConvolveMatrix: 0,
  609. feDiffuseLighting: 0,
  610. feDisplacementMap: 0,
  611. feDistantLight: 0,
  612. feFlood: 0,
  613. feFuncA: 0,
  614. feFuncB: 0,
  615. feFuncG: 0,
  616. feFuncR: 0,
  617. feGaussianBlur: 0,
  618. feImage: 0,
  619. feMerge: 0,
  620. feMergeNode: 0,
  621. feMorphology: 0,
  622. feOffset: 0,
  623. fePointLight: 0,
  624. feSpecularLighting: 0,
  625. feSpotLight: 0,
  626. feTile: 0,
  627. feTurbulence: 0,
  628. filter: 0,
  629. foreignObject: 0,
  630. g: 0,
  631. image: 0,
  632. line: 0,
  633. linearGradient: 0,
  634. marker: 0,
  635. mask: 0,
  636. metadata: 0,
  637. path: 0,
  638. pattern: 0,
  639. polygon: 0,
  640. polyline: 0,
  641. radialGradient: 0,
  642. rect: 0,
  643. stop: 0,
  644. svg: 0,
  645. switch: 0,
  646. symbol: 0,
  647. text: 0,
  648. textPath: 0,
  649. tspan: 0,
  650. use: 0,
  651. view: 0,
  652. };
  653. function createElement(tag, attr, ...children) {
  654. if (isString(attr) || Array.isArray(attr)) {
  655. children.unshift(attr);
  656. attr = {};
  657. }
  658.  
  659. attr = attr || {};
  660.  
  661. if (!attr.namespaceURI && svg[tag] === 0) {
  662. attr = { ...attr, namespaceURI: SVGNamespace };
  663. }
  664.  
  665. if (attr.children != null && !children.length) {
  666. ({ children, ...attr } = attr);
  667. }
  668.  
  669. let node;
  670.  
  671. if (isString(tag)) {
  672. node = attr.namespaceURI
  673. ? document.createElementNS(attr.namespaceURI, tag)
  674. : document.createElement(tag);
  675. attributes(attr, node);
  676. appendChild(children, node);
  677. } else if (isFunction(tag)) {
  678. if (isObject(tag.defaultProps)) {
  679. attr = { ...tag.defaultProps, ...attr };
  680. }
  681.  
  682. node = tag({ ...attr, children });
  683. }
  684.  
  685. if (isRef(attr.ref)) {
  686. attr.ref.current = node;
  687. } else if (isFunction(attr.ref)) {
  688. attr.ref(node);
  689. }
  690.  
  691. return node
  692. }
  693.  
  694. function appendChild(child, node) {
  695. if (isArrayLike(child)) {
  696. appendChildren(child, node);
  697. } else if (isString(child) || isNumber(child)) {
  698. appendChildToNode(document.createTextNode(child), node);
  699. } else if (child === null) {
  700. appendChildToNode(document.createComment(""), node);
  701. } else if (isElement(child)) {
  702. appendChildToNode(child, node);
  703. }
  704. }
  705.  
  706. function appendChildren(children, node) {
  707. for (const child of children) {
  708. appendChild(child, node);
  709. }
  710.  
  711. return node
  712. }
  713.  
  714. function appendChildToNode(child, node) {
  715. if (node instanceof window.HTMLTemplateElement) {
  716. node.content.appendChild(child);
  717. } else {
  718. node.appendChild(child);
  719. }
  720. }
  721.  
  722. function normalizeAttribute(s) {
  723. return s.replace(/[A-Z\d]/g, (match) => ":" + match.toLowerCase())
  724. }
  725.  
  726. function attribute(key, value, node) {
  727. switch (key) {
  728. case "xlinkActuate":
  729. case "xlinkArcrole":
  730. case "xlinkHref":
  731. case "xlinkRole":
  732. case "xlinkShow":
  733. case "xlinkTitle":
  734. case "xlinkType":
  735. attrNS(node, XLinkNamespace, normalizeAttribute(key), value);
  736. return
  737.  
  738. case "xmlnsXlink":
  739. attr(node, normalizeAttribute(key), value);
  740. return
  741.  
  742. case "xmlBase":
  743. case "xmlLang":
  744. case "xmlSpace":
  745. attrNS(node, XMLNamespace, normalizeAttribute(key), value);
  746. return
  747. }
  748.  
  749. switch (key) {
  750. case "htmlFor":
  751. attr(node, "for", value);
  752. return
  753.  
  754. case "dataset":
  755. forEach(value, (dataValue, dataKey) => {
  756. if (dataValue != null) {
  757. node.dataset[dataKey] = dataValue;
  758. }
  759. });
  760. return
  761.  
  762. case "innerHTML":
  763. case "innerText":
  764. case "textContent":
  765. node[key] = value;
  766. return
  767.  
  768. case "spellCheck":
  769. node.spellcheck = value;
  770. return
  771.  
  772. case "class":
  773. case "className":
  774. if (isFunction(value)) {
  775. value(node);
  776. } else {
  777. attr(node, "class", className(value));
  778. }
  779.  
  780. return
  781.  
  782. case "ref":
  783. case "namespaceURI":
  784. return
  785.  
  786. case "style":
  787. if (isObject(value)) {
  788. forEach(value, (val, key) => {
  789. if (isNumber(val) && isUnitlessNumber[key] !== 0) {
  790. node.style[key] = val + "px";
  791. } else {
  792. node.style[key] = val;
  793. }
  794. });
  795. return
  796. }
  797. }
  798.  
  799. if (isFunction(value)) {
  800. if (key[0] === "o" && key[1] === "n") {
  801. const attribute = key.toLowerCase();
  802.  
  803. if (node[attribute] == null) {
  804. node[attribute] = value;
  805. } else {
  806. node.addEventListener(key, value);
  807. }
  808. }
  809. } else if (value === true) {
  810. attr(node, key, "");
  811. } else if (value !== false && value != null) {
  812. attr(node, key, value);
  813. }
  814. }
  815.  
  816. function attr(node, key, value) {
  817. node.setAttribute(key, value);
  818. }
  819.  
  820. function attrNS(node, namespace, key, value) {
  821. node.setAttributeNS(namespace, key, value);
  822. }
  823.  
  824. function attributes(attr, node) {
  825. for (const key of keys(attr)) {
  826. attribute(key, attr[key], node);
  827. }
  828.  
  829. return node
  830. }
  831.  
  832. function messageGetter({
  833. getMessage,
  834. DEFAULT
  835. }) {
  836. return (key, params) => {
  837. const message = getMessage(key, params);
  838. if (message) return message;
  839. const defaultMessage = DEFAULT[key];
  840. if (!defaultMessage) return "";
  841. if (!params) return defaultMessage;
  842.  
  843. if (!Array.isArray(params)) {
  844. params = [params];
  845. }
  846.  
  847. return defaultMessage.replace(/\$(\d+)/g, (m, n) => params[n - 1]);
  848. };
  849. }
  850.  
  851. function fallback(getMessage) {
  852. return messageGetter({
  853. getMessage,
  854. DEFAULT: {
  855. currentScopeLabel: "Current scope",
  856. addScopeLabel: "Add new scope",
  857. deleteScopeLabel: "Delete current scope",
  858. learnMoreButton: "Learn more",
  859. importButton: "Import",
  860. exportButton: "Export",
  861. addScopePrompt: "Add new scope",
  862. deleteScopeConfirm: "Delete scope $1?",
  863. importPrompt: "Paste settings",
  864. exportPrompt: "Copy settings"
  865. }
  866. });
  867. }
  868.  
  869. const VALID_CONTROL = new Set(["import", "export", "scope-list", "add-scope", "delete-scope"]);
  870.  
  871. class DefaultMap extends Map {
  872. constructor(getDefault) {
  873. super();
  874. this.getDefault = getDefault;
  875. }
  876.  
  877. get(key) {
  878. let item = super.get(key);
  879.  
  880. if (!item) {
  881. item = this.getDefault();
  882. super.set(key, item);
  883. }
  884.  
  885. return item;
  886. }
  887.  
  888. }
  889.  
  890. function bindInputs(pref, inputs) {
  891. const bounds = [];
  892.  
  893. const onPrefChange = change => {
  894. for (const key in change) {
  895. if (!inputs.has(key)) {
  896. continue;
  897. }
  898.  
  899. for (const input of inputs.get(key)) {
  900. updateInput(input, change[key]);
  901. }
  902. }
  903. };
  904.  
  905. pref.on("change", onPrefChange);
  906. bounds.push(() => pref.off("change", onPrefChange));
  907.  
  908. for (const [key, list] of inputs.entries()) {
  909. for (const input of list) {
  910. const evt = input.hasAttribute("realtime") ? "input" : "change";
  911.  
  912. const onChange = () => updatePref(key, input);
  913.  
  914. input.addEventListener(evt, onChange);
  915. bounds.push(() => input.removeEventListener(evt, onChange));
  916. }
  917. }
  918.  
  919. onPrefChange(pref.getAll());
  920. return () => {
  921. for (const unbind of bounds) {
  922. unbind();
  923. }
  924. };
  925.  
  926. function updatePref(key, input) {
  927. if (!input.checkValidity()) {
  928. return;
  929. }
  930.  
  931. if (input.type === "checkbox") {
  932. pref.set(key, input.checked);
  933. return;
  934. }
  935.  
  936. if (input.type === "radio") {
  937. if (input.checked) {
  938. pref.set(key, input.value);
  939. }
  940.  
  941. return;
  942. }
  943.  
  944. if (input.nodeName === "SELECT" && input.multiple) {
  945. pref.set(key, [...input.options].filter(o => o.selected).map(o => o.value));
  946. return;
  947. }
  948.  
  949. if (input.type === "number" || input.type === "range") {
  950. pref.set(key, Number(input.value));
  951. return;
  952. }
  953.  
  954. pref.set(key, input.value);
  955. }
  956.  
  957. function updateInput(input, value) {
  958. if (input.nodeName === "INPUT" && input.type === "radio") {
  959. input.checked = input.value === value;
  960. return;
  961. }
  962.  
  963. if (input.type === "checkbox") {
  964. input.checked = value;
  965. return;
  966. }
  967.  
  968. if (input.nodeName === "SELECT" && input.multiple) {
  969. const checked = new Set(value);
  970.  
  971. for (const option of input.options) {
  972. option.selected = checked.has(option.value);
  973. }
  974.  
  975. return;
  976. }
  977.  
  978. input.value = value;
  979. }
  980. }
  981.  
  982. function bindFields(pref, fields) {
  983. const onPrefChange = change => {
  984. for (const key in change) {
  985. if (!fields.has(key)) {
  986. continue;
  987. }
  988.  
  989. for (const field of fields.get(key)) {
  990. field.disabled = field.dataset.bindToValue ? field.dataset.bindToValue !== change[key] : !change[key];
  991. }
  992. }
  993. };
  994.  
  995. pref.on("change", onPrefChange);
  996. onPrefChange(pref.getAll());
  997. return () => pref.off("change", onPrefChange);
  998. }
  999.  
  1000. function bindControls({
  1001. pref,
  1002. controls,
  1003. alert: _alert = alert,
  1004. confirm: _confirm = confirm,
  1005. prompt: _prompt = prompt,
  1006. getMessage = () => {},
  1007. getNewScope = () => ""
  1008. }) {
  1009. const CONTROL_METHODS = {
  1010. "import": ["click", doImport],
  1011. "export": ["click", doExport],
  1012. "scope-list": ["change", updateCurrentScope],
  1013. "add-scope": ["click", addScope],
  1014. "delete-scope": ["click", deleteScope]
  1015. };
  1016.  
  1017. for (const type in CONTROL_METHODS) {
  1018. for (const el of controls.get(type)) {
  1019. el.addEventListener(CONTROL_METHODS[type][0], CONTROL_METHODS[type][1]);
  1020. }
  1021. }
  1022.  
  1023. pref.on("scopeChange", updateCurrentScopeEl);
  1024. pref.on("scopeListChange", updateScopeList);
  1025. updateScopeList();
  1026. updateCurrentScopeEl();
  1027.  
  1028. const _ = fallback(getMessage);
  1029.  
  1030. return unbind;
  1031.  
  1032. function unbind() {
  1033. pref.off("scopeChange", updateCurrentScopeEl);
  1034. pref.off("scopeListChange", updateScopeList);
  1035.  
  1036. for (const type in CONTROL_METHODS) {
  1037. for (const el of controls.get(type)) {
  1038. el.removeEventListener(CONTROL_METHODS[type][0], CONTROL_METHODS[type][1]);
  1039. }
  1040. }
  1041. }
  1042.  
  1043. async function doImport() {
  1044. try {
  1045. const input = await _prompt(_("importPrompt"));
  1046.  
  1047. if (input == null) {
  1048. return;
  1049. }
  1050.  
  1051. const settings = JSON.parse(input);
  1052. return pref.import(settings);
  1053. } catch (err) {
  1054. await _alert(err.message);
  1055. }
  1056. }
  1057.  
  1058. async function doExport() {
  1059. try {
  1060. const settings = await pref.export();
  1061. await _prompt(_("exportPrompt"), JSON.stringify(settings));
  1062. } catch (err) {
  1063. await _alert(err.message);
  1064. }
  1065. }
  1066.  
  1067. function updateCurrentScope(e) {
  1068. pref.setCurrentScope(e.target.value);
  1069. }
  1070.  
  1071. async function addScope() {
  1072. try {
  1073. let scopeName = await _prompt(_("addScopePrompt"), getNewScope());
  1074.  
  1075. if (scopeName == null) {
  1076. return;
  1077. }
  1078.  
  1079. scopeName = scopeName.trim();
  1080.  
  1081. if (!scopeName) {
  1082. throw new Error("the value is empty");
  1083. }
  1084.  
  1085. await pref.addScope(scopeName);
  1086. pref.setCurrentScope(scopeName);
  1087. } catch (err) {
  1088. await _alert(err.message);
  1089. }
  1090. }
  1091.  
  1092. async function deleteScope() {
  1093. try {
  1094. const scopeName = pref.getCurrentScope();
  1095. const result = await _confirm(_("deleteScopeConfirm", scopeName));
  1096.  
  1097. if (result) {
  1098. return pref.deleteScope(scopeName);
  1099. }
  1100. } catch (err) {
  1101. await _alert(err.message);
  1102. }
  1103. }
  1104.  
  1105. function updateCurrentScopeEl() {
  1106. const scopeName = pref.getCurrentScope();
  1107.  
  1108. for (const el of controls.get("scope-list")) {
  1109. el.value = scopeName;
  1110. }
  1111. }
  1112.  
  1113. function updateScopeList() {
  1114. const scopeList = pref.getScopeList();
  1115.  
  1116. for (const el of controls.get("scope-list")) {
  1117. el.innerHTML = "";
  1118. el.append(...scopeList.map(scope => {
  1119. const option = document.createElement("option");
  1120. option.value = scope;
  1121. option.textContent = scope;
  1122. return option;
  1123. }));
  1124. }
  1125. }
  1126. }
  1127.  
  1128. function createBinding({
  1129. pref,
  1130. root,
  1131. elements = root.querySelectorAll("input, textarea, select, fieldset, button"),
  1132. keyPrefix = "pref-",
  1133. controlPrefix = "webext-pref-",
  1134. alert,
  1135. confirm,
  1136. prompt,
  1137. getMessage,
  1138. getNewScope
  1139. }) {
  1140. const inputs = new DefaultMap(() => []);
  1141. const fields = new DefaultMap(() => []);
  1142. const controls = new DefaultMap(() => []);
  1143.  
  1144. for (const element of elements) {
  1145. const id = element.id && stripPrefix(element.id, keyPrefix);
  1146.  
  1147. if (id && pref.has(id)) {
  1148. inputs.get(id).push(element);
  1149. continue;
  1150. }
  1151.  
  1152. if (element.nodeName === "INPUT" && element.type === "radio") {
  1153. const name = element.name && stripPrefix(element.name, keyPrefix);
  1154.  
  1155. if (name && pref.has(name)) {
  1156. inputs.get(name).push(element);
  1157. continue;
  1158. }
  1159. }
  1160.  
  1161. if (element.nodeName === "FIELDSET" && element.dataset.bindTo) {
  1162. fields.get(element.dataset.bindTo).push(element);
  1163. continue;
  1164. }
  1165.  
  1166. const controlType = findControlType(element.classList);
  1167.  
  1168. if (controlType) {
  1169. controls.get(controlType).push(element);
  1170. }
  1171. }
  1172.  
  1173. const bounds = [bindInputs(pref, inputs), bindFields(pref, fields), bindControls({
  1174. pref,
  1175. controls,
  1176. alert,
  1177. confirm,
  1178. prompt,
  1179. getMessage,
  1180. getNewScope
  1181. })];
  1182. return () => {
  1183. for (const unbind of bounds) {
  1184. unbind();
  1185. }
  1186. };
  1187.  
  1188. function stripPrefix(id, prefix) {
  1189. if (!prefix) {
  1190. return id;
  1191. }
  1192.  
  1193. return id.startsWith(prefix) ? id.slice(prefix.length) : "";
  1194. }
  1195.  
  1196. function findControlType(list) {
  1197. for (const name of list) {
  1198. const controlType = stripPrefix(name, controlPrefix);
  1199.  
  1200. if (VALID_CONTROL.has(controlType)) {
  1201. return controlType;
  1202. }
  1203. }
  1204. }
  1205. }
  1206.  
  1207. function createUI({
  1208. body,
  1209. getMessage = () => {},
  1210. toolbar = true,
  1211. navbar = true,
  1212. keyPrefix = "pref-",
  1213. controlPrefix = "webext-pref-"
  1214. }) {
  1215. const root = document.createDocumentFragment();
  1216.  
  1217. const _ = fallback(getMessage);
  1218.  
  1219. if (toolbar) {
  1220. root.append(createToolbar());
  1221. }
  1222.  
  1223. if (navbar) {
  1224. root.append(createNavbar());
  1225. }
  1226.  
  1227. root.append( /*#__PURE__*/createElement("div", {
  1228. class: controlPrefix + "body"
  1229. }, body.map(item => {
  1230. if (!item.hLevel) {
  1231. item.hLevel = 3;
  1232. }
  1233.  
  1234. return createItem(item);
  1235. })));
  1236. return root;
  1237.  
  1238. function createToolbar() {
  1239. return /*#__PURE__*/createElement("div", {
  1240. class: controlPrefix + "toolbar"
  1241. }, /*#__PURE__*/createElement("button", {
  1242. type: "button",
  1243. class: [controlPrefix + "import", "browser-style"]
  1244. }, _("importButton")), /*#__PURE__*/createElement("button", {
  1245. type: "button",
  1246. class: [controlPrefix + "export", "browser-style"]
  1247. }, _("exportButton")));
  1248. }
  1249.  
  1250. function createNavbar() {
  1251. return /*#__PURE__*/createElement("div", {
  1252. class: controlPrefix + "nav"
  1253. }, /*#__PURE__*/createElement("select", {
  1254. class: [controlPrefix + "scope-list", "browser-style"],
  1255. title: _("currentScopeLabel")
  1256. }), /*#__PURE__*/createElement("button", {
  1257. type: "button",
  1258. class: [controlPrefix + "delete-scope", "browser-style"],
  1259. title: _("deleteScopeLabel")
  1260. }, "\xD7"), /*#__PURE__*/createElement("button", {
  1261. type: "button",
  1262. class: [controlPrefix + "add-scope", "browser-style"],
  1263. title: _("addScopeLabel")
  1264. }, "+"));
  1265. }
  1266.  
  1267. function createItem(p) {
  1268. if (p.type === "section") {
  1269. return createSection(p);
  1270. }
  1271.  
  1272. if (p.type === "checkbox") {
  1273. return createCheckbox(p);
  1274. }
  1275.  
  1276. if (p.type === "radiogroup") {
  1277. return createRadioGroup(p);
  1278. }
  1279.  
  1280. return createInput(p);
  1281. }
  1282.  
  1283. function createInput(p) {
  1284. const key = keyPrefix + p.key;
  1285. let input;
  1286. const onChange = p.validate ? e => {
  1287. try {
  1288. p.validate(e.target.value);
  1289. e.target.setCustomValidity("");
  1290. } catch (err) {
  1291. e.target.setCustomValidity(err.message || String(err));
  1292. }
  1293. } : null;
  1294.  
  1295. if (p.type === "select") {
  1296. input = /*#__PURE__*/createElement("select", {
  1297. multiple: p.multiple,
  1298. class: "browser-style",
  1299. id: key,
  1300. onChange: onChange
  1301. }, Object.entries(p.options).map(([value, label]) => /*#__PURE__*/createElement("option", {
  1302. value: value
  1303. }, label)));
  1304. } else if (p.type === "textarea") {
  1305. input = /*#__PURE__*/createElement("textarea", {
  1306. rows: "8",
  1307. class: "browser-style",
  1308. id: key,
  1309. onChange: onChange
  1310. });
  1311. } else {
  1312. input = /*#__PURE__*/createElement("input", {
  1313. type: p.type,
  1314. id: key,
  1315. onChange: onChange
  1316. });
  1317. }
  1318.  
  1319. return /*#__PURE__*/createElement("div", {
  1320. class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
  1321. }, /*#__PURE__*/createElement("label", {
  1322. htmlFor: key
  1323. }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
  1324. url: p.learnMore
  1325. }), input, p.help && /*#__PURE__*/createElement(Help, {
  1326. content: p.help
  1327. }));
  1328. }
  1329.  
  1330. function createRadioGroup(p) {
  1331. return /*#__PURE__*/createElement("div", {
  1332. class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
  1333. }, /*#__PURE__*/createElement("div", {
  1334. class: controlPrefix + "radio-title"
  1335. }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
  1336. url: p.learnMore
  1337. }), p.help && /*#__PURE__*/createElement(Help, {
  1338. content: p.help
  1339. }), p.children.map(c => {
  1340. c.parentKey = p.key;
  1341. return createCheckbox(inheritProp(p, c));
  1342. }));
  1343. }
  1344.  
  1345. function Help({
  1346. content
  1347. }) {
  1348. return /*#__PURE__*/createElement("p", {
  1349. class: controlPrefix + "help"
  1350. }, content);
  1351. }
  1352.  
  1353. function LearnMore({
  1354. url
  1355. }) {
  1356. return /*#__PURE__*/createElement("a", {
  1357. href: url,
  1358. class: controlPrefix + "learn-more",
  1359. target: "_blank",
  1360. rel: "noopener noreferrer"
  1361. }, _("learnMoreButton"));
  1362. }
  1363.  
  1364. function createCheckbox(p) {
  1365. const id = p.parentKey ? `${keyPrefix}${p.parentKey}-${p.value}` : keyPrefix + p.key;
  1366. return /*#__PURE__*/createElement("div", {
  1367. class: [`${controlPrefix}${p.type}`, "browser-style", p.className]
  1368. }, /*#__PURE__*/createElement("input", {
  1369. type: p.type,
  1370. id: id,
  1371. name: p.parentKey ? keyPrefix + p.parentKey : null,
  1372. value: p.value
  1373. }), /*#__PURE__*/createElement("label", {
  1374. htmlFor: id
  1375. }, p.label), p.learnMore && /*#__PURE__*/createElement(LearnMore, {
  1376. url: p.learnMore
  1377. }), p.help && /*#__PURE__*/createElement(Help, {
  1378. content: p.help
  1379. }), p.children && /*#__PURE__*/createElement("fieldset", {
  1380. class: controlPrefix + "checkbox-children",
  1381. dataset: {
  1382. bindTo: p.parentKey || p.key,
  1383. bindToValue: p.value
  1384. }
  1385. }, p.children.map(c => createItem(inheritProp(p, c)))));
  1386. }
  1387.  
  1388. function createSection(p) {
  1389. const Header = `h${p.hLevel}`;
  1390. p.hLevel++;
  1391. return (
  1392. /*#__PURE__*/
  1393. // FIXME: do we need browser-style for section?
  1394. createElement("div", {
  1395. class: [controlPrefix + p.type, p.className]
  1396. }, /*#__PURE__*/createElement(Header, {
  1397. class: controlPrefix + "header"
  1398. }, p.label), p.help && /*#__PURE__*/createElement(Help, {
  1399. content: p.help
  1400. }), p.children && p.children.map(c => createItem(inheritProp(p, c))))
  1401. );
  1402. }
  1403.  
  1404. function inheritProp(parent, child) {
  1405. child.hLevel = parent.hLevel;
  1406. return child;
  1407. }
  1408. }
  1409.  
  1410. /* eslint-env greasemonkey */
  1411.  
  1412. function createGMStorage() {
  1413. const setValue = typeof GM_setValue === "function" ?
  1414. promisify(GM_setValue) : GM.setValue.bind(GM);
  1415. const getValue = typeof GM_getValue === "function" ?
  1416. promisify(GM_getValue) : GM.getValue.bind(GM);
  1417. const deleteValue = typeof GM_deleteValue === "function" ?
  1418. promisify(GM_deleteValue) : GM.deleteValue.bind(GM);
  1419. const events = new EventLite;
  1420. if (typeof GM_addValueChangeListener === "function") {
  1421. GM_addValueChangeListener("webext-pref-message", (name, oldValue, newValue) => {
  1422. const changes = JSON.parse(newValue);
  1423. for (const key of Object.keys(changes)) {
  1424. if (typeof changes[key] === "object" && changes[key].$undefined) {
  1425. changes[key] = undefined;
  1426. }
  1427. }
  1428. events.emit("change", changes);
  1429. });
  1430. }
  1431. return Object.assign(events, {getMany, setMany, deleteMany});
  1432. function getMany(keys) {
  1433. return Promise.all(keys.map(k =>
  1434. getValue(`webext-pref/${k}`)
  1435. .then(value => [k, typeof value === "string" ? JSON.parse(value) : value])
  1436. ))
  1437. .then(entries => {
  1438. const output = {};
  1439. for (const [key, value] of entries) {
  1440. output[key] = value;
  1441. }
  1442. return output;
  1443. });
  1444. }
  1445. function setMany(changes) {
  1446. return Promise.all(Object.entries(changes).map(([key, value]) =>
  1447. setValue(`webext-pref/${key}`, JSON.stringify(value))
  1448. ))
  1449. .then(() => {
  1450. if (typeof GM_addValueChangeListener === "function") {
  1451. return setValue("webext-pref-message", JSON.stringify(changes));
  1452. }
  1453. events.emit("change", changes);
  1454. });
  1455. }
  1456. function deleteMany(keys) {
  1457. return Promise.all(keys.map(k => deleteValue(`webext-pref/${k}`)))
  1458. .then(() => {
  1459. if (typeof GM_addValueChangeListener === "function") {
  1460. const changes = {};
  1461. for (const key of keys) {
  1462. changes[key] = {
  1463. $undefined: true
  1464. };
  1465. }
  1466. return setValue("webext-pref-message", JSON.stringify(changes));
  1467. }
  1468. const changes = {};
  1469. for (const key of keys) {
  1470. changes[key] = undefined;
  1471. }
  1472. events.emit("change", changes);
  1473. });
  1474. }
  1475. function promisify(fn) {
  1476. return (...args) => {
  1477. try {
  1478. return Promise.resolve(fn(...args));
  1479. } catch (err) {
  1480. return Promise.reject(err);
  1481. }
  1482. };
  1483. }
  1484. }
  1485.  
  1486. /* eslint-env greasemonkey */
  1487.  
  1488. function GM_webextPref({
  1489. default: default_,
  1490. separator,
  1491. css = "",
  1492. ...options
  1493. }) {
  1494. const pref = createPref(default_, separator);
  1495. const initializing = pref.connect(createGMStorage());
  1496. let isOpen = false;
  1497. const registerMenu =
  1498. typeof GM_registerMenuCommand === "function" ? GM_registerMenuCommand :
  1499. typeof GM !== "undefined" && GM && GM.registerMenuCommand ? GM.registerMenuCommand.bind(GM) :
  1500. undefined;
  1501. if (registerMenu) {
  1502. registerMenu(`${getTitle()} - Configure`, openDialog);
  1503. }
  1504. return Object.assign(pref, {
  1505. ready: () => initializing,
  1506. openDialog
  1507. });
  1508. function openDialog() {
  1509. if (isOpen) {
  1510. return;
  1511. }
  1512. isOpen = true;
  1513. let destroyView;
  1514. const modal = document.createElement("div");
  1515. modal.className = "webext-pref-modal";
  1516. modal.onclick = () => {
  1517. modal.classList.remove("webext-pref-modal-open");
  1518. modal.addEventListener("transitionend", () => {
  1519. if (destroyView) {
  1520. destroyView();
  1521. }
  1522. modal.remove();
  1523. isOpen = false;
  1524. });
  1525. };
  1526. const style = document.createElement("style");
  1527. 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}" + `
  1528. body {
  1529. padding-right: ${window.innerWidth - document.documentElement.offsetWidth}px;
  1530. }
  1531. `;
  1532. const iframe = document.createElement("iframe");
  1533. iframe.className = "webext-pref-iframe";
  1534. iframe.srcdoc = `
  1535. <html>
  1536. <head>
  1537. <style class="dialog-style"></style>
  1538. </head>
  1539. <body>
  1540. <div class="dialog-body"></div>
  1541. </body>
  1542. </html>
  1543. `;
  1544. const wrap = document.createElement("div");
  1545. wrap.className = "webext-pref-iframe-wrap";
  1546. wrap.append(iframe);
  1547. modal.append(style, wrap);
  1548. document.body.appendChild(modal);
  1549. iframe.onload = () => {
  1550. iframe.onload = null;
  1551. 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;
  1552. const root = iframe.contentDocument.querySelector(".dialog-body");
  1553. root.append(createUI(options));
  1554. destroyView = createBinding({
  1555. pref,
  1556. root,
  1557. ...options
  1558. });
  1559. const title = document.createElement("h2");
  1560. title.className = "dialog-title";
  1561. title.textContent = getTitle();
  1562. iframe.contentDocument.querySelector(".webext-pref-toolbar").prepend(title);
  1563. if (iframe.contentDocument.body.offsetWidth > modal.offsetWidth) {
  1564. iframe.contentDocument.body.classList.add("responsive");
  1565. }
  1566. // calc iframe size
  1567. iframe.style = `
  1568. width: ${iframe.contentDocument.body.offsetWidth}px;
  1569. height: ${iframe.contentDocument.body.scrollHeight}px;
  1570. `;
  1571. modal.classList.add("webext-pref-modal-open");
  1572. };
  1573. }
  1574. function getTitle() {
  1575. return typeof GM_info === "object" ?
  1576. GM_info.script.name : GM.info.script.name;
  1577. }
  1578. }
  1579.  
  1580. function prefDefault() {
  1581. return {
  1582. fuzzyIp: true,
  1583. embedImage: true,
  1584. embedImageExcludeElement: ".hljs, .highlight, .brush\\:",
  1585. ignoreMustache: false,
  1586. unicode: false,
  1587. mail: true,
  1588. newTab: false,
  1589. standalone: false,
  1590. boundaryLeft: "{[(\"'",
  1591. boundaryRight: "'\")]},.;?!",
  1592. excludeElement: ".highlight, .editbox, .brush\\:, .bdsug, .spreadsheetinfo",
  1593. includeElement: "",
  1594. timeout: 10000,
  1595. triggerByPageLoad: false,
  1596. triggerByNewNode: false,
  1597. triggerByHover: true,
  1598. triggerByClick: !supportHover(),
  1599. maxRunTime: 100,
  1600. customRules: "",
  1601. };
  1602. }
  1603.  
  1604. function supportHover() {
  1605. return window.matchMedia("(hover)").matches;
  1606. }
  1607.  
  1608. var prefBody = getMessage => {
  1609. return [
  1610. {
  1611. type: "section",
  1612. label: getMessage("optionsUrlMatcherLabel"),
  1613. children: [
  1614. {
  1615. key: "fuzzyIp",
  1616. type: "checkbox",
  1617. label: getMessage("optionsFuzzyIpLabel")
  1618. },
  1619. {
  1620. key: "ignoreMustache",
  1621. type: "checkbox",
  1622. label: getMessage("optionsIgnoreMustacheLabel")
  1623. },
  1624. {
  1625. key: "unicode",
  1626. type: "checkbox",
  1627. label: getMessage("optionsUnicodeLabel")
  1628. },
  1629. {
  1630. key: "mail",
  1631. type: "checkbox",
  1632. label: getMessage("optionsMailLabel")
  1633. },
  1634. {
  1635. key: "standalone",
  1636. type: "checkbox",
  1637. label: getMessage("optionsStandaloneLabel"),
  1638. children: [
  1639. {
  1640. key: "boundaryLeft",
  1641. type: "text",
  1642. label: getMessage("optionsBoundaryLeftLabel")
  1643. },
  1644. {
  1645. key: "boundaryRight",
  1646. type: "text",
  1647. label: getMessage("optionsBoundaryRightLabel")
  1648. }
  1649. ]
  1650. },
  1651. {
  1652. key: "customRules",
  1653. type: "textarea",
  1654. label: getMessage("optionsCustomRulesLabel"),
  1655. learnMore: "https://github.com/eight04/linkify-plus-plus?tab=readme-ov-file#custom-rules"
  1656. },
  1657.  
  1658. ]
  1659. },
  1660. {
  1661. type: "section",
  1662. label: getMessage("optionsLinkifierLabel"),
  1663. children: [
  1664. {
  1665. key: "triggerByPageLoad",
  1666. type: "checkbox",
  1667. label: getMessage("optionsTriggerByPageLoadLabel")
  1668. },
  1669. {
  1670. key: "triggerByNewNode",
  1671. type: "checkbox",
  1672. label: getMessage("optionsTriggerByNewNodeLabel")
  1673. },
  1674. {
  1675. key: "triggerByHover",
  1676. type: "checkbox",
  1677. label: getMessage("optionsTriggerByHoverLabel")
  1678. },
  1679. {
  1680. key: "triggerByClick",
  1681. type: "checkbox",
  1682. label: getMessage("optionsTriggerByClickLabel")
  1683. },
  1684. {
  1685. key: "embedImage",
  1686. type: "checkbox",
  1687. label: getMessage("optionsEmbedImageLabel"),
  1688. children: [
  1689. {
  1690. key: "embedImageExcludeElement",
  1691. type: "textarea",
  1692. label: getMessage("optionsEmbedImageExcludeElementLabel"),
  1693. validate: validateSelector
  1694. }
  1695. ]
  1696. },
  1697. {
  1698. key: "newTab",
  1699. type: "checkbox",
  1700. label: getMessage("optionsNewTabLabel")
  1701. },
  1702. {
  1703. key: "excludeElement",
  1704. type: "textarea",
  1705. label: getMessage("optionsExcludeElementLabel"),
  1706. validate: validateSelector
  1707. },
  1708. {
  1709. key: "includeElement",
  1710. type: "textarea",
  1711. label: getMessage("optionsIncludeElementLabel"),
  1712. validate: validateSelector
  1713. },
  1714. {
  1715. key: "timeout",
  1716. type: "number",
  1717. label: getMessage("optionsTimeoutLabel"),
  1718. help: getMessage("optionsTimeoutHelp")
  1719. },
  1720. {
  1721. key: "maxRunTime",
  1722. type: "number",
  1723. label: getMessage("optionsMaxRunTimeLabel"),
  1724. help: getMessage("optionsMaxRunTimeHelp")
  1725. },
  1726. ]
  1727. },
  1728. ];
  1729. function validateSelector(value) {
  1730. if (value) {
  1731. document.documentElement.matches(value);
  1732. }
  1733. }
  1734. };
  1735.  
  1736. var maxLength = 24;
  1737. var chars = "セール佛山ಭಾರತ集团在线한국ଭାରତভাৰতর八卦ישראלموقعবংল公司网站移动我爱你москвақзнлйт联通рбгеקוםファッションストアசிங்கபூர商标店城дию家電中文信国國娱乐భారత్ලංකා购物クラウドભારતभारतम्ोसंगठन餐厅络у香港食品飞利浦台湾灣手机الجزئرنیتبيپکسدغظحةڀ澳門닷컴شكგე构健康ไทย招聘фລາວみんなευλ世界書籍ഭാരതംਭਾਰਤ址넷コム游戏ö企业息صط广东இலைநதயாհայ新加坡ف政务";
  1738. var table = {
  1739. aaa: true,
  1740. aarp: true,
  1741. abb: true,
  1742. abbott: true,
  1743. abbvie: true,
  1744. abc: true,
  1745. able: true,
  1746. abogado: true,
  1747. abudhabi: true,
  1748. ac: true,
  1749. academy: true,
  1750. accountant: true,
  1751. accountants: true,
  1752. aco: true,
  1753. actor: true,
  1754. ad: true,
  1755. adult: true,
  1756. ae: true,
  1757. aeg: true,
  1758. aero: true,
  1759. aetna: true,
  1760. af: true,
  1761. afl: true,
  1762. africa: true,
  1763. ag: true,
  1764. agency: true,
  1765. ai: true,
  1766. aig: true,
  1767. airbus: true,
  1768. airforce: true,
  1769. akdn: true,
  1770. al: true,
  1771. allfinanz: true,
  1772. allstate: true,
  1773. ally: true,
  1774. alsace: true,
  1775. alstom: true,
  1776. am: true,
  1777. amazon: true,
  1778. americanexpress: true,
  1779. amex: true,
  1780. amfam: true,
  1781. amica: true,
  1782. amsterdam: true,
  1783. analytics: true,
  1784. android: true,
  1785. anz: true,
  1786. ao: true,
  1787. apartments: true,
  1788. app: true,
  1789. apple: true,
  1790. aq: true,
  1791. aquarelle: true,
  1792. ar: true,
  1793. archi: true,
  1794. army: true,
  1795. arpa: true,
  1796. art: true,
  1797. arte: true,
  1798. as: true,
  1799. asia: true,
  1800. associates: true,
  1801. at: true,
  1802. attorney: true,
  1803. au: true,
  1804. auction: true,
  1805. audi: true,
  1806. audio: true,
  1807. auspost: true,
  1808. auto: true,
  1809. autos: true,
  1810. aw: true,
  1811. aws: true,
  1812. ax: true,
  1813. axa: true,
  1814. az: true,
  1815. azure: true,
  1816. ba: true,
  1817. baby: true,
  1818. band: true,
  1819. bank: true,
  1820. bar: true,
  1821. barcelona: true,
  1822. barclaycard: true,
  1823. barclays: true,
  1824. bargains: true,
  1825. basketball: true,
  1826. bauhaus: true,
  1827. bayern: true,
  1828. bb: true,
  1829. bbc: true,
  1830. bbva: true,
  1831. bcn: true,
  1832. bd: true,
  1833. be: true,
  1834. beauty: true,
  1835. beer: true,
  1836. bentley: true,
  1837. berlin: true,
  1838. best: true,
  1839. bet: true,
  1840. bf: true,
  1841. bg: true,
  1842. bh: true,
  1843. bi: true,
  1844. bible: true,
  1845. bid: true,
  1846. bike: true,
  1847. bing: true,
  1848. bingo: true,
  1849. bio: true,
  1850. biz: true,
  1851. bj: true,
  1852. black: true,
  1853. blackfriday: true,
  1854. blog: true,
  1855. bloomberg: true,
  1856. blue: true,
  1857. bm: true,
  1858. bmw: true,
  1859. bn: true,
  1860. bnpparibas: true,
  1861. bo: true,
  1862. boats: true,
  1863. bond: true,
  1864. boo: true,
  1865. bostik: true,
  1866. boston: true,
  1867. bot: true,
  1868. boutique: true,
  1869. box: true,
  1870. br: true,
  1871. bradesco: true,
  1872. bridgestone: true,
  1873. broadway: true,
  1874. broker: true,
  1875. brother: true,
  1876. brussels: true,
  1877. bs: true,
  1878. bt: true,
  1879. build: true,
  1880. builders: true,
  1881. business: true,
  1882. buzz: true,
  1883. bw: true,
  1884. by: true,
  1885. bz: true,
  1886. bzh: true,
  1887. ca: true,
  1888. cab: true,
  1889. cafe: true,
  1890. cam: true,
  1891. camera: true,
  1892. camp: true,
  1893. canon: true,
  1894. capetown: true,
  1895. capital: true,
  1896. car: true,
  1897. cards: true,
  1898. care: true,
  1899. career: true,
  1900. careers: true,
  1901. cars: true,
  1902. casa: true,
  1903. "case": true,
  1904. cash: true,
  1905. casino: true,
  1906. cat: true,
  1907. catering: true,
  1908. catholic: true,
  1909. cba: true,
  1910. cbn: true,
  1911. cc: true,
  1912. cd: true,
  1913. center: true,
  1914. ceo: true,
  1915. cern: true,
  1916. cf: true,
  1917. cfa: true,
  1918. cfd: true,
  1919. cg: true,
  1920. ch: true,
  1921. chanel: true,
  1922. channel: true,
  1923. charity: true,
  1924. chase: true,
  1925. chat: true,
  1926. cheap: true,
  1927. chintai: true,
  1928. christmas: true,
  1929. church: true,
  1930. ci: true,
  1931. cisco: true,
  1932. citi: true,
  1933. citic: true,
  1934. city: true,
  1935. ck: true,
  1936. cl: true,
  1937. claims: true,
  1938. cleaning: true,
  1939. click: true,
  1940. clinic: true,
  1941. clothing: true,
  1942. cloud: true,
  1943. club: true,
  1944. clubmed: true,
  1945. cm: true,
  1946. cn: true,
  1947. co: true,
  1948. coach: true,
  1949. codes: true,
  1950. coffee: true,
  1951. college: true,
  1952. cologne: true,
  1953. com: true,
  1954. commbank: true,
  1955. community: true,
  1956. company: true,
  1957. compare: true,
  1958. computer: true,
  1959. condos: true,
  1960. construction: true,
  1961. consulting: true,
  1962. contact: true,
  1963. contractors: true,
  1964. cooking: true,
  1965. cool: true,
  1966. coop: true,
  1967. corsica: true,
  1968. country: true,
  1969. coupons: true,
  1970. courses: true,
  1971. cpa: true,
  1972. cr: true,
  1973. credit: true,
  1974. creditcard: true,
  1975. creditunion: true,
  1976. cricket: true,
  1977. crown: true,
  1978. crs: true,
  1979. cruises: true,
  1980. cu: true,
  1981. cuisinella: true,
  1982. cv: true,
  1983. cw: true,
  1984. cx: true,
  1985. cy: true,
  1986. cymru: true,
  1987. cyou: true,
  1988. cz: true,
  1989. dad: true,
  1990. dance: true,
  1991. date: true,
  1992. dating: true,
  1993. day: true,
  1994. de: true,
  1995. deal: true,
  1996. dealer: true,
  1997. deals: true,
  1998. degree: true,
  1999. delivery: true,
  2000. dell: true,
  2001. deloitte: true,
  2002. democrat: true,
  2003. dental: true,
  2004. dentist: true,
  2005. desi: true,
  2006. design: true,
  2007. dev: true,
  2008. dhl: true,
  2009. diamonds: true,
  2010. diet: true,
  2011. digital: true,
  2012. direct: true,
  2013. directory: true,
  2014. discount: true,
  2015. discover: true,
  2016. diy: true,
  2017. dj: true,
  2018. dk: true,
  2019. dm: true,
  2020. "do": true,
  2021. doctor: true,
  2022. dog: true,
  2023. domains: true,
  2024. download: true,
  2025. dubai: true,
  2026. dupont: true,
  2027. durban: true,
  2028. dvag: true,
  2029. dz: true,
  2030. earth: true,
  2031. ec: true,
  2032. eco: true,
  2033. edeka: true,
  2034. edu: true,
  2035. education: true,
  2036. ee: true,
  2037. eg: true,
  2038. email: true,
  2039. emerck: true,
  2040. energy: true,
  2041. engineer: true,
  2042. engineering: true,
  2043. enterprises: true,
  2044. equipment: true,
  2045. er: true,
  2046. ericsson: true,
  2047. erni: true,
  2048. es: true,
  2049. esq: true,
  2050. estate: true,
  2051. et: true,
  2052. eu: true,
  2053. eurovision: true,
  2054. eus: true,
  2055. events: true,
  2056. exchange: true,
  2057. expert: true,
  2058. exposed: true,
  2059. express: true,
  2060. extraspace: true,
  2061. fage: true,
  2062. fail: true,
  2063. fairwinds: true,
  2064. faith: true,
  2065. family: true,
  2066. fan: true,
  2067. fans: true,
  2068. farm: true,
  2069. fashion: true,
  2070. feedback: true,
  2071. ferrero: true,
  2072. fi: true,
  2073. film: true,
  2074. finance: true,
  2075. financial: true,
  2076. firmdale: true,
  2077. fish: true,
  2078. fishing: true,
  2079. fit: true,
  2080. fitness: true,
  2081. fj: true,
  2082. fk: true,
  2083. flickr: true,
  2084. flights: true,
  2085. flir: true,
  2086. florist: true,
  2087. flowers: true,
  2088. fm: true,
  2089. fo: true,
  2090. foo: true,
  2091. food: true,
  2092. football: true,
  2093. ford: true,
  2094. forex: true,
  2095. forsale: true,
  2096. forum: true,
  2097. foundation: true,
  2098. fox: true,
  2099. fr: true,
  2100. fresenius: true,
  2101. frl: true,
  2102. frogans: true,
  2103. fujitsu: true,
  2104. fun: true,
  2105. fund: true,
  2106. furniture: true,
  2107. futbol: true,
  2108. fyi: true,
  2109. ga: true,
  2110. gal: true,
  2111. gallery: true,
  2112. game: true,
  2113. games: true,
  2114. garden: true,
  2115. gay: true,
  2116. gd: true,
  2117. gdn: true,
  2118. ge: true,
  2119. gea: true,
  2120. gent: true,
  2121. genting: true,
  2122. gf: true,
  2123. gg: true,
  2124. gh: true,
  2125. gi: true,
  2126. gift: true,
  2127. gifts: true,
  2128. gives: true,
  2129. giving: true,
  2130. gl: true,
  2131. glass: true,
  2132. gle: true,
  2133. global: true,
  2134. globo: true,
  2135. gm: true,
  2136. gmail: true,
  2137. gmbh: true,
  2138. gmo: true,
  2139. gmx: true,
  2140. gn: true,
  2141. godaddy: true,
  2142. gold: true,
  2143. golf: true,
  2144. goog: true,
  2145. google: true,
  2146. gop: true,
  2147. gov: true,
  2148. gp: true,
  2149. gq: true,
  2150. gr: true,
  2151. grainger: true,
  2152. graphics: true,
  2153. gratis: true,
  2154. green: true,
  2155. gripe: true,
  2156. group: true,
  2157. gs: true,
  2158. gt: true,
  2159. gu: true,
  2160. gucci: true,
  2161. guide: true,
  2162. guitars: true,
  2163. guru: true,
  2164. gw: true,
  2165. gy: true,
  2166. hair: true,
  2167. hamburg: true,
  2168. haus: true,
  2169. health: true,
  2170. healthcare: true,
  2171. help: true,
  2172. helsinki: true,
  2173. here: true,
  2174. hermes: true,
  2175. hiphop: true,
  2176. hisamitsu: true,
  2177. hitachi: true,
  2178. hiv: true,
  2179. hk: true,
  2180. hm: true,
  2181. hn: true,
  2182. hockey: true,
  2183. holdings: true,
  2184. holiday: true,
  2185. homes: true,
  2186. honda: true,
  2187. horse: true,
  2188. hospital: true,
  2189. host: true,
  2190. hosting: true,
  2191. hotmail: true,
  2192. house: true,
  2193. how: true,
  2194. hr: true,
  2195. hsbc: true,
  2196. ht: true,
  2197. hu: true,
  2198. hyatt: true,
  2199. hyundai: true,
  2200. ice: true,
  2201. icu: true,
  2202. id: true,
  2203. ie: true,
  2204. ieee: true,
  2205. ifm: true,
  2206. ikano: true,
  2207. il: true,
  2208. im: true,
  2209. imamat: true,
  2210. immo: true,
  2211. immobilien: true,
  2212. "in": true,
  2213. inc: true,
  2214. industries: true,
  2215. info: true,
  2216. ing: true,
  2217. ink: true,
  2218. institute: true,
  2219. insurance: true,
  2220. insure: true,
  2221. int: true,
  2222. international: true,
  2223. investments: true,
  2224. io: true,
  2225. ipiranga: true,
  2226. iq: true,
  2227. ir: true,
  2228. irish: true,
  2229. is: true,
  2230. ismaili: true,
  2231. ist: true,
  2232. istanbul: true,
  2233. it: true,
  2234. itau: true,
  2235. itv: true,
  2236. jaguar: true,
  2237. java: true,
  2238. jcb: true,
  2239. je: true,
  2240. jetzt: true,
  2241. jewelry: true,
  2242. jio: true,
  2243. jll: true,
  2244. jm: true,
  2245. jmp: true,
  2246. jnj: true,
  2247. jo: true,
  2248. jobs: true,
  2249. joburg: true,
  2250. jp: true,
  2251. jpmorgan: true,
  2252. jprs: true,
  2253. juegos: true,
  2254. kaufen: true,
  2255. ke: true,
  2256. kfh: true,
  2257. kg: true,
  2258. kh: true,
  2259. ki: true,
  2260. kia: true,
  2261. kids: true,
  2262. kim: true,
  2263. kitchen: true,
  2264. kiwi: true,
  2265. km: true,
  2266. kn: true,
  2267. koeln: true,
  2268. komatsu: true,
  2269. kp: true,
  2270. kpmg: true,
  2271. kpn: true,
  2272. kr: true,
  2273. krd: true,
  2274. kred: true,
  2275. kw: true,
  2276. ky: true,
  2277. kyoto: true,
  2278. kz: true,
  2279. la: true,
  2280. lamborghini: true,
  2281. lancaster: true,
  2282. land: true,
  2283. landrover: true,
  2284. lanxess: true,
  2285. lat: true,
  2286. latrobe: true,
  2287. law: true,
  2288. lawyer: true,
  2289. lb: true,
  2290. lc: true,
  2291. lease: true,
  2292. leclerc: true,
  2293. legal: true,
  2294. lexus: true,
  2295. lgbt: true,
  2296. li: true,
  2297. lidl: true,
  2298. life: true,
  2299. lifestyle: true,
  2300. lighting: true,
  2301. lilly: true,
  2302. limited: true,
  2303. limo: true,
  2304. lincoln: true,
  2305. link: true,
  2306. live: true,
  2307. living: true,
  2308. lk: true,
  2309. llc: true,
  2310. loan: true,
  2311. loans: true,
  2312. locker: true,
  2313. locus: true,
  2314. lol: true,
  2315. london: true,
  2316. lotto: true,
  2317. love: true,
  2318. lr: true,
  2319. ls: true,
  2320. lt: true,
  2321. ltd: true,
  2322. ltda: true,
  2323. lu: true,
  2324. lundbeck: true,
  2325. luxe: true,
  2326. luxury: true,
  2327. lv: true,
  2328. ly: true,
  2329. ma: true,
  2330. madrid: true,
  2331. maif: true,
  2332. maison: true,
  2333. makeup: true,
  2334. man: true,
  2335. management: true,
  2336. mango: true,
  2337. market: true,
  2338. marketing: true,
  2339. markets: true,
  2340. marriott: true,
  2341. mattel: true,
  2342. mba: true,
  2343. mc: true,
  2344. md: true,
  2345. me: true,
  2346. med: true,
  2347. media: true,
  2348. meet: true,
  2349. melbourne: true,
  2350. meme: true,
  2351. memorial: true,
  2352. men: true,
  2353. menu: true,
  2354. mg: true,
  2355. mh: true,
  2356. miami: true,
  2357. microsoft: true,
  2358. mil: true,
  2359. mini: true,
  2360. mit: true,
  2361. mk: true,
  2362. ml: true,
  2363. mlb: true,
  2364. mm: true,
  2365. mma: true,
  2366. mn: true,
  2367. mo: true,
  2368. mobi: true,
  2369. moda: true,
  2370. moe: true,
  2371. moi: true,
  2372. mom: true,
  2373. monash: true,
  2374. money: true,
  2375. monster: true,
  2376. mortgage: true,
  2377. moscow: true,
  2378. motorcycles: true,
  2379. mov: true,
  2380. movie: true,
  2381. mp: true,
  2382. mq: true,
  2383. mr: true,
  2384. ms: true,
  2385. mt: true,
  2386. mtn: true,
  2387. mtr: true,
  2388. mu: true,
  2389. museum: true,
  2390. music: true,
  2391. mv: true,
  2392. mw: true,
  2393. mx: true,
  2394. my: true,
  2395. mz: true,
  2396. na: true,
  2397. nab: true,
  2398. nagoya: true,
  2399. name: true,
  2400. navy: true,
  2401. nc: true,
  2402. ne: true,
  2403. nec: true,
  2404. net: true,
  2405. netbank: true,
  2406. network: true,
  2407. neustar: true,
  2408. "new": true,
  2409. news: true,
  2410. next: true,
  2411. nexus: true,
  2412. nf: true,
  2413. ng: true,
  2414. ngo: true,
  2415. nhk: true,
  2416. ni: true,
  2417. nico: true,
  2418. nike: true,
  2419. ninja: true,
  2420. nissan: true,
  2421. nl: true,
  2422. no: true,
  2423. nokia: true,
  2424. now: true,
  2425. nowruz: true,
  2426. np: true,
  2427. nr: true,
  2428. nra: true,
  2429. nrw: true,
  2430. ntt: true,
  2431. nu: true,
  2432. nyc: true,
  2433. nz: true,
  2434. observer: true,
  2435. office: true,
  2436. okinawa: true,
  2437. om: true,
  2438. omega: true,
  2439. one: true,
  2440. ong: true,
  2441. onl: true,
  2442. online: true,
  2443. ooo: true,
  2444. oracle: true,
  2445. orange: true,
  2446. org: true,
  2447. organic: true,
  2448. osaka: true,
  2449. otsuka: true,
  2450. ovh: true,
  2451. pa: true,
  2452. page: true,
  2453. panasonic: true,
  2454. paris: true,
  2455. partners: true,
  2456. parts: true,
  2457. party: true,
  2458. pe: true,
  2459. pet: true,
  2460. pf: true,
  2461. pfizer: true,
  2462. pg: true,
  2463. ph: true,
  2464. pharmacy: true,
  2465. phd: true,
  2466. philips: true,
  2467. photo: true,
  2468. photography: true,
  2469. photos: true,
  2470. physio: true,
  2471. pics: true,
  2472. pictet: true,
  2473. pictures: true,
  2474. ping: true,
  2475. pink: true,
  2476. pioneer: true,
  2477. pizza: true,
  2478. pk: true,
  2479. pl: true,
  2480. place: true,
  2481. play: true,
  2482. plumbing: true,
  2483. plus: true,
  2484. pm: true,
  2485. pn: true,
  2486. pohl: true,
  2487. poker: true,
  2488. politie: true,
  2489. porn: true,
  2490. post: true,
  2491. pr: true,
  2492. praxi: true,
  2493. press: true,
  2494. prime: true,
  2495. pro: true,
  2496. productions: true,
  2497. prof: true,
  2498. promo: true,
  2499. properties: true,
  2500. property: true,
  2501. protection: true,
  2502. pru: true,
  2503. prudential: true,
  2504. ps: true,
  2505. pt: true,
  2506. pub: true,
  2507. pw: true,
  2508. pwc: true,
  2509. py: true,
  2510. qa: true,
  2511. qpon: true,
  2512. quebec: true,
  2513. quest: true,
  2514. racing: true,
  2515. radio: true,
  2516. re: true,
  2517. realestate: true,
  2518. realtor: true,
  2519. realty: true,
  2520. recipes: true,
  2521. red: true,
  2522. redstone: true,
  2523. rehab: true,
  2524. reise: true,
  2525. reisen: true,
  2526. reit: true,
  2527. ren: true,
  2528. rent: true,
  2529. rentals: true,
  2530. repair: true,
  2531. report: true,
  2532. republican: true,
  2533. rest: true,
  2534. restaurant: true,
  2535. review: true,
  2536. reviews: true,
  2537. rexroth: true,
  2538. rich: true,
  2539. ricoh: true,
  2540. rio: true,
  2541. rip: true,
  2542. ro: true,
  2543. rocks: true,
  2544. rodeo: true,
  2545. rogers: true,
  2546. rs: true,
  2547. rsvp: true,
  2548. ru: true,
  2549. rugby: true,
  2550. ruhr: true,
  2551. run: true,
  2552. rw: true,
  2553. ryukyu: true,
  2554. sa: true,
  2555. saarland: true,
  2556. sale: true,
  2557. salon: true,
  2558. samsung: true,
  2559. sandvik: true,
  2560. sandvikcoromant: true,
  2561. sanofi: true,
  2562. sap: true,
  2563. sarl: true,
  2564. saxo: true,
  2565. sb: true,
  2566. sbi: true,
  2567. sbs: true,
  2568. sc: true,
  2569. scb: true,
  2570. schaeffler: true,
  2571. schmidt: true,
  2572. school: true,
  2573. schule: true,
  2574. schwarz: true,
  2575. science: true,
  2576. scot: true,
  2577. sd: true,
  2578. se: true,
  2579. seat: true,
  2580. security: true,
  2581. select: true,
  2582. sener: true,
  2583. services: true,
  2584. seven: true,
  2585. sew: true,
  2586. sex: true,
  2587. sexy: true,
  2588. sfr: true,
  2589. sg: true,
  2590. sh: true,
  2591. sharp: true,
  2592. shell: true,
  2593. shiksha: true,
  2594. shoes: true,
  2595. shop: true,
  2596. shopping: true,
  2597. show: true,
  2598. si: true,
  2599. singles: true,
  2600. site: true,
  2601. sk: true,
  2602. ski: true,
  2603. skin: true,
  2604. sky: true,
  2605. skype: true,
  2606. sl: true,
  2607. sm: true,
  2608. smart: true,
  2609. sn: true,
  2610. sncf: true,
  2611. so: true,
  2612. soccer: true,
  2613. social: true,
  2614. softbank: true,
  2615. software: true,
  2616. sohu: true,
  2617. solar: true,
  2618. solutions: true,
  2619. sony: true,
  2620. soy: true,
  2621. spa: true,
  2622. space: true,
  2623. sport: true,
  2624. sr: true,
  2625. srl: true,
  2626. ss: true,
  2627. st: true,
  2628. stada: true,
  2629. statebank: true,
  2630. statefarm: true,
  2631. stc: true,
  2632. stockholm: true,
  2633. storage: true,
  2634. store: true,
  2635. stream: true,
  2636. studio: true,
  2637. study: true,
  2638. style: true,
  2639. su: true,
  2640. sucks: true,
  2641. supplies: true,
  2642. supply: true,
  2643. support: true,
  2644. surf: true,
  2645. surgery: true,
  2646. suzuki: true,
  2647. sv: true,
  2648. swatch: true,
  2649. swiss: true,
  2650. sx: true,
  2651. sy: true,
  2652. sydney: true,
  2653. systems: true,
  2654. sz: true,
  2655. taipei: true,
  2656. target: true,
  2657. tatamotors: true,
  2658. tatar: true,
  2659. tattoo: true,
  2660. tax: true,
  2661. taxi: true,
  2662. tc: true,
  2663. td: true,
  2664. team: true,
  2665. tech: true,
  2666. technology: true,
  2667. tel: true,
  2668. temasek: true,
  2669. tennis: true,
  2670. teva: true,
  2671. tf: true,
  2672. tg: true,
  2673. th: true,
  2674. theater: true,
  2675. theatre: true,
  2676. tickets: true,
  2677. tienda: true,
  2678. tips: true,
  2679. tires: true,
  2680. tirol: true,
  2681. tj: true,
  2682. tk: true,
  2683. tl: true,
  2684. tm: true,
  2685. tn: true,
  2686. to: true,
  2687. today: true,
  2688. tokyo: true,
  2689. tools: true,
  2690. top: true,
  2691. toray: true,
  2692. toshiba: true,
  2693. total: true,
  2694. tours: true,
  2695. town: true,
  2696. toyota: true,
  2697. toys: true,
  2698. tr: true,
  2699. trade: true,
  2700. trading: true,
  2701. training: true,
  2702. travel: true,
  2703. travelers: true,
  2704. trust: true,
  2705. tt: true,
  2706. tube: true,
  2707. tui: true,
  2708. tv: true,
  2709. tvs: true,
  2710. tw: true,
  2711. tz: true,
  2712. ua: true,
  2713. ug: true,
  2714. uk: true,
  2715. unicom: true,
  2716. university: true,
  2717. uno: true,
  2718. uol: true,
  2719. us: true,
  2720. uy: true,
  2721. uz: true,
  2722. va: true,
  2723. vacations: true,
  2724. vana: true,
  2725. vanguard: true,
  2726. vc: true,
  2727. ve: true,
  2728. vegas: true,
  2729. ventures: true,
  2730. versicherung: true,
  2731. vet: true,
  2732. vg: true,
  2733. vi: true,
  2734. viajes: true,
  2735. video: true,
  2736. vig: true,
  2737. villas: true,
  2738. vin: true,
  2739. vip: true,
  2740. vision: true,
  2741. vivo: true,
  2742. vlaanderen: true,
  2743. vn: true,
  2744. vodka: true,
  2745. vote: true,
  2746. voting: true,
  2747. voto: true,
  2748. voyage: true,
  2749. vu: true,
  2750. wales: true,
  2751. walter: true,
  2752. wang: true,
  2753. watch: true,
  2754. watches: true,
  2755. webcam: true,
  2756. weber: true,
  2757. website: true,
  2758. wed: true,
  2759. wedding: true,
  2760. weir: true,
  2761. wf: true,
  2762. whoswho: true,
  2763. wien: true,
  2764. wiki: true,
  2765. williamhill: true,
  2766. win: true,
  2767. windows: true,
  2768. wine: true,
  2769. wme: true,
  2770. woodside: true,
  2771. work: true,
  2772. works: true,
  2773. world: true,
  2774. ws: true,
  2775. wtf: true,
  2776. xbox: true,
  2777. xin: true,
  2778. "xn--1ck2e1b": true,
  2779. "xn--1qqw23a": true,
  2780. "xn--2scrj9c": true,
  2781. "xn--3bst00m": true,
  2782. "xn--3ds443g": true,
  2783. "xn--3e0b707e": true,
  2784. "xn--3hcrj9c": true,
  2785. "xn--45br5cyl": true,
  2786. "xn--45brj9c": true,
  2787. "xn--45q11c": true,
  2788. "xn--4dbrk0ce": true,
  2789. "xn--4gbrim": true,
  2790. "xn--54b7fta0cc": true,
  2791. "xn--55qx5d": true,
  2792. "xn--5tzm5g": true,
  2793. "xn--6frz82g": true,
  2794. "xn--6qq986b3xl": true,
  2795. "xn--80adxhks": true,
  2796. "xn--80ao21a": true,
  2797. "xn--80asehdb": true,
  2798. "xn--80aswg": true,
  2799. "xn--8y0a063a": true,
  2800. "xn--90a3ac": true,
  2801. "xn--90ae": true,
  2802. "xn--90ais": true,
  2803. "xn--9dbq2a": true,
  2804. "xn--bck1b9a5dre4c": true,
  2805. "xn--c1avg": true,
  2806. "xn--cck2b3b": true,
  2807. "xn--clchc0ea0b2g2a9gcd": true,
  2808. "xn--czr694b": true,
  2809. "xn--czrs0t": true,
  2810. "xn--czru2d": true,
  2811. "xn--d1acj3b": true,
  2812. "xn--d1alf": true,
  2813. "xn--e1a4c": true,
  2814. "xn--fct429k": true,
  2815. "xn--fiq228c5hs": true,
  2816. "xn--fiq64b": true,
  2817. "xn--fiqs8s": true,
  2818. "xn--fiqz9s": true,
  2819. "xn--fjq720a": true,
  2820. "xn--fpcrj9c3d": true,
  2821. "xn--fzc2c9e2c": true,
  2822. "xn--g2xx48c": true,
  2823. "xn--gckr3f0f": true,
  2824. "xn--gecrj9c": true,
  2825. "xn--h2breg3eve": true,
  2826. "xn--h2brj9c": true,
  2827. "xn--h2brj9c8c": true,
  2828. "xn--hxt814e": true,
  2829. "xn--i1b6b1a6a2e": true,
  2830. "xn--imr513n": true,
  2831. "xn--io0a7i": true,
  2832. "xn--j1amh": true,
  2833. "xn--j6w193g": true,
  2834. "xn--jvr189m": true,
  2835. "xn--kcrx77d1x4a": true,
  2836. "xn--kprw13d": true,
  2837. "xn--kpry57d": true,
  2838. "xn--kput3i": true,
  2839. "xn--l1acc": true,
  2840. "xn--lgbbat1ad8j": true,
  2841. "xn--mgb9awbf": true,
  2842. "xn--mgba3a4f16a": true,
  2843. "xn--mgbaam7a8h": true,
  2844. "xn--mgbab2bd": true,
  2845. "xn--mgbah1a3hjkrd": true,
  2846. "xn--mgbai9azgqp6j": true,
  2847. "xn--mgbayh7gpa": true,
  2848. "xn--mgbbh1a": true,
  2849. "xn--mgbc0a9azcg": true,
  2850. "xn--mgbca7dzdo": true,
  2851. "xn--mgbcpq6gpa1a": true,
  2852. "xn--mgberp4a5d4ar": true,
  2853. "xn--mgbgu82a": true,
  2854. "xn--mgbpl2fh": true,
  2855. "xn--mgbtx2b": true,
  2856. "xn--mix891f": true,
  2857. "xn--mk1bu44c": true,
  2858. "xn--ngbc5azd": true,
  2859. "xn--ngbe9e0a": true,
  2860. "xn--node": true,
  2861. "xn--nqv7f": true,
  2862. "xn--nyqy26a": true,
  2863. "xn--o3cw4h": true,
  2864. "xn--ogbpf8fl": true,
  2865. "xn--otu796d": true,
  2866. "xn--p1acf": true,
  2867. "xn--p1ai": true,
  2868. "xn--pgbs0dh": true,
  2869. "xn--q7ce6a": true,
  2870. "xn--q9jyb4c": true,
  2871. "xn--qxa6a": true,
  2872. "xn--qxam": true,
  2873. "xn--rhqv96g": true,
  2874. "xn--rovu88b": true,
  2875. "xn--rvc1e0am3e": true,
  2876. "xn--s9brj9c": true,
  2877. "xn--ses554g": true,
  2878. "xn--t60b56a": true,
  2879. "xn--tckwe": true,
  2880. "xn--unup4y": true,
  2881. "xn--vermgensberatung-pwb": true,
  2882. "xn--vhquv": true,
  2883. "xn--vuq861b": true,
  2884. "xn--wgbh1c": true,
  2885. "xn--wgbl6a": true,
  2886. "xn--xhq521b": true,
  2887. "xn--xkc2al3hye2a": true,
  2888. "xn--xkc2dl3a5ee0h": true,
  2889. "xn--y9a3aq": true,
  2890. "xn--yfro4i67o": true,
  2891. "xn--ygbi2ammx": true,
  2892. "xn--zfr164b": true,
  2893. xxx: true,
  2894. xyz: true,
  2895. yachts: true,
  2896. yahoo: true,
  2897. yandex: true,
  2898. ye: true,
  2899. yodobashi: true,
  2900. yoga: true,
  2901. yokohama: true,
  2902. youtube: true,
  2903. yt: true,
  2904. za: true,
  2905. zappos: true,
  2906. zara: true,
  2907. zip: true,
  2908. zm: true,
  2909. zone: true,
  2910. zuerich: true,
  2911. zw: true,
  2912. "セール": true,
  2913. "佛山": true,
  2914. "ಭಾರತ": true,
  2915. "集团": true,
  2916. "在线": true,
  2917. "한국": true,
  2918. "ଭାରତ": true,
  2919. "ভাৰত": true,
  2920. "ভারত": true,
  2921. "八卦": true,
  2922. "ישראל": true,
  2923. "موقع": true,
  2924. "বাংলা": true,
  2925. "公司": true,
  2926. "网站": true,
  2927. "移动": true,
  2928. "我爱你": true,
  2929. "москва": true,
  2930. "қаз": true,
  2931. "онлайн": true,
  2932. "сайт": true,
  2933. "联通": true,
  2934. "срб": true,
  2935. "бг": true,
  2936. "бел": true,
  2937. "קום": true,
  2938. "ファッション": true,
  2939. "орг": true,
  2940. "ストア": true,
  2941. "சிங்கப்பூர்": true,
  2942. "商标": true,
  2943. "商店": true,
  2944. "商城": true,
  2945. "дети": true,
  2946. "мкд": true,
  2947. "ею": true,
  2948. "家電": true,
  2949. "中文网": true,
  2950. "中信": true,
  2951. "中国": true,
  2952. "中國": true,
  2953. "娱乐": true,
  2954. "భారత్": true,
  2955. "ලංකා": true,
  2956. "购物": true,
  2957. "クラウド": true,
  2958. "ભારત": true,
  2959. "भारतम्": true,
  2960. "भारत": true,
  2961. "भारोत": true,
  2962. "网店": true,
  2963. "संगठन": true,
  2964. "餐厅": true,
  2965. "网络": true,
  2966. "укр": true,
  2967. "香港": true,
  2968. "食品": true,
  2969. "飞利浦": true,
  2970. "台湾": true,
  2971. "台灣": true,
  2972. "手机": true,
  2973. "мон": true,
  2974. "الجزائر": true,
  2975. "عمان": true,
  2976. "ایران": true,
  2977. "امارات": true,
  2978. "بازار": true,
  2979. "موريتانيا": true,
  2980. "پاکستان": true,
  2981. "الاردن": true,
  2982. "بارت": true,
  2983. "المغرب": true,
  2984. "ابوظبي": true,
  2985. "البحرين": true,
  2986. "السعودية": true,
  2987. "ڀارت": true,
  2988. "سودان": true,
  2989. "عراق": true,
  2990. "澳門": true,
  2991. "닷컴": true,
  2992. "شبكة": true,
  2993. "بيتك": true,
  2994. "გე": true,
  2995. "机构": true,
  2996. "健康": true,
  2997. "ไทย": true,
  2998. "سورية": true,
  2999. "招聘": true,
  3000. "рус": true,
  3001. "рф": true,
  3002. "تونس": true,
  3003. "ລາວ": true,
  3004. "みんな": true,
  3005. "ευ": true,
  3006. "ελ": true,
  3007. "世界": true,
  3008. "書籍": true,
  3009. "ഭാരതം": true,
  3010. "ਭਾਰਤ": true,
  3011. "网址": true,
  3012. "닷넷": true,
  3013. "コム": true,
  3014. "游戏": true,
  3015. "vermögensberatung": true,
  3016. "企业": true,
  3017. "信息": true,
  3018. "مصر": true,
  3019. "قطر": true,
  3020. "广东": true,
  3021. "இலங்கை": true,
  3022. "இந்தியா": true,
  3023. "հայ": true,
  3024. "新加坡": true,
  3025. "فلسطين": true,
  3026. "政务": true,
  3027. onion: true
  3028. };
  3029.  
  3030. var RE = {
  3031. PROTOCOL: "([a-z][-a-z*]+://)?",
  3032. USER: "(?:([\\w:.+-]+)@)?",
  3033. DOMAIN_UNI: `([a-z0-9-.\\u00A0-\\uFFFF]+\\.[a-z0-9-${chars}]{1,${maxLength}})`,
  3034. DOMAIN: `([a-z0-9-.]+\\.[a-z0-9-]{1,${maxLength}})`,
  3035. PORT: "(:\\d+\\b)?",
  3036. PATH_UNI: "([/?#]\\S*)?",
  3037. PATH: "([/?#][\\w-.~!$&*+;=:@%/?#(),'\\[\\]]*)?"
  3038. },
  3039. TLD_TABLE = table;
  3040.  
  3041. function regexEscape(text) {
  3042. return text.replace(/[[\]\\^-]/g, "\\$&");
  3043. }
  3044.  
  3045. function buildRegex({
  3046. unicode = false, customRules = [], standalone = false,
  3047. boundaryLeft, boundaryRight
  3048. }) {
  3049. var pattern = RE.PROTOCOL + RE.USER;
  3050. if (unicode) {
  3051. pattern += RE.DOMAIN_UNI + RE.PORT + RE.PATH_UNI;
  3052. } else {
  3053. pattern += RE.DOMAIN + RE.PORT + RE.PATH;
  3054. }
  3055. if (customRules.length) {
  3056. pattern = "(?:(" + customRules.join("|") + ")|" + pattern + ")";
  3057. } else {
  3058. pattern = "()" + pattern;
  3059. }
  3060. var prefix, suffix, invalidSuffix;
  3061. if (standalone) {
  3062. if (boundaryLeft) {
  3063. prefix = "((?:^|\\s)[" + regexEscape(boundaryLeft) + "]*?)";
  3064. } else {
  3065. prefix = "(^|\\s)";
  3066. }
  3067. if (boundaryRight) {
  3068. suffix = "([" + regexEscape(boundaryRight) + "]*(?:$|\\s))";
  3069. } else {
  3070. suffix = "($|\\s)";
  3071. }
  3072. invalidSuffix = "[^\\s" + regexEscape(boundaryRight) + "]";
  3073. } else {
  3074. prefix = "(^|\\b|_)";
  3075. suffix = "()";
  3076. }
  3077. pattern = prefix + pattern + suffix;
  3078. return {
  3079. url: new RegExp(pattern, "igm"),
  3080. invalidSuffix: invalidSuffix && new RegExp(invalidSuffix),
  3081. mustache: /\{\{[\s\S]+?\}\}/g
  3082. };
  3083. }
  3084.  
  3085. function pathStrip(m, re, repl) {
  3086. var s = m.path.replace(re, repl);
  3087.  
  3088. if (s == m.path) return;
  3089. m.end -= m.path.length - s.length;
  3090. m.suffix = m.path.slice(s.length) + m.suffix;
  3091. m.path = s;
  3092. }
  3093.  
  3094. function pathStripQuote(m, c) {
  3095. var i = 0, s = m.path, end, pos = 0;
  3096. if (!s.endsWith(c)) return;
  3097. while ((pos = s.indexOf(c, pos)) >= 0) {
  3098. if (i % 2) {
  3099. end = null;
  3100. } else {
  3101. end = pos;
  3102. }
  3103. pos++;
  3104. i++;
  3105. }
  3106. if (!end) return;
  3107. m.end -= s.length - end;
  3108. m.path = s.slice(0, end);
  3109. m.suffix = s.slice(end) + m.suffix;
  3110. }
  3111.  
  3112. function pathStripBrace(m, left, right) {
  3113. var str = m.path,
  3114. re = new RegExp("[\\" + left + "\\" + right + "]", "g"),
  3115. match, count = 0, end;
  3116.  
  3117. // Match loop
  3118. while ((match = re.exec(str))) {
  3119. if (count % 2 == 0) {
  3120. end = match.index;
  3121. if (match[0] == right) {
  3122. break;
  3123. }
  3124. } else {
  3125. if (match[0] == left) {
  3126. break;
  3127. }
  3128. }
  3129. count++;
  3130. }
  3131.  
  3132. if (!match && count % 2 == 0) {
  3133. return;
  3134. }
  3135. m.end -= m.path.length - end;
  3136. m.path = str.slice(0, end);
  3137. m.suffix = str.slice(end) + m.suffix;
  3138. }
  3139.  
  3140. function isIP(s) {
  3141. var m, i;
  3142. if (!(m = s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))) {
  3143. return false;
  3144. }
  3145. for (i = 1; i < m.length; i++) {
  3146. if (+m[i] > 255 || (m[i].length > 1 && m[i][0] == "0")) {
  3147. return false;
  3148. }
  3149. }
  3150. return true;
  3151. }
  3152.  
  3153. function inTLDS(domain) {
  3154. var match = domain.match(/\.([^.]+)$/);
  3155. if (!match) {
  3156. return false;
  3157. }
  3158. var key = match[1].toLowerCase();
  3159. // eslint-disable-next-line no-prototype-builtins
  3160. return TLD_TABLE.hasOwnProperty(key);
  3161. }
  3162.  
  3163. class UrlMatcher {
  3164. constructor(options = {}) {
  3165. this.options = options;
  3166. this.regex = buildRegex(options);
  3167. }
  3168. *match(text) {
  3169. var {
  3170. fuzzyIp = true,
  3171. ignoreMustache = false,
  3172. mail = true
  3173. } = this.options,
  3174. {
  3175. url,
  3176. invalidSuffix,
  3177. mustache
  3178. } = this.regex,
  3179. urlLastIndex, mustacheLastIndex;
  3180. mustache.lastIndex = 0;
  3181. url.lastIndex = 0;
  3182. var mustacheMatch, mustacheRange;
  3183. if (ignoreMustache) {
  3184. mustacheMatch = mustache.exec(text);
  3185. if (mustacheMatch) {
  3186. mustacheRange = {
  3187. start: mustacheMatch.index,
  3188. end: mustache.lastIndex
  3189. };
  3190. }
  3191. }
  3192. var urlMatch;
  3193. while ((urlMatch = url.exec(text))) {
  3194. const result = {
  3195. start: 0,
  3196. end: 0,
  3197. text: "",
  3198. url: "",
  3199. prefix: urlMatch[1],
  3200. custom: urlMatch[2],
  3201. protocol: urlMatch[3],
  3202. auth: urlMatch[4] || "",
  3203. domain: urlMatch[5],
  3204. port: urlMatch[6] || "",
  3205. path: urlMatch[7] || "",
  3206. suffix: urlMatch[8]
  3207. };
  3208. if (result.custom) {
  3209. result.start = urlMatch.index;
  3210. result.end = url.lastIndex;
  3211. result.text = result.url = urlMatch[0];
  3212. } else {
  3213. result.start = urlMatch.index + result.prefix.length;
  3214. result.end = url.lastIndex - result.suffix.length;
  3215. }
  3216. if (mustacheRange && mustacheRange.end <= result.start) {
  3217. mustacheMatch = mustache.exec(text);
  3218. if (mustacheMatch) {
  3219. mustacheRange.start = mustacheMatch.index;
  3220. mustacheRange.end = mustache.lastIndex;
  3221. } else {
  3222. mustacheRange = null;
  3223. }
  3224. }
  3225. // ignore urls inside mustache pair
  3226. if (mustacheRange && result.start < mustacheRange.end && result.end >= mustacheRange.start) {
  3227. continue;
  3228. }
  3229. if (!result.custom) {
  3230. // adjust path and suffix
  3231. if (result.path) {
  3232. // Strip BBCode
  3233. pathStrip(result, /\[\/?(b|i|u|url|img|quote|code|size|color)\].*/i, "");
  3234. // Strip braces
  3235. pathStripBrace(result, "(", ")");
  3236. pathStripBrace(result, "[", "]");
  3237. pathStripBrace(result, "{", "}");
  3238. // Strip quotes
  3239. pathStripQuote(result, "'");
  3240. pathStripQuote(result, '"');
  3241. // Remove trailing ".,?"
  3242. pathStrip(result, /(^|[^-_])[.,?]+$/, "$1");
  3243. }
  3244. // check suffix
  3245. if (invalidSuffix && invalidSuffix.test(result.suffix)) {
  3246. if (/\s$/.test(result.suffix)) {
  3247. url.lastIndex--;
  3248. }
  3249. continue;
  3250. }
  3251. // ignore fuzzy ip
  3252. if (!fuzzyIp && isIP(result.domain) &&
  3253. !result.protocol && !result.auth && !result.path) {
  3254. continue;
  3255. }
  3256. // mailto protocol
  3257. if (!result.protocol && result.auth) {
  3258. var matchMail = result.auth.match(/^mailto:(.+)/);
  3259. if (matchMail) {
  3260. result.protocol = "mailto:";
  3261. result.auth = matchMail[1];
  3262. }
  3263. }
  3264.  
  3265. // http alias
  3266. if (result.protocol && result.protocol.match(/^(hxxp|h\*\*p|ttp)/)) {
  3267. result.protocol = "http://";
  3268. }
  3269.  
  3270. // guess protocol
  3271. if (!result.protocol) {
  3272. var domainMatch;
  3273. if ((domainMatch = result.domain.match(/^(ftp|irc)/))) {
  3274. result.protocol = domainMatch[0] + "://";
  3275. } else if (result.domain.match(/^(www|web)/)) {
  3276. result.protocol = "http://";
  3277. } else if (result.auth && result.auth.indexOf(":") < 0 && !result.path) {
  3278. result.protocol = "mailto:";
  3279. } else {
  3280. result.protocol = "http://";
  3281. }
  3282. }
  3283. // ignore mail
  3284. if (!mail && result.protocol === "mailto:") {
  3285. continue;
  3286. }
  3287. // verify domain
  3288. if (!isIP(result.domain)) {
  3289. if (/^(http|https|mailto)/.test(result.protocol) && !inTLDS(result.domain)) {
  3290. continue;
  3291. }
  3292. const invalidLabel = getInvalidLabel(result.domain);
  3293. if (invalidLabel) {
  3294. url.lastIndex = urlMatch.index + invalidLabel.index + 1;
  3295. continue;
  3296. }
  3297. }
  3298.  
  3299. // Create URL
  3300. result.url = result.protocol + (result.auth && result.auth + "@") + result.domain + result.port + result.path;
  3301. result.text = text.slice(result.start, result.end);
  3302. }
  3303. // since regex is shared with other parse generators, cache lastIndex position and restore later
  3304. mustacheLastIndex = mustache.lastIndex;
  3305. urlLastIndex = url.lastIndex;
  3306. yield result;
  3307. url.lastIndex = urlLastIndex;
  3308. mustache.lastIndex = mustacheLastIndex;
  3309. }
  3310. }
  3311. }
  3312.  
  3313. function getInvalidLabel(domain) {
  3314. // https://tools.ietf.org/html/rfc1035
  3315. // https://serverfault.com/questions/638260/is-it-valid-for-a-hostname-to-start-with-a-digit
  3316. let index = 0;
  3317. const parts = domain.split(".");
  3318. for (const part of parts) {
  3319. if (
  3320. !part ||
  3321. part.startsWith("-") ||
  3322. part.endsWith("-")
  3323. ) {
  3324. return {
  3325. index,
  3326. value: part
  3327. };
  3328. }
  3329. index += part.length + 1;
  3330. }
  3331. }
  3332.  
  3333. /* eslint-env browser */
  3334.  
  3335.  
  3336. var INVALID_TAGS = {
  3337. a: true,
  3338. noscript: true,
  3339. option: true,
  3340. script: true,
  3341. style: true,
  3342. textarea: true,
  3343. svg: true,
  3344. canvas: true,
  3345. button: true,
  3346. select: true,
  3347. template: true,
  3348. meter: true,
  3349. progress: true,
  3350. math: true,
  3351. time: true
  3352. };
  3353.  
  3354. class Pos {
  3355. constructor(container, offset, i = 0) {
  3356. this.container = container;
  3357. this.offset = offset;
  3358. this.i = i;
  3359. }
  3360. add(change) {
  3361. var cont = this.container,
  3362. offset = this.offset;
  3363.  
  3364. this.i += change;
  3365. // If the container is #text.parentNode
  3366. if (cont.childNodes.length) {
  3367. cont = cont.childNodes[offset];
  3368. offset = 0;
  3369. }
  3370.  
  3371. // If the container is #text
  3372. while (cont) {
  3373. if (cont.nodeType == 3) {
  3374. if (!cont.LEN) {
  3375. cont.LEN = cont.nodeValue.length;
  3376. }
  3377. if (offset + change <= cont.LEN) {
  3378. this.container = cont;
  3379. this.offset = offset + change;
  3380. return;
  3381. }
  3382. change = offset + change - cont.LEN;
  3383. offset = 0;
  3384. }
  3385. cont = cont.nextSibling;
  3386. }
  3387. }
  3388. moveTo(offset) {
  3389. this.add(offset - this.i);
  3390. }
  3391. }
  3392.  
  3393. function cloneContents(range) {
  3394. if (range.startContainer == range.endContainer) {
  3395. return document.createTextNode(range.toString());
  3396. }
  3397. return range.cloneContents();
  3398. }
  3399.  
  3400. var DEFAULT_OPTIONS = {
  3401. maxRunTime: 100,
  3402. timeout: 10000,
  3403. newTab: true,
  3404. noOpener: true,
  3405. embedImage: true,
  3406. recursive: true,
  3407. };
  3408.  
  3409. class Linkifier extends EventLite {
  3410. constructor(root, options = {}) {
  3411. super();
  3412. if (!(root instanceof Node)) {
  3413. options = root;
  3414. root = options.root;
  3415. }
  3416. this.root = root;
  3417. this.options = Object.assign({}, DEFAULT_OPTIONS, options);
  3418. this.aborted = false;
  3419. }
  3420. start() {
  3421. var time = Date.now,
  3422. startTime = time(),
  3423. chunks = this.generateChunks();
  3424. var next = () => {
  3425. if (this.aborted) {
  3426. this.emit("error", new Error("Aborted"));
  3427. return;
  3428. }
  3429. var chunkStart = time(),
  3430. now;
  3431. do {
  3432. if (chunks.next().done) {
  3433. this.emit("complete", time() - startTime);
  3434. return;
  3435. }
  3436. } while ((now = time()) - chunkStart < this.options.maxRunTime);
  3437. if (now - startTime > this.options.timeout) {
  3438. this.emit("error", new Error(`max execution time exceeded: ${now - startTime}, on ${this.root}`));
  3439. return;
  3440. }
  3441. setTimeout(next);
  3442. };
  3443. setTimeout(next);
  3444. }
  3445. abort() {
  3446. this.aborted = true;
  3447. }
  3448. *generateRanges() {
  3449. var {validator, recursive} = this.options;
  3450. var filter = {
  3451. acceptNode: function(node) {
  3452. if (validator && !validator(node)) {
  3453. return NodeFilter.FILTER_REJECT;
  3454. }
  3455. if (INVALID_TAGS[node.localName]) {
  3456. return NodeFilter.FILTER_REJECT;
  3457. }
  3458. if (node.localName == "wbr") {
  3459. return NodeFilter.FILTER_ACCEPT;
  3460. }
  3461. if (node.nodeType == 3) {
  3462. return NodeFilter.FILTER_ACCEPT;
  3463. }
  3464. return recursive ? NodeFilter.FILTER_SKIP : NodeFilter.FILTER_REJECT;
  3465. }
  3466. };
  3467. // Generate linkified ranges.
  3468. var walker = document.createTreeWalker(
  3469. this.root,
  3470. NodeFilter.SHOW_TEXT + NodeFilter.SHOW_ELEMENT,
  3471. filter
  3472. ), start, end, current, range;
  3473.  
  3474. end = start = walker.nextNode();
  3475. if (!start) {
  3476. return;
  3477. }
  3478. range = document.createRange();
  3479. range.setStartBefore(start);
  3480. while ((current = walker.nextNode())) {
  3481. if (end.nextSibling == current) {
  3482. end = current;
  3483. continue;
  3484. }
  3485. range.setEndAfter(end);
  3486. yield range;
  3487.  
  3488. end = start = current;
  3489. range.setStartBefore(start);
  3490. }
  3491. range.setEndAfter(end);
  3492. yield range;
  3493. }
  3494. *generateChunks() {
  3495. var {matcher} = this.options;
  3496. for (var range of this.generateRanges()) {
  3497. var frag = null,
  3498. pos = null,
  3499. text = range.toString(),
  3500. textRange = null;
  3501. for (var result of matcher.match(text)) {
  3502. if (!frag) {
  3503. frag = document.createDocumentFragment();
  3504. pos = new Pos(range.startContainer, range.startOffset);
  3505. textRange = range.cloneRange();
  3506. }
  3507. // clone text
  3508. pos.moveTo(result.start);
  3509. textRange.setEnd(pos.container, pos.offset);
  3510. frag.appendChild(cloneContents(textRange));
  3511. // clone link
  3512. textRange.collapse();
  3513. pos.moveTo(result.end);
  3514. textRange.setEnd(pos.container, pos.offset);
  3515. var content = cloneContents(textRange),
  3516. link = this.buildLink(result, content);
  3517.  
  3518. textRange.collapse();
  3519.  
  3520. frag.appendChild(link);
  3521. this.emit("link", {link, range, result, content});
  3522. }
  3523. if (pos) {
  3524. pos.moveTo(text.length);
  3525. textRange.setEnd(pos.container, pos.offset);
  3526. frag.appendChild(cloneContents(textRange));
  3527. range.deleteContents();
  3528. range.insertNode(frag);
  3529. }
  3530. yield;
  3531. }
  3532. }
  3533. buildLink(result, content) {
  3534. var {newTab, embedImage, noOpener} = this.options;
  3535. var link = document.createElement("a");
  3536. link.href = result.url;
  3537. link.title = "Linkify Plus Plus";
  3538. link.className = "linkifyplus";
  3539. if (newTab) {
  3540. link.target = "_blank";
  3541. }
  3542. if (noOpener) {
  3543. link.rel = "noopener";
  3544. }
  3545. var child;
  3546. if (embedImage && /^[^?#]+\.(?:jpg|jpeg|png|apng|gif|svg|webp)(?:$|[?#])/i.test(result.url)) {
  3547. child = new Image;
  3548. child.src = result.url;
  3549. child.alt = result.text;
  3550. } else {
  3551. child = content;
  3552. }
  3553. link.appendChild(child);
  3554. return link;
  3555. }
  3556. }
  3557.  
  3558. function linkify(...args) {
  3559. return new Promise((resolve, reject) => {
  3560. var linkifier = new Linkifier(...args);
  3561. linkifier.on("error", reject);
  3562. linkifier.on("complete", resolve);
  3563. for (var key of Object.keys(linkifier.options)) {
  3564. if (key.startsWith("on")) {
  3565. linkifier.on(key.slice(2), linkifier.options[key]);
  3566. }
  3567. }
  3568. linkifier.start();
  3569. });
  3570. }
  3571.  
  3572. const processedNodes = new WeakSet;
  3573. const nodeValidationCache = new WeakMap; // Node -> boolean
  3574.  
  3575. async function linkifyRoot(root, options, useIncludeElement = true) {
  3576. if (validRoot(root, options.validator)) {
  3577. processedNodes.add(root);
  3578. await linkify({...options, root, recursive: true});
  3579. }
  3580. if (options.includeElement && useIncludeElement) {
  3581. for (const el of root.querySelectorAll(options.includeElement)) {
  3582. await linkifyRoot(el, options, false);
  3583. }
  3584. }
  3585. }
  3586.  
  3587. function validRoot(node, validator) {
  3588. if (processedNodes.has(node)) {
  3589. return false;
  3590. }
  3591. return getValidation(node);
  3592.  
  3593. function getValidation(p) {
  3594. if (!p.parentNode) {
  3595. return false;
  3596. }
  3597. let r = nodeValidationCache.get(p);
  3598. if (r === undefined) {
  3599. if (validator.isIncluded(p)) {
  3600. r = true;
  3601. } else if (validator.isExcluded(p)) {
  3602. r = false;
  3603. } else if (p.parentNode != document.documentElement) {
  3604. r = getValidation(p.parentNode);
  3605. } else {
  3606. r = true;
  3607. }
  3608. nodeValidationCache.set(p, r);
  3609. }
  3610. return r;
  3611. }
  3612. }
  3613.  
  3614. function prepareDocument() {
  3615. // wait till everything is ready
  3616. return prepareBody().then(prepareApp);
  3617. function prepareApp() {
  3618. const appRoot = document.querySelector("[data-server-rendered]");
  3619. if (!appRoot) {
  3620. return;
  3621. }
  3622. return new Promise(resolve => {
  3623. const onChange = () => {
  3624. if (!appRoot.hasAttribute("data-server-rendered")) {
  3625. resolve();
  3626. observer.disconnect();
  3627. }
  3628. };
  3629. const observer = new MutationObserver(onChange);
  3630. observer.observe(appRoot, {attributes: true});
  3631. });
  3632. }
  3633. function prepareBody() {
  3634. if (document.readyState !== "loading") {
  3635. return Promise.resolve();
  3636. }
  3637. return new Promise(resolve => {
  3638. // https://github.com/Tampermonkey/tampermonkey/issues/485
  3639. document.addEventListener("DOMContentLoaded", resolve, {once: true});
  3640. });
  3641. }
  3642. }
  3643.  
  3644. // import {processedNodes} from "./cache.mjs";
  3645.  
  3646. var load = {
  3647. key: "triggerByPageLoad",
  3648. enable: async options => {
  3649. await prepareDocument();
  3650. await linkifyRoot(document.body, options);
  3651. },
  3652. disable: () => {}
  3653. };
  3654.  
  3655. let options$1;
  3656.  
  3657. const EVENTS$1 = [
  3658. ["click", handle$1, {passive: true}],
  3659. ];
  3660.  
  3661. function handle$1(e) {
  3662. const el = e.target;
  3663. if (validRoot(el, options$1.validator)) {
  3664. processedNodes.add(el);
  3665. linkify({...options$1, root: el, recursive: false});
  3666. }
  3667. }
  3668.  
  3669. function enable$2(_options) {
  3670. options$1 = _options;
  3671. for (const [event, handler, options] of EVENTS$1) {
  3672. document.addEventListener(event, handler, options);
  3673. }
  3674. }
  3675.  
  3676. function disable$2() {
  3677. for (const [event, handler, options] of EVENTS$1) {
  3678. document.removeEventListener(event, handler, options);
  3679. }
  3680. }
  3681.  
  3682. var click = {
  3683. key: "triggerByClick",
  3684. enable: enable$2,
  3685. disable: disable$2
  3686. };
  3687.  
  3688. let options;
  3689.  
  3690. const EVENTS = [
  3691. // catch the first mousemove event since mouseover doesn't fire at page refresh
  3692. ["mousemove", handle, {passive: true, once: true}],
  3693. ["mouseover", handle, {passive: true}]
  3694. ];
  3695.  
  3696. function handle(e) {
  3697. const el = e.target;
  3698. if (validRoot(el, options.validator)) {
  3699. processedNodes.add(el);
  3700. linkify({...options, root: el, recursive: false});
  3701. }
  3702. }
  3703.  
  3704. function enable$1(_options) {
  3705. options = _options;
  3706. for (const [event, handler, options] of EVENTS) {
  3707. document.addEventListener(event, handler, options);
  3708. }
  3709. }
  3710.  
  3711. function disable$1() {
  3712. for (const [event, handler, options] of EVENTS) {
  3713. document.removeEventListener(event, handler, options);
  3714. }
  3715. }
  3716.  
  3717. var hover = {
  3718. key: "triggerByHover",
  3719. enable: enable$1,
  3720. disable: disable$1
  3721. };
  3722.  
  3723. const MAX_PROCESSES = 100;
  3724. let processes = 0;
  3725. let observer;
  3726.  
  3727. async function enable(options) {
  3728. await prepareDocument();
  3729. observer = new MutationObserver(function(mutations){
  3730. // Filter out mutations generated by LPP
  3731. var lastRecord = mutations[mutations.length - 1],
  3732. nodes = lastRecord.addedNodes,
  3733. i;
  3734.  
  3735. if (nodes.length >= 2) {
  3736. for (i = 0; i < 2; i++) {
  3737. if (nodes[i].className == "linkifyplus") {
  3738. return;
  3739. }
  3740. }
  3741. }
  3742.  
  3743. for (var record of mutations) {
  3744. for (const node of record.addedNodes) {
  3745. if (node.nodeType === 1 && !processedNodes.has(node)) {
  3746. if (processes >= MAX_PROCESSES) {
  3747. throw new Error("Too many processes");
  3748. }
  3749. processes++;
  3750. linkifyRoot(node, options)
  3751. .finally(() => {
  3752. processes--;
  3753. });
  3754. }
  3755. }
  3756. }
  3757. });
  3758. observer.observe(document.body, {
  3759. childList: true,
  3760. subtree: true
  3761. });
  3762. }
  3763.  
  3764. async function disable() {
  3765. await prepareDocument();
  3766. observer && observer.disconnect();
  3767. }
  3768.  
  3769. var mutation = {
  3770. key: "triggerByNewNode",
  3771. enable,
  3772. disable
  3773. };
  3774.  
  3775. var triggers = [load, click, hover, mutation];
  3776.  
  3777. function createValidator({includeElement, excludeElement}) {
  3778. const f = function(node) {
  3779. if (processedNodes.has(node)) {
  3780. return false;
  3781. }
  3782.  
  3783. if (node.isContentEditable) {
  3784. return false;
  3785. }
  3786.  
  3787. if (node.matches) {
  3788. if (includeElement && node.matches(includeElement)) {
  3789. return true;
  3790. }
  3791. if (excludeElement && node.matches(excludeElement)) {
  3792. return false;
  3793. }
  3794. }
  3795. return true;
  3796. };
  3797. f.isIncluded = node => {
  3798. return includeElement && node.matches(includeElement);
  3799. };
  3800. f.isExcluded = node => {
  3801. if (INVALID_TAGS[node.localName]) {
  3802. return true;
  3803. }
  3804. return excludeElement && node.matches(excludeElement);
  3805. };
  3806. return f;
  3807. }
  3808.  
  3809. function stringToList(value) {
  3810. value = value.trim();
  3811. if (!value) {
  3812. return [];
  3813. }
  3814. return value.split(/\s*\n\s*/g);
  3815. }
  3816.  
  3817. function createOptions(pref) {
  3818. const options = {};
  3819. pref.on("change", update);
  3820. update(pref.getAll());
  3821. return options;
  3822. function update(changes) {
  3823. Object.assign(options, changes);
  3824. options.validator = createValidator(options);
  3825. if (typeof options.customRules === "string") {
  3826. options.customRules = stringToList(options.customRules);
  3827. }
  3828. options.matcher = new UrlMatcher(options);
  3829. options.onlink = options.embedImageExcludeElement ? onlink : null;
  3830. }
  3831. function onlink({link, range, content}) {
  3832. if (link.childNodes[0].localName !== "img" || !options.embedImageExcludeElement) {
  3833. return;
  3834. }
  3835. var parent = range.startContainer;
  3836. // it might be a text node
  3837. if (!parent.closest) {
  3838. parent = parent.parentNode;
  3839. }
  3840. if (!parent.closest(options.embedImageExcludeElement)) return;
  3841. // remove image
  3842. link.innerHTML = "";
  3843. link.appendChild(content);
  3844. }
  3845. }
  3846.  
  3847. async function startLinkifyPlusPlus(getPref) {
  3848. // Limit contentType to specific content type
  3849. if (
  3850. document.contentType &&
  3851. !["text/plain", "text/html", "application/xhtml+xml"].includes(document.contentType)
  3852. ) {
  3853. return;
  3854. }
  3855. const pref = await getPref();
  3856. const options = createOptions(pref);
  3857. for (const trigger of triggers) {
  3858. if (pref.get(trigger.key)) {
  3859. trigger.enable(options);
  3860. }
  3861. }
  3862. pref.on("change", changes => {
  3863. for (const trigger of triggers) {
  3864. if (changes[trigger.key] === true) {
  3865. trigger.enable(options);
  3866. }
  3867. if (changes[trigger.key] === false) {
  3868. trigger.disable();
  3869. }
  3870. }
  3871. });
  3872. }
  3873.  
  3874. function getMessageFactory() {
  3875. return (key, params) => {
  3876. if (!params) {
  3877. return translate[key];
  3878. }
  3879. if (!Array.isArray(params)) {
  3880. params = [params];
  3881. }
  3882. return translate[key].replace(/\$\d/g, m => {
  3883. const index = Number(m.slice(1));
  3884. return params[index - 1];
  3885. });
  3886. };
  3887. }
  3888.  
  3889. startLinkifyPlusPlus(async () => {
  3890. const getMessage = getMessageFactory();
  3891. const pref = GM_webextPref({
  3892. default: prefDefault(),
  3893. body: prefBody(getMessage),
  3894. getMessage,
  3895. getNewScope: () => location.hostname
  3896. });
  3897. await pref.ready();
  3898. await pref.setCurrentScope(location.hostname);
  3899. return pref;
  3900. });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址