UserUtils

Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more

当前为 2024-04-06 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/472956/1355459/UserUtils.js

  1. // ==UserScript==
  2. // @namespace https://github.com/Sv443-Network/UserUtils
  3. // @exclude *
  4. // @author Sv443
  5. // @supportURL https://github.com/Sv443-Network/UserUtils/issues
  6. // @homepageURL https://github.com/Sv443-Network/UserUtils
  7.  
  8. // ==UserLibrary==
  9. // @name UserUtils
  10. // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more
  11. // @version 6.3.0
  12. // @license MIT
  13. // @copyright Sv443 (https://github.com/Sv443)
  14.  
  15. // ==/UserScript==
  16. // ==/UserLibrary==
  17.  
  18. // ==OpenUserJS==
  19. // @author Sv443
  20. // ==/OpenUserJS==
  21.  
  22. var UserUtils = (function (exports) {
  23. var __defProp = Object.defineProperty;
  24. var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  25. var __hasOwnProp = Object.prototype.hasOwnProperty;
  26. var __propIsEnum = Object.prototype.propertyIsEnumerable;
  27. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  28. var __spreadValues = (a, b) => {
  29. for (var prop in b || (b = {}))
  30. if (__hasOwnProp.call(b, prop))
  31. __defNormalProp(a, prop, b[prop]);
  32. if (__getOwnPropSymbols)
  33. for (var prop of __getOwnPropSymbols(b)) {
  34. if (__propIsEnum.call(b, prop))
  35. __defNormalProp(a, prop, b[prop]);
  36. }
  37. return a;
  38. };
  39. var __objRest = (source, exclude) => {
  40. var target = {};
  41. for (var prop in source)
  42. if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
  43. target[prop] = source[prop];
  44. if (source != null && __getOwnPropSymbols)
  45. for (var prop of __getOwnPropSymbols(source)) {
  46. if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
  47. target[prop] = source[prop];
  48. }
  49. return target;
  50. };
  51. var __publicField = (obj, key, value) => {
  52. __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  53. return value;
  54. };
  55. var __async = (__this, __arguments, generator) => {
  56. return new Promise((resolve, reject) => {
  57. var fulfilled = (value) => {
  58. try {
  59. step(generator.next(value));
  60. } catch (e) {
  61. reject(e);
  62. }
  63. };
  64. var rejected = (value) => {
  65. try {
  66. step(generator.throw(value));
  67. } catch (e) {
  68. reject(e);
  69. }
  70. };
  71. var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
  72. step((generator = generator.apply(__this, __arguments)).next());
  73. });
  74. };
  75.  
  76. // lib/math.ts
  77. function clamp(value, min, max) {
  78. return Math.max(Math.min(value, max), min);
  79. }
  80. function mapRange(value, range1min, range1max, range2min, range2max) {
  81. if (Number(range1min) === 0 && Number(range2min) === 0)
  82. return value * (range2max / range1max);
  83. return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min;
  84. }
  85. function randRange(...args) {
  86. let min, max;
  87. if (typeof args[0] === "number" && typeof args[1] === "number")
  88. [min, max] = args;
  89. else if (typeof args[0] === "number" && typeof args[1] !== "number") {
  90. min = 0;
  91. [max] = args;
  92. } else
  93. throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof args[0]}" and "${typeof args[1]}"`);
  94. min = Number(min);
  95. max = Number(max);
  96. if (isNaN(min) || isNaN(max))
  97. return NaN;
  98. if (min > max)
  99. throw new TypeError(`Parameter "min" can't be bigger than "max"`);
  100. return Math.floor(Math.random() * (max - min + 1)) + min;
  101. }
  102. function randomId(length = 16, radix = 16) {
  103. const arr = new Uint8Array(length);
  104. crypto.getRandomValues(arr);
  105. return Array.from(
  106. arr,
  107. (v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1)
  108. ).join("");
  109. }
  110.  
  111. // lib/array.ts
  112. function randomItem(array) {
  113. return randomItemIndex(array)[0];
  114. }
  115. function randomItemIndex(array) {
  116. if (array.length === 0)
  117. return [void 0, void 0];
  118. const idx = randRange(array.length - 1);
  119. return [array[idx], idx];
  120. }
  121. function takeRandomItem(arr) {
  122. const [itm, idx] = randomItemIndex(arr);
  123. if (idx === void 0)
  124. return void 0;
  125. arr.splice(idx, 1);
  126. return itm;
  127. }
  128. function randomizeArray(array) {
  129. const retArray = [...array];
  130. if (array.length === 0)
  131. return retArray;
  132. for (let i = retArray.length - 1; i > 0; i--) {
  133. const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
  134. [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
  135. }
  136. return retArray;
  137. }
  138.  
  139. // lib/DataStore.ts
  140. var DataStore = class {
  141. /**
  142. * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
  143. * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
  144. *
  145. * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue`
  146. * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
  147. *
  148. * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `options.defaultData`) - this should also be the type of the data format associated with the current `options.formatVersion`
  149. * @param options The options for this DataStore instance
  150. */
  151. constructor(options) {
  152. __publicField(this, "id");
  153. __publicField(this, "formatVersion");
  154. __publicField(this, "defaultData");
  155. __publicField(this, "cachedData");
  156. __publicField(this, "migrations");
  157. __publicField(this, "encodeData");
  158. __publicField(this, "decodeData");
  159. this.id = options.id;
  160. this.formatVersion = options.formatVersion;
  161. this.defaultData = options.defaultData;
  162. this.cachedData = options.defaultData;
  163. this.migrations = options.migrations;
  164. this.encodeData = options.encodeData;
  165. this.decodeData = options.decodeData;
  166. }
  167. /**
  168. * Loads the data saved in persistent storage into the in-memory cache and also returns it.
  169. * Automatically populates persistent storage with default data if it doesn't contain any data yet.
  170. * Also runs all necessary migration functions if the data format has changed since the last time the data was saved.
  171. */
  172. loadData() {
  173. return __async(this, null, function* () {
  174. try {
  175. const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultData);
  176. let gmFmtVer = Number(yield GM.getValue(`_uucfgver-${this.id}`));
  177. if (typeof gmData !== "string") {
  178. yield this.saveDefaultData();
  179. return __spreadValues({}, this.defaultData);
  180. }
  181. const isEncoded = yield GM.getValue(`_uucfgenc-${this.id}`, false);
  182. if (isNaN(gmFmtVer))
  183. yield GM.setValue(`_uucfgver-${this.id}`, gmFmtVer = this.formatVersion);
  184. let parsed = yield this.deserializeData(gmData, isEncoded);
  185. if (gmFmtVer < this.formatVersion && this.migrations)
  186. parsed = yield this.runMigrations(parsed, gmFmtVer);
  187. return __spreadValues({}, this.cachedData = parsed);
  188. } catch (err) {
  189. console.warn("Error while parsing JSON data, resetting it to the default value.", err);
  190. yield this.saveDefaultData();
  191. return this.defaultData;
  192. }
  193. });
  194. }
  195. /**
  196. * Returns a copy of the data from the in-memory cache.
  197. * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
  198. */
  199. getData() {
  200. return this.deepCopy(this.cachedData);
  201. }
  202. /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
  203. setData(data) {
  204. this.cachedData = data;
  205. const useEncoding = Boolean(this.encodeData && this.decodeData);
  206. return new Promise((resolve) => __async(this, null, function* () {
  207. yield Promise.all([
  208. GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(data, useEncoding)),
  209. GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
  210. GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
  211. ]);
  212. resolve();
  213. }));
  214. }
  215. /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
  216. saveDefaultData() {
  217. return __async(this, null, function* () {
  218. this.cachedData = this.defaultData;
  219. const useEncoding = Boolean(this.encodeData && this.decodeData);
  220. return new Promise((resolve) => __async(this, null, function* () {
  221. yield Promise.all([
  222. GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
  223. GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
  224. GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
  225. ]);
  226. resolve();
  227. }));
  228. });
  229. }
  230. /**
  231. * Call this method to clear all persistently stored data associated with this DataStore instance.
  232. * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
  233. * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
  234. *
  235. * ⚠️ This requires the additional directive `@grant GM.deleteValue`
  236. */
  237. deleteData() {
  238. return __async(this, null, function* () {
  239. yield Promise.all([
  240. GM.deleteValue(`_uucfg-${this.id}`),
  241. GM.deleteValue(`_uucfgver-${this.id}`),
  242. GM.deleteValue(`_uucfgenc-${this.id}`)
  243. ]);
  244. });
  245. }
  246. /** Runs all necessary migration functions consecutively - may be overwritten in a subclass */
  247. runMigrations(oldData, oldFmtVer) {
  248. return __async(this, null, function* () {
  249. if (!this.migrations)
  250. return oldData;
  251. let newData = oldData;
  252. const sortedMigrations = Object.entries(this.migrations).sort(([a], [b]) => Number(a) - Number(b));
  253. let lastFmtVer = oldFmtVer;
  254. for (const [fmtVer, migrationFunc] of sortedMigrations) {
  255. const ver = Number(fmtVer);
  256. if (oldFmtVer < this.formatVersion && oldFmtVer < ver) {
  257. try {
  258. const migRes = migrationFunc(newData);
  259. newData = migRes instanceof Promise ? yield migRes : migRes;
  260. lastFmtVer = oldFmtVer = ver;
  261. } catch (err) {
  262. console.error(`Error while running migration function for format version '${fmtVer}' - resetting to the default value.`, err);
  263. yield this.saveDefaultData();
  264. return this.getData();
  265. }
  266. }
  267. }
  268. yield Promise.all([
  269. GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(newData)),
  270. GM.setValue(`_uucfgver-${this.id}`, lastFmtVer),
  271. GM.setValue(`_uucfgenc-${this.id}`, Boolean(this.encodeData && this.decodeData))
  272. ]);
  273. return newData;
  274. });
  275. }
  276. /** Serializes the data using the optional this.encodeData() and returns it as a string */
  277. serializeData(data, useEncoding = true) {
  278. return __async(this, null, function* () {
  279. const stringData = JSON.stringify(data);
  280. if (!this.encodeData || !this.decodeData || !useEncoding)
  281. return stringData;
  282. const encRes = this.encodeData(stringData);
  283. if (encRes instanceof Promise)
  284. return yield encRes;
  285. return encRes;
  286. });
  287. }
  288. /** Deserializes the data using the optional this.decodeData() and returns it as a JSON object */
  289. deserializeData(data, useEncoding = true) {
  290. return __async(this, null, function* () {
  291. let decRes = this.decodeData && this.encodeData && useEncoding ? this.decodeData(data) : void 0;
  292. if (decRes instanceof Promise)
  293. decRes = yield decRes;
  294. return JSON.parse(decRes != null ? decRes : data);
  295. });
  296. }
  297. /** Copies a JSON-compatible object and loses its internal references */
  298. deepCopy(obj) {
  299. return JSON.parse(JSON.stringify(obj));
  300. }
  301. };
  302.  
  303. // lib/dom.ts
  304. function getUnsafeWindow() {
  305. try {
  306. return unsafeWindow;
  307. } catch (e) {
  308. return window;
  309. }
  310. }
  311. function insertAfter(beforeElement, afterElement) {
  312. var _a;
  313. (_a = beforeElement.parentNode) == null ? void 0 : _a.insertBefore(afterElement, beforeElement.nextSibling);
  314. return afterElement;
  315. }
  316. function addParent(element, newParent) {
  317. const oldParent = element.parentNode;
  318. if (!oldParent)
  319. throw new Error("Element doesn't have a parent node");
  320. oldParent.replaceChild(newParent, element);
  321. newParent.appendChild(element);
  322. return newParent;
  323. }
  324. function addGlobalStyle(style) {
  325. const styleElem = document.createElement("style");
  326. styleElem.innerHTML = style;
  327. document.head.appendChild(styleElem);
  328. return styleElem;
  329. }
  330. function preloadImages(srcUrls, rejects = false) {
  331. const promises = srcUrls.map((src) => new Promise((res, rej) => {
  332. const image = new Image();
  333. image.src = src;
  334. image.addEventListener("load", () => res(image));
  335. image.addEventListener("error", (evt) => rejects && rej(evt));
  336. }));
  337. return Promise.allSettled(promises);
  338. }
  339. function openInNewTab(href, background) {
  340. try {
  341. GM.openInTab(href, background);
  342. } catch (e) {
  343. const openElem = document.createElement("a");
  344. Object.assign(openElem, {
  345. className: "userutils-open-in-new-tab",
  346. target: "_blank",
  347. rel: "noopener noreferrer",
  348. href
  349. });
  350. openElem.style.display = "none";
  351. document.body.appendChild(openElem);
  352. openElem.click();
  353. setTimeout(openElem.remove, 50);
  354. }
  355. }
  356. function interceptEvent(eventObject, eventName, predicate = () => true) {
  357. Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
  358. if (isNaN(Error.stackTraceLimit))
  359. Error.stackTraceLimit = 100;
  360. (function(original) {
  361. eventObject.__proto__.addEventListener = function(...args) {
  362. var _a, _b;
  363. const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a = args[1]) == null ? void 0 : _a.handleEvent) != null ? _b : () => void 0;
  364. args[1] = function(...a) {
  365. if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a))
  366. return;
  367. else
  368. return origListener.apply(this, a);
  369. };
  370. original.apply(this, args);
  371. };
  372. })(eventObject.__proto__.addEventListener);
  373. }
  374. function interceptWindowEvent(eventName, predicate = () => true) {
  375. return interceptEvent(getUnsafeWindow(), eventName, predicate);
  376. }
  377. function isScrollable(element) {
  378. const { overflowX, overflowY } = getComputedStyle(element);
  379. return {
  380. vertical: (overflowY === "scroll" || overflowY === "auto") && element.scrollHeight > element.clientHeight,
  381. horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth
  382. };
  383. }
  384. function observeElementProp(element, property, callback) {
  385. const elementPrototype = Object.getPrototypeOf(element);
  386. if (elementPrototype.hasOwnProperty(property)) {
  387. const descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
  388. Object.defineProperty(element, property, {
  389. get: function() {
  390. var _a;
  391. return (_a = descriptor == null ? void 0 : descriptor.get) == null ? void 0 : _a.apply(this, arguments);
  392. },
  393. set: function() {
  394. var _a;
  395. const oldValue = this[property];
  396. (_a = descriptor == null ? void 0 : descriptor.set) == null ? void 0 : _a.apply(this, arguments);
  397. const newValue = this[property];
  398. if (typeof callback === "function") {
  399. callback.bind(this, oldValue, newValue);
  400. }
  401. return newValue;
  402. }
  403. });
  404. }
  405. }
  406. function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
  407. var _a, _b;
  408. const siblings = [...(_b = (_a = refElement.parentNode) == null ? void 0 : _a.childNodes) != null ? _b : []];
  409. const elemSiblIdx = siblings.indexOf(refElement);
  410. if (elemSiblIdx === -1)
  411. throw new Error("Element doesn't have a parent node");
  412. if (refElementAlignment === "top")
  413. return [...siblings.slice(elemSiblIdx + Number(!includeRef), elemSiblIdx + siblingAmount + Number(!includeRef))];
  414. else if (refElementAlignment.startsWith("center-")) {
  415. const halfAmount = (refElementAlignment === "center-bottom" ? Math.ceil : Math.floor)(siblingAmount / 2);
  416. const startIdx = Math.max(0, elemSiblIdx - halfAmount);
  417. const topOffset = Number(refElementAlignment === "center-top" && siblingAmount % 2 === 0 && includeRef);
  418. const btmOffset = Number(refElementAlignment === "center-bottom" && siblingAmount % 2 !== 0 && includeRef);
  419. const startIdxWithOffset = startIdx + topOffset + btmOffset;
  420. return [
  421. ...siblings.filter((_, idx) => includeRef || idx !== elemSiblIdx).slice(startIdxWithOffset, startIdxWithOffset + siblingAmount)
  422. ];
  423. } else if (refElementAlignment === "bottom")
  424. return [...siblings.slice(elemSiblIdx - siblingAmount + Number(includeRef), elemSiblIdx + Number(includeRef))];
  425. return [];
  426. }
  427.  
  428. // lib/misc.ts
  429. function autoPlural(word, num) {
  430. if (Array.isArray(num) || num instanceof NodeList)
  431. num = num.length;
  432. return `${word}${num === 1 ? "" : "s"}`;
  433. }
  434. function pauseFor(time) {
  435. return new Promise((res) => {
  436. setTimeout(() => res(), time);
  437. });
  438. }
  439. function debounce(func, timeout = 300, edge = "falling") {
  440. let timer;
  441. return function(...args) {
  442. if (edge === "rising") {
  443. if (!timer) {
  444. func.apply(this, args);
  445. timer = setTimeout(() => timer = void 0, timeout);
  446. }
  447. } else {
  448. clearTimeout(timer);
  449. timer = setTimeout(() => func.apply(this, args), timeout);
  450. }
  451. };
  452. }
  453. function fetchAdvanced(_0) {
  454. return __async(this, arguments, function* (input, options = {}) {
  455. const { timeout = 1e4 } = options;
  456. let signalOpts = {}, id = void 0;
  457. if (timeout >= 0) {
  458. const controller = new AbortController();
  459. id = setTimeout(() => controller.abort(), timeout);
  460. signalOpts = { signal: controller.signal };
  461. }
  462. const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
  463. clearTimeout(id);
  464. return res;
  465. });
  466. }
  467. function insertValues(input, ...values) {
  468. return input.replace(/%\d/gm, (match) => {
  469. var _a, _b;
  470. const argIndex = Number(match.substring(1)) - 1;
  471. return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
  472. });
  473. }
  474. function compress(input, compressionFormat, outputType = "string") {
  475. return __async(this, null, function* () {
  476. const byteArray = typeof input === "string" ? new TextEncoder().encode(input) : input;
  477. const comp = new CompressionStream(compressionFormat);
  478. const writer = comp.writable.getWriter();
  479. writer.write(byteArray);
  480. writer.close();
  481. const buf = yield new Response(comp.readable).arrayBuffer();
  482. return outputType === "arrayBuffer" ? buf : ab2str(buf);
  483. });
  484. }
  485. function decompress(input, compressionFormat, outputType = "string") {
  486. return __async(this, null, function* () {
  487. const byteArray = typeof input === "string" ? str2ab(input) : input;
  488. const decomp = new DecompressionStream(compressionFormat);
  489. const writer = decomp.writable.getWriter();
  490. writer.write(byteArray);
  491. writer.close();
  492. const buf = yield new Response(decomp.readable).arrayBuffer();
  493. return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
  494. });
  495. }
  496. function ab2str(buf) {
  497. return getUnsafeWindow().btoa(
  498. new Uint8Array(buf).reduce((data, byte) => data + String.fromCharCode(byte), "")
  499. );
  500. }
  501. function str2ab(str) {
  502. return Uint8Array.from(getUnsafeWindow().atob(str), (c) => c.charCodeAt(0));
  503. }
  504.  
  505. // lib/SelectorObserver.ts
  506. var SelectorObserver = class {
  507. constructor(baseElement, options = {}) {
  508. __publicField(this, "enabled", false);
  509. __publicField(this, "baseElement");
  510. __publicField(this, "observer");
  511. __publicField(this, "observerOptions");
  512. __publicField(this, "customOptions");
  513. __publicField(this, "listenerMap");
  514. this.baseElement = baseElement;
  515. this.listenerMap = /* @__PURE__ */ new Map();
  516. this.observer = new MutationObserver(() => this.checkAllSelectors());
  517. const _a = options, {
  518. defaultDebounce,
  519. defaultDebounceEdge,
  520. disableOnNoListeners,
  521. enableOnAddListener
  522. } = _a, observerOptions = __objRest(_a, [
  523. "defaultDebounce",
  524. "defaultDebounceEdge",
  525. "disableOnNoListeners",
  526. "enableOnAddListener"
  527. ]);
  528. this.observerOptions = __spreadValues({
  529. childList: true,
  530. subtree: true
  531. }, observerOptions);
  532. this.customOptions = {
  533. defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
  534. defaultDebounceEdge: defaultDebounceEdge != null ? defaultDebounceEdge : "rising",
  535. disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
  536. enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
  537. };
  538. }
  539. checkAllSelectors() {
  540. for (const [selector, listeners] of this.listenerMap.entries())
  541. this.checkSelector(selector, listeners);
  542. }
  543. checkSelector(selector, listeners) {
  544. var _a;
  545. if (!this.enabled)
  546. return;
  547. const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
  548. if (!baseElement)
  549. return;
  550. const all = listeners.some((listener) => listener.all);
  551. const one = listeners.some((listener) => !listener.all);
  552. const allElements = all ? baseElement.querySelectorAll(selector) : null;
  553. const oneElement = one ? baseElement.querySelector(selector) : null;
  554. for (const options of listeners) {
  555. if (options.all) {
  556. if (allElements && allElements.length > 0) {
  557. options.listener(allElements);
  558. if (!options.continuous)
  559. this.removeListener(selector, options);
  560. }
  561. } else {
  562. if (oneElement) {
  563. options.listener(oneElement);
  564. if (!options.continuous)
  565. this.removeListener(selector, options);
  566. }
  567. }
  568. if (((_a = this.listenerMap.get(selector)) == null ? void 0 : _a.length) === 0)
  569. this.listenerMap.delete(selector);
  570. if (this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
  571. this.disable();
  572. }
  573. }
  574. debounce(func, time, edge = "falling") {
  575. return debounce(func, time, edge);
  576. }
  577. /**
  578. * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
  579. * @param selector The selector to observe
  580. * @param options Options for the selector observation
  581. * @param options.listener Gets called whenever the selector was found in the DOM
  582. * @param [options.all] Whether to use `querySelectorAll()` instead - default is false
  583. * @param [options.continuous] Whether to call the listener continuously instead of just once - default is false
  584. * @param [options.debounce] Whether to debounce the listener to reduce calls to `querySelector` or `querySelectorAll` - set undefined or <=0 to disable (default)
  585. */
  586. addListener(selector, options) {
  587. options = __spreadValues({ all: false, continuous: false, debounce: 0 }, options);
  588. if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
  589. options.listener = this.debounce(
  590. options.listener,
  591. options.debounce || this.customOptions.defaultDebounce,
  592. options.debounceEdge || this.customOptions.defaultDebounceEdge
  593. );
  594. }
  595. if (this.listenerMap.has(selector))
  596. this.listenerMap.get(selector).push(options);
  597. else
  598. this.listenerMap.set(selector, [options]);
  599. if (this.enabled === false && this.customOptions.enableOnAddListener)
  600. this.enable();
  601. this.checkSelector(selector, [options]);
  602. }
  603. /** Disables the observation of the child elements */
  604. disable() {
  605. if (!this.enabled)
  606. return;
  607. this.enabled = false;
  608. this.observer.disconnect();
  609. }
  610. /**
  611. * Enables or reenables the observation of the child elements.
  612. * @param immediatelyCheckSelectors Whether to immediately check if all previously registered selectors exist (default is true)
  613. * @returns Returns true when the observation was enabled, false otherwise (e.g. when the base element wasn't found)
  614. */
  615. enable(immediatelyCheckSelectors = true) {
  616. const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
  617. if (this.enabled || !baseElement)
  618. return false;
  619. this.enabled = true;
  620. this.observer.observe(baseElement, this.observerOptions);
  621. if (immediatelyCheckSelectors)
  622. this.checkAllSelectors();
  623. return true;
  624. }
  625. /** Returns whether the observation of the child elements is currently enabled */
  626. isEnabled() {
  627. return this.enabled;
  628. }
  629. /** Removes all listeners that have been registered with {@linkcode addListener()} */
  630. clearListeners() {
  631. this.listenerMap.clear();
  632. }
  633. /**
  634. * Removes all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()}
  635. * @returns Returns true when all listeners for the associated selector were found and removed, false otherwise
  636. */
  637. removeAllListeners(selector) {
  638. return this.listenerMap.delete(selector);
  639. }
  640. /**
  641. * Removes a single listener for the given {@linkcode selector} and {@linkcode options} that has been registered with {@linkcode addListener()}
  642. * @returns Returns true when the listener was found and removed, false otherwise
  643. */
  644. removeListener(selector, options) {
  645. const listeners = this.listenerMap.get(selector);
  646. if (!listeners)
  647. return false;
  648. const index = listeners.indexOf(options);
  649. if (index > -1) {
  650. listeners.splice(index, 1);
  651. return true;
  652. }
  653. return false;
  654. }
  655. /** Returns all listeners that have been registered with {@linkcode addListener()} */
  656. getAllListeners() {
  657. return this.listenerMap;
  658. }
  659. /** Returns all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()} */
  660. getListeners(selector) {
  661. return this.listenerMap.get(selector);
  662. }
  663. };
  664.  
  665. // lib/translation.ts
  666. var trans = {};
  667. var curLang;
  668. function tr(key, ...args) {
  669. var _a;
  670. if (!curLang)
  671. return key;
  672. const trText = (_a = trans[curLang]) == null ? void 0 : _a[key];
  673. if (!trText)
  674. return key;
  675. if (args.length > 0 && trText.match(/%\d/)) {
  676. return insertValues(trText, ...args);
  677. }
  678. return trText;
  679. }
  680. tr.addLanguage = (language, translations) => {
  681. trans[language] = translations;
  682. };
  683. tr.setLanguage = (language) => {
  684. curLang = language;
  685. };
  686. tr.getLanguage = () => {
  687. return curLang;
  688. };
  689.  
  690. exports.DataStore = DataStore;
  691. exports.SelectorObserver = SelectorObserver;
  692. exports.addGlobalStyle = addGlobalStyle;
  693. exports.addParent = addParent;
  694. exports.autoPlural = autoPlural;
  695. exports.clamp = clamp;
  696. exports.compress = compress;
  697. exports.debounce = debounce;
  698. exports.decompress = decompress;
  699. exports.fetchAdvanced = fetchAdvanced;
  700. exports.getSiblingsFrame = getSiblingsFrame;
  701. exports.getUnsafeWindow = getUnsafeWindow;
  702. exports.insertAfter = insertAfter;
  703. exports.insertValues = insertValues;
  704. exports.interceptEvent = interceptEvent;
  705. exports.interceptWindowEvent = interceptWindowEvent;
  706. exports.isScrollable = isScrollable;
  707. exports.mapRange = mapRange;
  708. exports.observeElementProp = observeElementProp;
  709. exports.openInNewTab = openInNewTab;
  710. exports.pauseFor = pauseFor;
  711. exports.preloadImages = preloadImages;
  712. exports.randRange = randRange;
  713. exports.randomId = randomId;
  714. exports.randomItem = randomItem;
  715. exports.randomItemIndex = randomItemIndex;
  716. exports.randomizeArray = randomizeArray;
  717. exports.takeRandomItem = takeRandomItem;
  718. exports.tr = tr;
  719.  
  720. return exports;
  721.  
  722. })({});