UserUtils

General purpose DOM/GreaseMonkey library that allows you to register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and much more

当前为 2025-03-21 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/472956/1557712/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 General purpose DOM/GreaseMonkey library that allows you to register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and much more
  11. // @version 9.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) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  52. var __async = (__this, __arguments, generator) => {
  53. return new Promise((resolve, reject) => {
  54. var fulfilled = (value) => {
  55. try {
  56. step(generator.next(value));
  57. } catch (e) {
  58. reject(e);
  59. }
  60. };
  61. var rejected = (value) => {
  62. try {
  63. step(generator.throw(value));
  64. } catch (e) {
  65. reject(e);
  66. }
  67. };
  68. var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
  69. step((generator = generator.apply(__this, __arguments)).next());
  70. });
  71. };
  72.  
  73. // lib/math.ts
  74. function clamp(value, min, max) {
  75. if (typeof max !== "number") {
  76. max = min;
  77. min = 0;
  78. }
  79. return Math.max(Math.min(value, max), min);
  80. }
  81. function mapRange(value, range1min, range1max, range2min, range2max) {
  82. if (typeof range2min === "undefined" || typeof range2max === "undefined") {
  83. range2max = range1max;
  84. range1max = range1min;
  85. range2min = range1min = 0;
  86. }
  87. if (Number(range1min) === 0 && Number(range2min) === 0)
  88. return value * (range2max / range1max);
  89. return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min;
  90. }
  91. function randRange(...args) {
  92. let min, max, enhancedEntropy = false;
  93. if (typeof args[0] === "number" && typeof args[1] === "number")
  94. [min, max] = args;
  95. else if (typeof args[0] === "number" && typeof args[1] !== "number") {
  96. min = 0;
  97. [max] = args;
  98. } else
  99. throw new TypeError(`Wrong parameter(s) provided - expected (number, boolean|undefined) or (number, number, boolean|undefined) but got (${args.map((a) => typeof a).join(", ")}) instead`);
  100. if (typeof args[2] === "boolean")
  101. enhancedEntropy = args[2];
  102. else if (typeof args[1] === "boolean")
  103. enhancedEntropy = args[1];
  104. min = Number(min);
  105. max = Number(max);
  106. if (isNaN(min) || isNaN(max))
  107. return NaN;
  108. if (min > max)
  109. throw new TypeError(`Parameter "min" can't be bigger than "max"`);
  110. if (enhancedEntropy) {
  111. const uintArr = new Uint8Array(1);
  112. crypto.getRandomValues(uintArr);
  113. return Number(Array.from(
  114. uintArr,
  115. (v) => Math.round(mapRange(v, 0, 255, min, max)).toString(10)
  116. ).join(""));
  117. } else
  118. return Math.floor(Math.random() * (max - min + 1)) + min;
  119. }
  120. function digitCount(num, withDecimals = true) {
  121. num = Number(!["string", "number"].includes(typeof num) ? String(num) : num);
  122. if (typeof num === "number" && isNaN(num))
  123. return NaN;
  124. const [intPart, decPart] = num.toString().split(".");
  125. const intDigits = intPart === "0" ? 1 : Math.floor(Math.log10(Math.abs(Number(intPart))) + 1);
  126. const decDigits = withDecimals && decPart ? decPart.length : 0;
  127. return intDigits + decDigits;
  128. }
  129. function roundFixed(num, fractionDigits) {
  130. const scale = 10 ** fractionDigits;
  131. return Math.round(num * scale) / scale;
  132. }
  133. function bitSetHas(bitSet, checkVal) {
  134. return (bitSet & checkVal) === checkVal;
  135. }
  136.  
  137. // lib/array.ts
  138. function randomItem(array) {
  139. return randomItemIndex(array)[0];
  140. }
  141. function randomItemIndex(array) {
  142. if (array.length === 0)
  143. return [undefined, undefined];
  144. const idx = randRange(array.length - 1);
  145. return [array[idx], idx];
  146. }
  147. function takeRandomItem(arr) {
  148. const [itm, idx] = randomItemIndex(arr);
  149. if (idx === undefined)
  150. return undefined;
  151. arr.splice(idx, 1);
  152. return itm;
  153. }
  154. function randomizeArray(array) {
  155. const retArray = [...array];
  156. if (array.length === 0)
  157. return retArray;
  158. for (let i = retArray.length - 1; i > 0; i--) {
  159. const j = Math.floor(Math.random() * (i + 1));
  160. [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
  161. }
  162. return retArray;
  163. }
  164.  
  165. // lib/colors.ts
  166. function hexToRgb(hex) {
  167. hex = (hex.startsWith("#") ? hex.slice(1) : hex).trim();
  168. const a = hex.length === 8 || hex.length === 4 ? parseInt(hex.slice(-(hex.length / 4)), 16) / (hex.length === 8 ? 255 : 15) : undefined;
  169. if (!isNaN(Number(a)))
  170. hex = hex.slice(0, -(hex.length / 4));
  171. if (hex.length === 3 || hex.length === 4)
  172. hex = hex.split("").map((c) => c + c).join("");
  173. const bigint = parseInt(hex, 16);
  174. const r = bigint >> 16 & 255;
  175. const g = bigint >> 8 & 255;
  176. const b = bigint & 255;
  177. return [clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), typeof a === "number" ? clamp(a, 0, 1) : undefined];
  178. }
  179. function rgbToHex(red, green, blue, alpha, withHash = true, upperCase = false) {
  180. const toHexVal = (n) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")[upperCase ? "toUpperCase" : "toLowerCase"]();
  181. return `${withHash ? "#" : ""}${toHexVal(red)}${toHexVal(green)}${toHexVal(blue)}${alpha ? toHexVal(alpha * 255) : ""}`;
  182. }
  183. function lightenColor(color, percent, upperCase = false) {
  184. return darkenColor(color, percent * -1, upperCase);
  185. }
  186. function darkenColor(color, percent, upperCase = false) {
  187. var _a;
  188. color = color.trim();
  189. const darkenRgb = (r2, g2, b2, percent2) => {
  190. r2 = Math.max(0, Math.min(255, r2 - r2 * percent2 / 100));
  191. g2 = Math.max(0, Math.min(255, g2 - g2 * percent2 / 100));
  192. b2 = Math.max(0, Math.min(255, b2 - b2 * percent2 / 100));
  193. return [r2, g2, b2];
  194. };
  195. let r, g, b, a;
  196. const isHexCol = color.match(/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/);
  197. if (isHexCol)
  198. [r, g, b, a] = hexToRgb(color);
  199. else if (color.startsWith("rgb")) {
  200. const rgbValues = (_a = color.match(/\d+(\.\d+)?/g)) == null ? undefined : _a.map(Number);
  201. if (!rgbValues)
  202. throw new TypeError("Invalid RGB/RGBA color format");
  203. [r, g, b, a] = rgbValues;
  204. } else
  205. throw new TypeError("Unsupported color format");
  206. [r, g, b] = darkenRgb(r, g, b, percent);
  207. if (isHexCol)
  208. return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
  209. else if (color.startsWith("rgba"))
  210. return `rgba(${r}, ${g}, ${b}, ${a != null ? a : NaN})`;
  211. else if (color.startsWith("rgb"))
  212. return `rgb(${r}, ${g}, ${b})`;
  213. else
  214. throw new TypeError("Unsupported color format");
  215. }
  216.  
  217. // lib/errors.ts
  218. var UUError = class extends Error {
  219. constructor(message, options) {
  220. super(message, options);
  221. __publicField(this, "date");
  222. this.date = /* @__PURE__ */ new Date();
  223. }
  224. };
  225. var ChecksumMismatchError = class extends UUError {
  226. constructor(message, options) {
  227. super(message, options);
  228. this.name = "ChecksumMismatchError";
  229. }
  230. };
  231. var MigrationError = class extends UUError {
  232. constructor(message, options) {
  233. super(message, options);
  234. this.name = "MigrationError";
  235. }
  236. };
  237. var PlatformError = class extends UUError {
  238. constructor(message, options) {
  239. super(message, options);
  240. this.name = "PlatformError";
  241. }
  242. };
  243.  
  244. // lib/dom.ts
  245. var domReady = false;
  246. document.addEventListener("DOMContentLoaded", () => domReady = true);
  247. function getUnsafeWindow() {
  248. try {
  249. return unsafeWindow;
  250. } catch (e) {
  251. return window;
  252. }
  253. }
  254. function addParent(element, newParent) {
  255. const oldParent = element.parentNode;
  256. if (!oldParent)
  257. throw new Error("Element doesn't have a parent node");
  258. oldParent.replaceChild(newParent, element);
  259. newParent.appendChild(element);
  260. return newParent;
  261. }
  262. function addGlobalStyle(style) {
  263. const styleElem = document.createElement("style");
  264. setInnerHtmlUnsafe(styleElem, style);
  265. document.head.appendChild(styleElem);
  266. return styleElem;
  267. }
  268. function preloadImages(srcUrls, rejects = false) {
  269. const promises = srcUrls.map((src) => new Promise((res, rej) => {
  270. const image = new Image();
  271. image.src = src;
  272. image.addEventListener("load", () => res(image));
  273. image.addEventListener("error", (evt) => rejects && rej(evt));
  274. }));
  275. return Promise.allSettled(promises);
  276. }
  277. function openInNewTab(href, background, additionalProps) {
  278. var _a;
  279. try {
  280. (_a = GM.openInTab) == null ? void 0 : _a.call(GM, href, background);
  281. } catch (e) {
  282. const openElem = document.createElement("a");
  283. Object.assign(openElem, __spreadValues({
  284. className: "userutils-open-in-new-tab",
  285. target: "_blank",
  286. rel: "noopener noreferrer",
  287. tabIndex: -1,
  288. ariaHidden: "true",
  289. href
  290. }, additionalProps));
  291. Object.assign(openElem.style, {
  292. display: "none",
  293. pointerEvents: "none"
  294. });
  295. document.body.appendChild(openElem);
  296. openElem.click();
  297. setTimeout(openElem.remove, 0);
  298. }
  299. }
  300. function interceptEvent(eventObject, eventName, predicate = () => true) {
  301. var _a;
  302. if (((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
  303. throw new PlatformError("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
  304. Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
  305. if (isNaN(Error.stackTraceLimit))
  306. Error.stackTraceLimit = 100;
  307. (function(original) {
  308. eventObject.__proto__.addEventListener = function(...args) {
  309. var _a2, _b;
  310. const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a2 = args[1]) == null ? undefined : _a2.handleEvent) != null ? _b : () => undefined;
  311. args[1] = function(...a) {
  312. if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a))
  313. return;
  314. else
  315. return origListener.apply(this, a);
  316. };
  317. original.apply(this, args);
  318. };
  319. })(eventObject.__proto__.addEventListener);
  320. }
  321. function interceptWindowEvent(eventName, predicate = () => true) {
  322. return interceptEvent(getUnsafeWindow(), eventName, predicate);
  323. }
  324. function isScrollable(element) {
  325. const { overflowX, overflowY } = getComputedStyle(element);
  326. return {
  327. vertical: (overflowY === "scroll" || overflowY === "auto") && element.scrollHeight > element.clientHeight,
  328. horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth
  329. };
  330. }
  331. function observeElementProp(element, property, callback) {
  332. const elementPrototype = Object.getPrototypeOf(element);
  333. if (elementPrototype.hasOwnProperty(property)) {
  334. const descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
  335. Object.defineProperty(element, property, {
  336. get: function() {
  337. var _a;
  338. return (_a = descriptor == null ? undefined : descriptor.get) == null ? undefined : _a.apply(this, arguments);
  339. },
  340. set: function() {
  341. var _a;
  342. const oldValue = this[property];
  343. (_a = descriptor == null ? undefined : descriptor.set) == null ? undefined : _a.apply(this, arguments);
  344. const newValue = this[property];
  345. if (typeof callback === "function") {
  346. callback.bind(this, oldValue, newValue);
  347. }
  348. return newValue;
  349. }
  350. });
  351. }
  352. }
  353. function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
  354. var _a, _b;
  355. const siblings = [...(_b = (_a = refElement.parentNode) == null ? undefined : _a.childNodes) != null ? _b : []];
  356. const elemSiblIdx = siblings.indexOf(refElement);
  357. if (elemSiblIdx === -1)
  358. throw new Error("Element doesn't have a parent node");
  359. if (refElementAlignment === "top")
  360. return [...siblings.slice(elemSiblIdx + Number(!includeRef), elemSiblIdx + siblingAmount + Number(!includeRef))];
  361. else if (refElementAlignment.startsWith("center-")) {
  362. const halfAmount = (refElementAlignment === "center-bottom" ? Math.ceil : Math.floor)(siblingAmount / 2);
  363. const startIdx = Math.max(0, elemSiblIdx - halfAmount);
  364. const topOffset = Number(refElementAlignment === "center-top" && siblingAmount % 2 === 0 && includeRef);
  365. const btmOffset = Number(refElementAlignment === "center-bottom" && siblingAmount % 2 !== 0 && includeRef);
  366. const startIdxWithOffset = startIdx + topOffset + btmOffset;
  367. return [
  368. ...siblings.filter((_, idx) => includeRef || idx !== elemSiblIdx).slice(startIdxWithOffset, startIdxWithOffset + siblingAmount)
  369. ];
  370. } else if (refElementAlignment === "bottom")
  371. return [...siblings.slice(elemSiblIdx - siblingAmount + Number(includeRef), elemSiblIdx + Number(includeRef))];
  372. return [];
  373. }
  374. var ttPolicy;
  375. function setInnerHtmlUnsafe(element, html) {
  376. var _a, _b, _c;
  377. if (!ttPolicy && typeof ((_a = window == null ? undefined : window.trustedTypes) == null ? undefined : _a.createPolicy) === "function") {
  378. ttPolicy = window.trustedTypes.createPolicy("_uu_set_innerhtml_unsafe", {
  379. createHTML: (unsafeHtml) => unsafeHtml
  380. });
  381. }
  382. element.innerHTML = (_c = (_b = ttPolicy == null ? undefined : ttPolicy.createHTML) == null ? undefined : _b.call(ttPolicy, html)) != null ? _c : html;
  383. return element;
  384. }
  385. function probeElementStyle(probeStyle, element, hideOffscreen = true, parentElement = document.body) {
  386. const el = element ? typeof element === "function" ? element() : element : document.createElement("span");
  387. if (hideOffscreen) {
  388. el.style.position = "absolute";
  389. el.style.left = "-9999px";
  390. el.style.top = "-9999px";
  391. el.style.zIndex = "-9999";
  392. }
  393. el.classList.add("_uu_probe_element");
  394. parentElement.appendChild(el);
  395. const style = window.getComputedStyle(el);
  396. const result = probeStyle(style, el);
  397. setTimeout(() => el.remove(), 1);
  398. return result;
  399. }
  400. function isDomLoaded() {
  401. return domReady;
  402. }
  403. function onDomLoad(cb) {
  404. return new Promise((res) => {
  405. if (domReady) {
  406. cb == null ? undefined : cb();
  407. res();
  408. } else
  409. document.addEventListener("DOMContentLoaded", () => {
  410. cb == null ? undefined : cb();
  411. res();
  412. });
  413. });
  414. }
  415.  
  416. // lib/crypto.ts
  417. function compress(input, compressionFormat, outputType = "string") {
  418. return __async(this, null, function* () {
  419. const byteArray = typeof input === "string" ? new TextEncoder().encode(input) : input;
  420. const comp = new CompressionStream(compressionFormat);
  421. const writer = comp.writable.getWriter();
  422. writer.write(byteArray);
  423. writer.close();
  424. const buf = yield new Response(comp.readable).arrayBuffer();
  425. return outputType === "arrayBuffer" ? buf : ab2str(buf);
  426. });
  427. }
  428. function decompress(input, compressionFormat, outputType = "string") {
  429. return __async(this, null, function* () {
  430. const byteArray = typeof input === "string" ? str2ab(input) : input;
  431. const decomp = new DecompressionStream(compressionFormat);
  432. const writer = decomp.writable.getWriter();
  433. writer.write(byteArray);
  434. writer.close();
  435. const buf = yield new Response(decomp.readable).arrayBuffer();
  436. return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
  437. });
  438. }
  439. function ab2str(buf) {
  440. return getUnsafeWindow().btoa(
  441. new Uint8Array(buf).reduce((data, byte) => data + String.fromCharCode(byte), "")
  442. );
  443. }
  444. function str2ab(str) {
  445. return Uint8Array.from(getUnsafeWindow().atob(str), (c) => c.charCodeAt(0));
  446. }
  447. function computeHash(input, algorithm = "SHA-256") {
  448. return __async(this, null, function* () {
  449. let data;
  450. if (typeof input === "string") {
  451. const encoder = new TextEncoder();
  452. data = encoder.encode(input);
  453. } else
  454. data = input;
  455. const hashBuffer = yield crypto.subtle.digest(algorithm, data);
  456. const hashArray = Array.from(new Uint8Array(hashBuffer));
  457. const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
  458. return hashHex;
  459. });
  460. }
  461. function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase = true) {
  462. if (radix < 2 || radix > 36)
  463. throw new RangeError("The radix argument must be between 2 and 36");
  464. let arr = [];
  465. const caseArr = randomCase ? [0, 1] : [0];
  466. if (enhancedEntropy) {
  467. const uintArr = new Uint8Array(length);
  468. crypto.getRandomValues(uintArr);
  469. arr = Array.from(
  470. uintArr,
  471. (v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1)
  472. );
  473. } else {
  474. arr = Array.from(
  475. { length },
  476. () => Math.floor(Math.random() * radix).toString(radix)
  477. );
  478. }
  479. if (!arr.some((v) => /[a-zA-Z]/.test(v)))
  480. return arr.join("");
  481. return arr.map((v) => caseArr[randRange(0, caseArr.length - 1, enhancedEntropy)] === 1 ? v.toUpperCase() : v).join("");
  482. }
  483.  
  484. // lib/DataStore.ts
  485. var DataStore = class {
  486. /**
  487. * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
  488. * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
  489. *
  490. * - ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue` if the storageMethod is left as the default of `"GM"`
  491. * - ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
  492. *
  493. * @template TData The type of the data that is saved in persistent storage for the currently set format version (will be automatically inferred from `defaultData` if not provided) - **This has to be a JSON-compatible object!** (no undefined, circular references, etc.)
  494. * @param options The options for this DataStore instance
  495. */
  496. constructor(options) {
  497. __publicField(this, "id");
  498. __publicField(this, "formatVersion");
  499. __publicField(this, "defaultData");
  500. __publicField(this, "encodeData");
  501. __publicField(this, "decodeData");
  502. __publicField(this, "storageMethod");
  503. __publicField(this, "cachedData");
  504. __publicField(this, "migrations");
  505. __publicField(this, "migrateIds", []);
  506. var _a;
  507. this.id = options.id;
  508. this.formatVersion = options.formatVersion;
  509. this.defaultData = options.defaultData;
  510. this.cachedData = options.defaultData;
  511. this.migrations = options.migrations;
  512. if (options.migrateIds)
  513. this.migrateIds = Array.isArray(options.migrateIds) ? options.migrateIds : [options.migrateIds];
  514. this.storageMethod = (_a = options.storageMethod) != null ? _a : "GM";
  515. this.encodeData = options.encodeData;
  516. this.decodeData = options.decodeData;
  517. }
  518. //#region public
  519. /**
  520. * Loads the data saved in persistent storage into the in-memory cache and also returns it.
  521. * Automatically populates persistent storage with default data if it doesn't contain any data yet.
  522. * Also runs all necessary migration functions if the data format has changed since the last time the data was saved.
  523. */
  524. loadData() {
  525. return __async(this, null, function* () {
  526. try {
  527. if (this.migrateIds.length > 0) {
  528. yield this.migrateId(this.migrateIds);
  529. this.migrateIds = [];
  530. }
  531. const gmData = yield this.getValue(`_uucfg-${this.id}`, JSON.stringify(this.defaultData));
  532. let gmFmtVer = Number(yield this.getValue(`_uucfgver-${this.id}`, NaN));
  533. if (typeof gmData !== "string") {
  534. yield this.saveDefaultData();
  535. return __spreadValues({}, this.defaultData);
  536. }
  537. const isEncoded = Boolean(yield this.getValue(`_uucfgenc-${this.id}`, false));
  538. let saveData = false;
  539. if (isNaN(gmFmtVer)) {
  540. yield this.setValue(`_uucfgver-${this.id}`, gmFmtVer = this.formatVersion);
  541. saveData = true;
  542. }
  543. let parsed = yield this.deserializeData(gmData, isEncoded);
  544. if (gmFmtVer < this.formatVersion && this.migrations)
  545. parsed = yield this.runMigrations(parsed, gmFmtVer);
  546. if (saveData)
  547. yield this.setData(parsed);
  548. this.cachedData = __spreadValues({}, parsed);
  549. return this.cachedData;
  550. } catch (err) {
  551. console.warn("Error while parsing JSON data, resetting it to the default value.", err);
  552. yield this.saveDefaultData();
  553. return this.defaultData;
  554. }
  555. });
  556. }
  557. /**
  558. * Returns a copy of the data from the in-memory cache.
  559. * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
  560. * @param deepCopy Whether to return a deep copy of the data (default: `false`) - only necessary if your data object is nested and may have a bigger performance impact if enabled
  561. */
  562. getData(deepCopy = false) {
  563. return deepCopy ? this.deepCopy(this.cachedData) : __spreadValues({}, this.cachedData);
  564. }
  565. /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
  566. setData(data) {
  567. this.cachedData = data;
  568. const useEncoding = this.encodingEnabled();
  569. return new Promise((resolve) => __async(this, null, function* () {
  570. yield Promise.all([
  571. this.setValue(`_uucfg-${this.id}`, yield this.serializeData(data, useEncoding)),
  572. this.setValue(`_uucfgver-${this.id}`, this.formatVersion),
  573. this.setValue(`_uucfgenc-${this.id}`, useEncoding)
  574. ]);
  575. resolve();
  576. }));
  577. }
  578. /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
  579. saveDefaultData() {
  580. return __async(this, null, function* () {
  581. this.cachedData = this.defaultData;
  582. const useEncoding = this.encodingEnabled();
  583. return new Promise((resolve) => __async(this, null, function* () {
  584. yield Promise.all([
  585. this.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
  586. this.setValue(`_uucfgver-${this.id}`, this.formatVersion),
  587. this.setValue(`_uucfgenc-${this.id}`, useEncoding)
  588. ]);
  589. resolve();
  590. }));
  591. });
  592. }
  593. /**
  594. * Call this method to clear all persistently stored data associated with this DataStore instance.
  595. * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
  596. * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
  597. *
  598. * - ⚠️ This requires the additional directive `@grant GM.deleteValue` if the storageMethod is left as the default of `"GM"`
  599. */
  600. deleteData() {
  601. return __async(this, null, function* () {
  602. yield Promise.all([
  603. this.deleteValue(`_uucfg-${this.id}`),
  604. this.deleteValue(`_uucfgver-${this.id}`),
  605. this.deleteValue(`_uucfgenc-${this.id}`)
  606. ]);
  607. });
  608. }
  609. /** Returns whether encoding and decoding are enabled for this DataStore instance */
  610. encodingEnabled() {
  611. return Boolean(this.encodeData && this.decodeData);
  612. }
  613. //#region migrations
  614. /**
  615. * Runs all necessary migration functions consecutively and saves the result to the in-memory cache and persistent storage and also returns it.
  616. * This method is automatically called by {@linkcode loadData()} if the data format has changed since the last time the data was saved.
  617. * Though calling this method manually is not necessary, it can be useful if you want to run migrations for special occasions like a user importing potentially outdated data that has been previously exported.
  618. *
  619. * If one of the migrations fails, the data will be reset to the default value if `resetOnError` is set to `true` (default). Otherwise, an error will be thrown and no data will be saved.
  620. */
  621. runMigrations(oldData, oldFmtVer, resetOnError = true) {
  622. return __async(this, null, function* () {
  623. if (!this.migrations)
  624. return oldData;
  625. let newData = oldData;
  626. const sortedMigrations = Object.entries(this.migrations).sort(([a], [b]) => Number(a) - Number(b));
  627. let lastFmtVer = oldFmtVer;
  628. for (const [fmtVer, migrationFunc] of sortedMigrations) {
  629. const ver = Number(fmtVer);
  630. if (oldFmtVer < this.formatVersion && oldFmtVer < ver) {
  631. try {
  632. const migRes = migrationFunc(newData);
  633. newData = migRes instanceof Promise ? yield migRes : migRes;
  634. lastFmtVer = oldFmtVer = ver;
  635. } catch (err) {
  636. if (!resetOnError)
  637. throw new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
  638. yield this.saveDefaultData();
  639. return this.getData();
  640. }
  641. }
  642. }
  643. yield Promise.all([
  644. this.setValue(`_uucfg-${this.id}`, yield this.serializeData(newData)),
  645. this.setValue(`_uucfgver-${this.id}`, lastFmtVer),
  646. this.setValue(`_uucfgenc-${this.id}`, this.encodingEnabled())
  647. ]);
  648. return this.cachedData = __spreadValues({}, newData);
  649. });
  650. }
  651. /**
  652. * Tries to migrate the currently saved persistent data from one or more old IDs to the ID set in the constructor.
  653. * If no data exist for the old ID(s), nothing will be done, but some time may still pass trying to fetch the non-existent data.
  654. */
  655. migrateId(oldIds) {
  656. return __async(this, null, function* () {
  657. const ids = Array.isArray(oldIds) ? oldIds : [oldIds];
  658. yield Promise.all(ids.map((id) => __async(this, null, function* () {
  659. const data = yield this.getValue(`_uucfg-${id}`, JSON.stringify(this.defaultData));
  660. const fmtVer = Number(yield this.getValue(`_uucfgver-${id}`, NaN));
  661. const isEncoded = Boolean(yield this.getValue(`_uucfgenc-${id}`, false));
  662. if (data === undefined || isNaN(fmtVer))
  663. return;
  664. const parsed = yield this.deserializeData(data, isEncoded);
  665. yield Promise.allSettled([
  666. this.setValue(`_uucfg-${this.id}`, yield this.serializeData(parsed)),
  667. this.setValue(`_uucfgver-${this.id}`, fmtVer),
  668. this.setValue(`_uucfgenc-${this.id}`, isEncoded),
  669. this.deleteValue(`_uucfg-${id}`),
  670. this.deleteValue(`_uucfgver-${id}`),
  671. this.deleteValue(`_uucfgenc-${id}`)
  672. ]);
  673. })));
  674. });
  675. }
  676. //#region serialization
  677. /** Serializes the data using the optional this.encodeData() and returns it as a string */
  678. serializeData(data, useEncoding = true) {
  679. return __async(this, null, function* () {
  680. const stringData = JSON.stringify(data);
  681. if (!this.encodingEnabled() || !useEncoding)
  682. return stringData;
  683. const encRes = this.encodeData(stringData);
  684. if (encRes instanceof Promise)
  685. return yield encRes;
  686. return encRes;
  687. });
  688. }
  689. /** Deserializes the data using the optional this.decodeData() and returns it as a JSON object */
  690. deserializeData(data, useEncoding = true) {
  691. return __async(this, null, function* () {
  692. let decRes = this.encodingEnabled() && useEncoding ? this.decodeData(data) : undefined;
  693. if (decRes instanceof Promise)
  694. decRes = yield decRes;
  695. return JSON.parse(decRes != null ? decRes : data);
  696. });
  697. }
  698. /** Copies a JSON-compatible object and loses all its internal references in the process */
  699. deepCopy(obj) {
  700. return JSON.parse(JSON.stringify(obj));
  701. }
  702. //#region storage
  703. /** Gets a value from persistent storage - can be overwritten in a subclass if you want to use something other than the default storage methods */
  704. getValue(name, defaultValue) {
  705. return __async(this, null, function* () {
  706. var _a, _b;
  707. switch (this.storageMethod) {
  708. case "localStorage":
  709. return (_a = localStorage.getItem(name)) != null ? _a : defaultValue;
  710. case "sessionStorage":
  711. return (_b = sessionStorage.getItem(name)) != null ? _b : defaultValue;
  712. default:
  713. return GM.getValue(name, defaultValue);
  714. }
  715. });
  716. }
  717. /**
  718. * Sets a value in persistent storage - can be overwritten in a subclass if you want to use something other than the default storage methods.
  719. * The default storage engines will stringify all passed values like numbers or booleans, so be aware of that.
  720. */
  721. setValue(name, value) {
  722. return __async(this, null, function* () {
  723. switch (this.storageMethod) {
  724. case "localStorage":
  725. return localStorage.setItem(name, String(value));
  726. case "sessionStorage":
  727. return sessionStorage.setItem(name, String(value));
  728. default:
  729. return GM.setValue(name, String(value));
  730. }
  731. });
  732. }
  733. /** Deletes a value from persistent storage - can be overwritten in a subclass if you want to use something other than the default storage methods */
  734. deleteValue(name) {
  735. return __async(this, null, function* () {
  736. switch (this.storageMethod) {
  737. case "localStorage":
  738. return localStorage.removeItem(name);
  739. case "sessionStorage":
  740. return sessionStorage.removeItem(name);
  741. default:
  742. return GM.deleteValue(name);
  743. }
  744. });
  745. }
  746. };
  747.  
  748. // lib/DataStoreSerializer.ts
  749. var DataStoreSerializer = class _DataStoreSerializer {
  750. constructor(stores, options = {}) {
  751. __publicField(this, "stores");
  752. __publicField(this, "options");
  753. if (!getUnsafeWindow().crypto || !getUnsafeWindow().crypto.subtle)
  754. throw new Error("DataStoreSerializer has to run in a secure context (HTTPS)!");
  755. this.stores = stores;
  756. this.options = __spreadValues({
  757. addChecksum: true,
  758. ensureIntegrity: true
  759. }, options);
  760. }
  761. /** Calculates the checksum of a string */
  762. calcChecksum(input) {
  763. return __async(this, null, function* () {
  764. return computeHash(input, "SHA-256");
  765. });
  766. }
  767. /**
  768. * Serializes only a subset of the data stores into a string.
  769. * @param stores An array of store IDs or functions that take a store ID and return a boolean
  770. * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
  771. * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
  772. */
  773. serializePartial(stores, useEncoding = true, stringified = true) {
  774. return __async(this, null, function* () {
  775. const serData = [];
  776. for (const storeInst of this.stores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
  777. const data = useEncoding && storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
  778. serData.push({
  779. id: storeInst.id,
  780. data,
  781. formatVersion: storeInst.formatVersion,
  782. encoded: useEncoding && storeInst.encodingEnabled(),
  783. checksum: this.options.addChecksum ? yield this.calcChecksum(data) : undefined
  784. });
  785. }
  786. return stringified ? JSON.stringify(serData) : serData;
  787. });
  788. }
  789. /**
  790. * Serializes the data stores into a string.
  791. * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
  792. * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
  793. */
  794. serialize(useEncoding = true, stringified = true) {
  795. return __async(this, null, function* () {
  796. return this.serializePartial(this.stores.map((s) => s.id), useEncoding, stringified);
  797. });
  798. }
  799. /**
  800. * Deserializes the data exported via {@linkcode serialize()} and imports only a subset into the DataStore instances.
  801. * Also triggers the migration process if the data format has changed.
  802. */
  803. deserializePartial(stores, data) {
  804. return __async(this, null, function* () {
  805. const deserStores = typeof data === "string" ? JSON.parse(data) : data;
  806. if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStore))
  807. throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
  808. for (const storeData of deserStores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
  809. const storeInst = this.stores.find((s) => s.id === storeData.id);
  810. if (!storeInst)
  811. throw new Error(`DataStore instance with ID "${storeData.id}" not found! Make sure to provide it in the DataStoreSerializer constructor.`);
  812. if (this.options.ensureIntegrity && typeof storeData.checksum === "string") {
  813. const checksum = yield this.calcChecksum(storeData.data);
  814. if (checksum !== storeData.checksum)
  815. throw new ChecksumMismatchError(`Checksum mismatch for DataStore with ID "${storeData.id}"!
  816. Expected: ${storeData.checksum}
  817. Has: ${checksum}`);
  818. }
  819. const decodedData = storeData.encoded && storeInst.encodingEnabled() ? yield storeInst.decodeData(storeData.data) : storeData.data;
  820. if (storeData.formatVersion && !isNaN(Number(storeData.formatVersion)) && Number(storeData.formatVersion) < storeInst.formatVersion)
  821. yield storeInst.runMigrations(JSON.parse(decodedData), Number(storeData.formatVersion), false);
  822. else
  823. yield storeInst.setData(JSON.parse(decodedData));
  824. }
  825. });
  826. }
  827. /**
  828. * Deserializes the data exported via {@linkcode serialize()} and imports the data into all matching DataStore instances.
  829. * Also triggers the migration process if the data format has changed.
  830. */
  831. deserialize(data) {
  832. return __async(this, null, function* () {
  833. return this.deserializePartial(this.stores.map((s) => s.id), data);
  834. });
  835. }
  836. /**
  837. * Loads the persistent data of the DataStore instances into the in-memory cache.
  838. * Also triggers the migration process if the data format has changed.
  839. * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
  840. */
  841. loadStoresData() {
  842. return __async(this, null, function* () {
  843. return Promise.allSettled(this.stores.map(
  844. (store) => __async(this, null, function* () {
  845. return {
  846. id: store.id,
  847. data: yield store.loadData()
  848. };
  849. })
  850. ));
  851. });
  852. }
  853. /** Resets the persistent data of the DataStore instances to their default values. */
  854. resetStoresData() {
  855. return __async(this, null, function* () {
  856. return Promise.allSettled(this.stores.map((store) => store.saveDefaultData()));
  857. });
  858. }
  859. /**
  860. * Deletes the persistent data of the DataStore instances.
  861. * Leaves the in-memory data untouched.
  862. */
  863. deleteStoresData() {
  864. return __async(this, null, function* () {
  865. return Promise.allSettled(this.stores.map((store) => store.deleteData()));
  866. });
  867. }
  868. /** Checks if a given value is a SerializedDataStore object */
  869. static isSerializedDataStore(obj) {
  870. return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
  871. }
  872. };
  873.  
  874. // node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
  875. var createNanoEvents = () => ({
  876. emit(event, ...args) {
  877. for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
  878. callbacks[i](...args);
  879. }
  880. },
  881. events: {},
  882. on(event, cb) {
  883. var _a;
  884. ((_a = this.events)[event] || (_a[event] = [])).push(cb);
  885. return () => {
  886. var _a2;
  887. this.events[event] = (_a2 = this.events[event]) == null ? undefined : _a2.filter((i) => cb !== i);
  888. };
  889. }
  890. });
  891.  
  892. // lib/NanoEmitter.ts
  893. var NanoEmitter = class {
  894. constructor(options = {}) {
  895. __publicField(this, "events", createNanoEvents());
  896. __publicField(this, "eventUnsubscribes", []);
  897. __publicField(this, "emitterOptions");
  898. this.emitterOptions = __spreadValues({
  899. publicEmit: false
  900. }, options);
  901. }
  902. /** Subscribes to an event - returns a function that unsubscribes the event listener */
  903. on(event, cb) {
  904. let unsub;
  905. const unsubProxy = () => {
  906. if (!unsub)
  907. return;
  908. unsub();
  909. this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
  910. };
  911. unsub = this.events.on(event, cb);
  912. this.eventUnsubscribes.push(unsub);
  913. return unsubProxy;
  914. }
  915. /** Subscribes to an event and calls the callback or resolves the Promise only once */
  916. once(event, cb) {
  917. return new Promise((resolve) => {
  918. let unsub;
  919. const onceProxy = (...args) => {
  920. unsub();
  921. cb == null ? undefined : cb(...args);
  922. resolve(args);
  923. };
  924. unsub = this.on(event, onceProxy);
  925. });
  926. }
  927. /** Emits an event on this instance - Needs `publicEmit` to be set to true in the constructor! */
  928. emit(event, ...args) {
  929. if (this.emitterOptions.publicEmit) {
  930. this.events.emit(event, ...args);
  931. return true;
  932. }
  933. return false;
  934. }
  935. /** Unsubscribes all event listeners */
  936. unsubscribeAll() {
  937. for (const unsub of this.eventUnsubscribes)
  938. unsub();
  939. this.eventUnsubscribes = [];
  940. }
  941. };
  942.  
  943. // lib/Debouncer.ts
  944. var Debouncer = class extends NanoEmitter {
  945. /**
  946. * Creates a new debouncer with the specified timeout and edge type.
  947. * @param timeout Timeout in milliseconds between letting through calls - defaults to 200
  948. * @param type The edge type to use for the debouncer - see {@linkcode DebouncerType} for details or [the documentation for an explanation and diagram](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#debouncer) - defaults to "immediate"
  949. */
  950. constructor(timeout = 200, type = "immediate") {
  951. super();
  952. this.timeout = timeout;
  953. this.type = type;
  954. /** All registered listener functions and the time they were attached */
  955. __publicField(this, "listeners", []);
  956. /** The currently active timeout */
  957. __publicField(this, "activeTimeout");
  958. /** The latest queued call */
  959. __publicField(this, "queuedCall");
  960. }
  961. //#region listeners
  962. /** Adds a listener function that will be called on timeout */
  963. addListener(fn) {
  964. this.listeners.push(fn);
  965. }
  966. /** Removes the listener with the specified function reference */
  967. removeListener(fn) {
  968. const idx = this.listeners.findIndex((l) => l === fn);
  969. idx !== -1 && this.listeners.splice(idx, 1);
  970. }
  971. /** Removes all listeners */
  972. removeAllListeners() {
  973. this.listeners = [];
  974. }
  975. //#region timeout
  976. /** Sets the timeout for the debouncer */
  977. setTimeout(timeout) {
  978. this.emit("change", this.timeout = timeout, this.type);
  979. }
  980. /** Returns the current timeout */
  981. getTimeout() {
  982. return this.timeout;
  983. }
  984. /** Whether the timeout is currently active, meaning any latest call to the {@linkcode call()} method will be queued */
  985. isTimeoutActive() {
  986. return typeof this.activeTimeout !== "undefined";
  987. }
  988. //#region type
  989. /** Sets the edge type for the debouncer */
  990. setType(type) {
  991. this.emit("change", this.timeout, this.type = type);
  992. }
  993. /** Returns the current edge type */
  994. getType() {
  995. return this.type;
  996. }
  997. //#region call
  998. /** Use this to call the debouncer with the specified arguments that will be passed to all listener functions registered with {@linkcode addListener()} */
  999. call(...args) {
  1000. const cl = (...a) => {
  1001. this.queuedCall = undefined;
  1002. this.emit("call", ...a);
  1003. this.listeners.forEach((l) => l.apply(this, a));
  1004. };
  1005. const setRepeatTimeout = () => {
  1006. this.activeTimeout = setTimeout(() => {
  1007. if (this.queuedCall) {
  1008. this.queuedCall();
  1009. setRepeatTimeout();
  1010. } else
  1011. this.activeTimeout = undefined;
  1012. }, this.timeout);
  1013. };
  1014. switch (this.type) {
  1015. case "immediate":
  1016. if (typeof this.activeTimeout === "undefined") {
  1017. cl(...args);
  1018. setRepeatTimeout();
  1019. } else
  1020. this.queuedCall = () => cl(...args);
  1021. break;
  1022. case "idle":
  1023. if (this.activeTimeout)
  1024. clearTimeout(this.activeTimeout);
  1025. this.activeTimeout = setTimeout(() => {
  1026. cl(...args);
  1027. this.activeTimeout = undefined;
  1028. }, this.timeout);
  1029. break;
  1030. default:
  1031. throw new TypeError(`Invalid debouncer type: ${this.type}`);
  1032. }
  1033. }
  1034. };
  1035. function debounce(fn, timeout = 200, type = "immediate") {
  1036. const debouncer = new Debouncer(timeout, type);
  1037. debouncer.addListener(fn);
  1038. const func = (...args) => debouncer.call(...args);
  1039. func.debouncer = debouncer;
  1040. return func;
  1041. }
  1042.  
  1043. // lib/Dialog.ts
  1044. var defaultDialogCss = `.uu-no-select {
  1045. user-select: none;
  1046. }
  1047.  
  1048. .uu-dialog-bg {
  1049. --uu-dialog-bg: #333333;
  1050. --uu-dialog-bg-highlight: #252525;
  1051. --uu-scroll-indicator-bg: rgba(10, 10, 10, 0.7);
  1052. --uu-dialog-separator-color: #797979;
  1053. --uu-dialog-border-radius: 10px;
  1054. }
  1055.  
  1056. .uu-dialog-bg {
  1057. display: block;
  1058. position: fixed;
  1059. width: 100%;
  1060. height: 100%;
  1061. top: 0;
  1062. left: 0;
  1063. z-index: 5;
  1064. background-color: rgba(0, 0, 0, 0.6);
  1065. }
  1066.  
  1067. .uu-dialog {
  1068. --uu-calc-dialog-height: calc(min(100vh - 40px, var(--uu-dialog-height-max)));
  1069. position: absolute;
  1070. display: flex;
  1071. flex-direction: column;
  1072. width: calc(min(100% - 60px, var(--uu-dialog-width-max)));
  1073. border-radius: var(--uu-dialog-border-radius);
  1074. height: auto;
  1075. max-height: var(--uu-calc-dialog-height);
  1076. left: 50%;
  1077. top: 50%;
  1078. transform: translate(-50%, -50%);
  1079. z-index: 6;
  1080. color: #fff;
  1081. background-color: var(--uu-dialog-bg);
  1082. }
  1083.  
  1084. .uu-dialog.align-top {
  1085. top: 0;
  1086. transform: translate(-50%, 40px);
  1087. }
  1088.  
  1089. .uu-dialog.align-bottom {
  1090. top: 100%;
  1091. transform: translate(-50%, -100%);
  1092. }
  1093.  
  1094. .uu-dialog-body {
  1095. font-size: 1.5rem;
  1096. padding: 20px;
  1097. }
  1098.  
  1099. .uu-dialog-body.small {
  1100. padding: 15px;
  1101. }
  1102.  
  1103. #uu-dialog-opts {
  1104. display: flex;
  1105. flex-direction: column;
  1106. position: relative;
  1107. padding: 30px 0px;
  1108. overflow-y: auto;
  1109. }
  1110.  
  1111. .uu-dialog-header {
  1112. display: flex;
  1113. justify-content: space-between;
  1114. align-items: center;
  1115. margin-bottom: 6px;
  1116. padding: 15px 20px 15px 20px;
  1117. background-color: var(--uu-dialog-bg);
  1118. border: 2px solid var(--uu-dialog-separator-color);
  1119. border-style: none none solid none !important;
  1120. border-radius: var(--uu-dialog-border-radius) var(--uu-dialog-border-radius) 0px 0px;
  1121. }
  1122.  
  1123. .uu-dialog-header.small {
  1124. padding: 10px 15px;
  1125. border-style: none none solid none !important;
  1126. }
  1127.  
  1128. .uu-dialog-header-pad {
  1129. content: " ";
  1130. min-height: 32px;
  1131. }
  1132.  
  1133. .uu-dialog-header-pad.small {
  1134. min-height: 24px;
  1135. }
  1136.  
  1137. .uu-dialog-titlecont {
  1138. display: flex;
  1139. align-items: center;
  1140. }
  1141.  
  1142. .uu-dialog-titlecont-no-title {
  1143. display: flex;
  1144. justify-content: flex-end;
  1145. align-items: center;
  1146. }
  1147.  
  1148. .uu-dialog-title {
  1149. position: relative;
  1150. display: inline-block;
  1151. font-size: 22px;
  1152. }
  1153.  
  1154. .uu-dialog-close {
  1155. cursor: pointer;
  1156. }
  1157.  
  1158. .uu-dialog-header-img,
  1159. .uu-dialog-close
  1160. {
  1161. width: 32px;
  1162. height: 32px;
  1163. }
  1164.  
  1165. .uu-dialog-header-img.small,
  1166. .uu-dialog-close.small
  1167. {
  1168. width: 24px;
  1169. height: 24px;
  1170. }
  1171.  
  1172. .uu-dialog-footer {
  1173. font-size: 17px;
  1174. text-decoration: underline;
  1175. }
  1176.  
  1177. .uu-dialog-footer.hidden {
  1178. display: none;
  1179. }
  1180.  
  1181. .uu-dialog-footer-cont {
  1182. margin-top: 6px;
  1183. padding: 15px 20px;
  1184. background: var(--uu-dialog-bg);
  1185. background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, var(--uu-dialog-bg) 30%, var(--uu-dialog-bg) 100%);
  1186. border: 2px solid var(--uu-dialog-separator-color);
  1187. border-style: solid none none none !important;
  1188. border-radius: 0px 0px var(--uu-dialog-border-radius) var(--uu-dialog-border-radius);
  1189. }
  1190.  
  1191. .uu-dialog-footer-buttons-cont button:not(:last-of-type) {
  1192. margin-right: 15px;
  1193. }`;
  1194. exports.currentDialogId = null;
  1195. var openDialogs = [];
  1196. var defaultStrings = {
  1197. closeDialogTooltip: "Click to close the dialog"
  1198. };
  1199. var Dialog = class _Dialog extends NanoEmitter {
  1200. constructor(options) {
  1201. super();
  1202. /** Options passed to the dialog in the constructor */
  1203. __publicField(this, "options");
  1204. /** ID that gets added to child element IDs - has to be unique and conform to HTML ID naming rules! */
  1205. __publicField(this, "id");
  1206. /** Strings used in the dialog (used for translations) */
  1207. __publicField(this, "strings");
  1208. __publicField(this, "dialogOpen", false);
  1209. __publicField(this, "dialogMounted", false);
  1210. const _a = options, { strings } = _a, opts = __objRest(_a, ["strings"]);
  1211. this.strings = __spreadValues(__spreadValues({}, defaultStrings), strings != null ? strings : {});
  1212. this.options = __spreadValues({
  1213. closeOnBgClick: true,
  1214. closeOnEscPress: true,
  1215. destroyOnClose: false,
  1216. unmountOnClose: true,
  1217. removeListenersOnDestroy: true,
  1218. small: false,
  1219. verticalAlign: "center"
  1220. }, opts);
  1221. this.id = opts.id;
  1222. }
  1223. //#region public
  1224. /** Call after DOMContentLoaded to pre-render the dialog and invisibly mount it in the DOM */
  1225. mount() {
  1226. return __async(this, null, function* () {
  1227. var _a;
  1228. if (this.dialogMounted)
  1229. return;
  1230. this.dialogMounted = true;
  1231. if (!document.querySelector("style.uu-dialog-css"))
  1232. addGlobalStyle((_a = this.options.dialogCss) != null ? _a : defaultDialogCss).classList.add("uu-dialog-css");
  1233. const bgElem = document.createElement("div");
  1234. bgElem.id = `uu-${this.id}-dialog-bg`;
  1235. bgElem.classList.add("uu-dialog-bg");
  1236. if (this.options.closeOnBgClick)
  1237. bgElem.ariaLabel = bgElem.title = this.getString("closeDialogTooltip");
  1238. bgElem.style.setProperty("--uu-dialog-width-max", `${this.options.width}px`);
  1239. bgElem.style.setProperty("--uu-dialog-height-max", `${this.options.height}px`);
  1240. bgElem.style.visibility = "hidden";
  1241. bgElem.style.display = "none";
  1242. bgElem.inert = true;
  1243. bgElem.appendChild(yield this.getDialogContent());
  1244. document.body.appendChild(bgElem);
  1245. this.attachListeners(bgElem);
  1246. this.events.emit("render");
  1247. return bgElem;
  1248. });
  1249. }
  1250. /** Closes the dialog and clears all its contents (unmounts elements from the DOM) in preparation for a new rendering call */
  1251. unmount() {
  1252. var _a;
  1253. this.close();
  1254. this.dialogMounted = false;
  1255. const clearSelectors = [
  1256. `#uu-${this.id}-dialog-bg`,
  1257. `#uu-style-dialog-${this.id}`
  1258. ];
  1259. for (const sel of clearSelectors)
  1260. (_a = document.querySelector(sel)) == null ? undefined : _a.remove();
  1261. this.events.emit("clear");
  1262. }
  1263. /** Clears the DOM of the dialog and then renders it again */
  1264. remount() {
  1265. return __async(this, null, function* () {
  1266. this.unmount();
  1267. yield this.mount();
  1268. });
  1269. }
  1270. /**
  1271. * Opens the dialog - also mounts it if it hasn't been mounted yet
  1272. * Prevents default action and immediate propagation of the passed event
  1273. */
  1274. open(e) {
  1275. return __async(this, null, function* () {
  1276. var _a;
  1277. e == null ? undefined : e.preventDefault();
  1278. e == null ? undefined : e.stopImmediatePropagation();
  1279. if (this.isOpen())
  1280. return;
  1281. this.dialogOpen = true;
  1282. if (openDialogs.includes(this.id))
  1283. throw new Error(`A dialog with the same ID of '${this.id}' already exists and is open!`);
  1284. if (!this.isMounted())
  1285. yield this.mount();
  1286. const dialogBg = document.querySelector(`#uu-${this.id}-dialog-bg`);
  1287. if (!dialogBg)
  1288. return console.warn(`Couldn't find background element for dialog with ID '${this.id}'`);
  1289. dialogBg.style.visibility = "visible";
  1290. dialogBg.style.display = "block";
  1291. dialogBg.inert = false;
  1292. exports.currentDialogId = this.id;
  1293. openDialogs.unshift(this.id);
  1294. for (const dialogId of openDialogs)
  1295. if (dialogId !== this.id)
  1296. (_a = document.querySelector(`#uu-${dialogId}-dialog-bg`)) == null ? undefined : _a.setAttribute("inert", "true");
  1297. document.body.classList.remove("uu-no-select");
  1298. document.body.setAttribute("inert", "true");
  1299. this.events.emit("open");
  1300. return dialogBg;
  1301. });
  1302. }
  1303. /** Closes the dialog - prevents default action and immediate propagation of the passed event */
  1304. close(e) {
  1305. var _a, _b;
  1306. e == null ? undefined : e.preventDefault();
  1307. e == null ? undefined : e.stopImmediatePropagation();
  1308. if (!this.isOpen())
  1309. return;
  1310. this.dialogOpen = false;
  1311. const dialogBg = document.querySelector(`#uu-${this.id}-dialog-bg`);
  1312. if (!dialogBg)
  1313. return console.warn(`Couldn't find background element for dialog with ID '${this.id}'`);
  1314. dialogBg.style.visibility = "hidden";
  1315. dialogBg.style.display = "none";
  1316. dialogBg.inert = true;
  1317. openDialogs.splice(openDialogs.indexOf(this.id), 1);
  1318. exports.currentDialogId = (_a = openDialogs[0]) != null ? _a : null;
  1319. if (exports.currentDialogId)
  1320. (_b = document.querySelector(`#uu-${exports.currentDialogId}-dialog-bg`)) == null ? undefined : _b.removeAttribute("inert");
  1321. if (openDialogs.length === 0) {
  1322. document.body.classList.add("uu-no-select");
  1323. document.body.removeAttribute("inert");
  1324. }
  1325. this.events.emit("close");
  1326. if (this.options.destroyOnClose)
  1327. this.destroy();
  1328. else if (this.options.unmountOnClose)
  1329. this.unmount();
  1330. }
  1331. /** Returns true if the dialog is currently open */
  1332. isOpen() {
  1333. return this.dialogOpen;
  1334. }
  1335. /** Returns true if the dialog is currently mounted */
  1336. isMounted() {
  1337. return this.dialogMounted;
  1338. }
  1339. /** Clears the DOM of the dialog and removes all event listeners */
  1340. destroy() {
  1341. this.unmount();
  1342. this.events.emit("destroy");
  1343. this.options.removeListenersOnDestroy && this.unsubscribeAll();
  1344. }
  1345. //#region static
  1346. /** Returns the ID of the top-most dialog (the dialog that has been opened last) */
  1347. static getCurrentDialogId() {
  1348. return exports.currentDialogId;
  1349. }
  1350. /** Returns the IDs of all currently open dialogs, top-most first */
  1351. static getOpenDialogs() {
  1352. return openDialogs;
  1353. }
  1354. //#region protected
  1355. getString(key) {
  1356. var _a;
  1357. return (_a = this.strings[key]) != null ? _a : defaultStrings[key];
  1358. }
  1359. /** Called once to attach all generic event listeners */
  1360. attachListeners(bgElem) {
  1361. if (this.options.closeOnBgClick) {
  1362. bgElem.addEventListener("click", (e) => {
  1363. var _a;
  1364. if (this.isOpen() && ((_a = e.target) == null ? undefined : _a.id) === `uu-${this.id}-dialog-bg`)
  1365. this.close(e);
  1366. });
  1367. }
  1368. if (this.options.closeOnEscPress) {
  1369. document.body.addEventListener("keydown", (e) => {
  1370. if (e.key === "Escape" && this.isOpen() && _Dialog.getCurrentDialogId() === this.id)
  1371. this.close(e);
  1372. });
  1373. }
  1374. }
  1375. //#region protected
  1376. /**
  1377. * Adds generic, accessible interaction listeners to the passed element.
  1378. * All listeners have the default behavior prevented and stop propagation (for keyboard events only as long as the captured key is valid).
  1379. * @param listenerOptions Provide a {@linkcode listenerOptions} object to configure the listeners
  1380. */
  1381. onInteraction(elem, listener, listenerOptions) {
  1382. const _a = listenerOptions != null ? listenerOptions : {}, { preventDefault = true, stopPropagation = true } = _a, listenerOpts = __objRest(_a, ["preventDefault", "stopPropagation"]);
  1383. const interactionKeys = ["Enter", " ", "Space"];
  1384. const proxListener = (e) => {
  1385. if (e instanceof KeyboardEvent) {
  1386. if (interactionKeys.includes(e.key)) {
  1387. preventDefault && e.preventDefault();
  1388. stopPropagation && e.stopPropagation();
  1389. } else return;
  1390. } else if (e instanceof MouseEvent) {
  1391. preventDefault && e.preventDefault();
  1392. stopPropagation && e.stopPropagation();
  1393. }
  1394. (listenerOpts == null ? undefined : listenerOpts.once) && e.type === "keydown" && elem.removeEventListener("click", proxListener, listenerOpts);
  1395. (listenerOpts == null ? undefined : listenerOpts.once) && e.type === "click" && elem.removeEventListener("keydown", proxListener, listenerOpts);
  1396. listener(e);
  1397. };
  1398. elem.addEventListener("click", proxListener, listenerOpts);
  1399. elem.addEventListener("keydown", proxListener, listenerOpts);
  1400. }
  1401. /** Returns the dialog content element and all its children */
  1402. getDialogContent() {
  1403. return __async(this, null, function* () {
  1404. var _a, _b, _c, _d;
  1405. const header = (_b = (_a = this.options).renderHeader) == null ? undefined : _b.call(_a);
  1406. const footer = (_d = (_c = this.options).renderFooter) == null ? undefined : _d.call(_c);
  1407. const dialogWrapperEl = document.createElement("div");
  1408. dialogWrapperEl.id = `uu-${this.id}-dialog`;
  1409. dialogWrapperEl.classList.add("uu-dialog");
  1410. dialogWrapperEl.ariaLabel = dialogWrapperEl.title = "";
  1411. dialogWrapperEl.role = "dialog";
  1412. dialogWrapperEl.setAttribute("aria-labelledby", `uu-${this.id}-dialog-title`);
  1413. dialogWrapperEl.setAttribute("aria-describedby", `uu-${this.id}-dialog-body`);
  1414. if (this.options.verticalAlign !== "center")
  1415. dialogWrapperEl.classList.add(`align-${this.options.verticalAlign}`);
  1416. const headerWrapperEl = document.createElement("div");
  1417. headerWrapperEl.classList.add("uu-dialog-header");
  1418. this.options.small && headerWrapperEl.classList.add("small");
  1419. if (header) {
  1420. const headerTitleWrapperEl = document.createElement("div");
  1421. headerTitleWrapperEl.id = `uu-${this.id}-dialog-title`;
  1422. headerTitleWrapperEl.classList.add("uu-dialog-title-wrapper");
  1423. headerTitleWrapperEl.role = "heading";
  1424. headerTitleWrapperEl.ariaLevel = "1";
  1425. headerTitleWrapperEl.appendChild(header instanceof Promise ? yield header : header);
  1426. headerWrapperEl.appendChild(headerTitleWrapperEl);
  1427. } else {
  1428. const padEl = document.createElement("div");
  1429. padEl.classList.add("uu-dialog-header-pad", this.options.small ? "small" : "");
  1430. headerWrapperEl.appendChild(padEl);
  1431. }
  1432. if (this.options.renderCloseBtn) {
  1433. const closeBtnEl = yield this.options.renderCloseBtn();
  1434. closeBtnEl.classList.add("uu-dialog-close");
  1435. this.options.small && closeBtnEl.classList.add("small");
  1436. closeBtnEl.tabIndex = 0;
  1437. if (closeBtnEl.hasAttribute("alt"))
  1438. closeBtnEl.setAttribute("alt", this.getString("closeDialogTooltip"));
  1439. closeBtnEl.title = closeBtnEl.ariaLabel = this.getString("closeDialogTooltip");
  1440. this.onInteraction(closeBtnEl, () => this.close());
  1441. headerWrapperEl.appendChild(closeBtnEl);
  1442. }
  1443. dialogWrapperEl.appendChild(headerWrapperEl);
  1444. const dialogBodyElem = document.createElement("div");
  1445. dialogBodyElem.id = `uu-${this.id}-dialog-body`;
  1446. dialogBodyElem.classList.add("uu-dialog-body");
  1447. this.options.small && dialogBodyElem.classList.add("small");
  1448. const body = this.options.renderBody();
  1449. dialogBodyElem.appendChild(body instanceof Promise ? yield body : body);
  1450. dialogWrapperEl.appendChild(dialogBodyElem);
  1451. if (footer) {
  1452. const footerWrapper = document.createElement("div");
  1453. footerWrapper.classList.add("uu-dialog-footer-cont");
  1454. dialogWrapperEl.appendChild(footerWrapper);
  1455. footerWrapper.appendChild(footer instanceof Promise ? yield footer : footer);
  1456. }
  1457. return dialogWrapperEl;
  1458. });
  1459. }
  1460. };
  1461.  
  1462. // lib/misc.ts
  1463. function autoPlural(term, num, pluralType = "auto") {
  1464. let n = num;
  1465. if (typeof n !== "number")
  1466. n = getListLength(n, false);
  1467. if (!["-s", "-ies"].includes(pluralType))
  1468. pluralType = "auto";
  1469. if (isNaN(n))
  1470. n = 2;
  1471. const pType = pluralType === "auto" ? String(term).endsWith("y") ? "-ies" : "-s" : pluralType;
  1472. switch (pType) {
  1473. case "-s":
  1474. return `${term}${n === 1 ? "" : "s"}`;
  1475. case "-ies":
  1476. return `${String(term).slice(0, -1)}${n === 1 ? "y" : "ies"}`;
  1477. default:
  1478. return String(term);
  1479. }
  1480. }
  1481. function insertValues(input, ...values) {
  1482. return input.replace(/%\d/gm, (match) => {
  1483. var _a, _b;
  1484. const argIndex = Number(match.substring(1)) - 1;
  1485. return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? undefined : _b.toString();
  1486. });
  1487. }
  1488. function pauseFor(time, signal, rejectOnAbort = false) {
  1489. return new Promise((res, rej) => {
  1490. const timeout = setTimeout(() => res(), time);
  1491. signal == null ? undefined : signal.addEventListener("abort", () => {
  1492. clearTimeout(timeout);
  1493. rejectOnAbort ? rej(new Error("The pause was aborted")) : res();
  1494. });
  1495. });
  1496. }
  1497. function fetchAdvanced(_0) {
  1498. return __async(this, arguments, function* (input, options = {}) {
  1499. const { timeout = 1e4 } = options;
  1500. const ctl = new AbortController();
  1501. const _a = options, { signal } = _a, restOpts = __objRest(_a, ["signal"]);
  1502. signal == null ? undefined : signal.addEventListener("abort", () => ctl.abort());
  1503. let sigOpts = {}, id = undefined;
  1504. if (timeout >= 0) {
  1505. id = setTimeout(() => ctl.abort(), timeout);
  1506. sigOpts = { signal: ctl.signal };
  1507. }
  1508. try {
  1509. const res = yield fetch(input, __spreadValues(__spreadValues({}, restOpts), sigOpts));
  1510. typeof id !== "undefined" && clearTimeout(id);
  1511. return res;
  1512. } catch (err) {
  1513. typeof id !== "undefined" && clearTimeout(id);
  1514. throw new Error("Error while calling fetch", { cause: err });
  1515. }
  1516. });
  1517. }
  1518. function consumeGen(valGen) {
  1519. return __async(this, null, function* () {
  1520. return yield typeof valGen === "function" ? valGen() : valGen;
  1521. });
  1522. }
  1523. function consumeStringGen(strGen) {
  1524. return __async(this, null, function* () {
  1525. return typeof strGen === "string" ? strGen : String(
  1526. typeof strGen === "function" ? yield strGen() : strGen
  1527. );
  1528. });
  1529. }
  1530. function getListLength(obj, zeroOnInvalid = true) {
  1531. return "length" in obj ? obj.length : "size" in obj ? obj.size : "count" in obj ? obj.count : zeroOnInvalid ? 0 : NaN;
  1532. }
  1533. function purifyObj(obj) {
  1534. return Object.assign(/* @__PURE__ */ Object.create(null), obj);
  1535. }
  1536.  
  1537. // lib/SelectorObserver.ts
  1538. var SelectorObserver = class {
  1539. constructor(baseElement, options = {}) {
  1540. __publicField(this, "enabled", false);
  1541. __publicField(this, "baseElement");
  1542. __publicField(this, "observer");
  1543. __publicField(this, "observerOptions");
  1544. __publicField(this, "customOptions");
  1545. __publicField(this, "listenerMap");
  1546. this.baseElement = baseElement;
  1547. this.listenerMap = /* @__PURE__ */ new Map();
  1548. const _a = options, {
  1549. defaultDebounce,
  1550. defaultDebounceType,
  1551. disableOnNoListeners,
  1552. enableOnAddListener
  1553. } = _a, observerOptions = __objRest(_a, [
  1554. "defaultDebounce",
  1555. "defaultDebounceType",
  1556. "disableOnNoListeners",
  1557. "enableOnAddListener"
  1558. ]);
  1559. this.observerOptions = __spreadValues({
  1560. childList: true,
  1561. subtree: true
  1562. }, observerOptions);
  1563. this.customOptions = {
  1564. defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
  1565. defaultDebounceType: defaultDebounceType != null ? defaultDebounceType : "immediate",
  1566. disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
  1567. enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
  1568. };
  1569. if (typeof this.customOptions.checkInterval !== "number") {
  1570. this.observer = new MutationObserver(() => this.checkAllSelectors());
  1571. } else {
  1572. this.checkAllSelectors();
  1573. setInterval(() => this.checkAllSelectors(), this.customOptions.checkInterval);
  1574. }
  1575. }
  1576. /** Call to check all selectors in the {@linkcode listenerMap} using {@linkcode checkSelector()} */
  1577. checkAllSelectors() {
  1578. if (!this.enabled || !isDomLoaded())
  1579. return;
  1580. for (const [selector, listeners] of this.listenerMap.entries())
  1581. this.checkSelector(selector, listeners);
  1582. }
  1583. /** Checks if the element(s) with the given {@linkcode selector} exist in the DOM and calls the respective {@linkcode listeners} accordingly */
  1584. checkSelector(selector, listeners) {
  1585. var _a;
  1586. if (!this.enabled)
  1587. return;
  1588. const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
  1589. if (!baseElement)
  1590. return;
  1591. const all = listeners.some((listener) => listener.all);
  1592. const one = listeners.some((listener) => !listener.all);
  1593. const allElements = all ? baseElement.querySelectorAll(selector) : null;
  1594. const oneElement = one ? baseElement.querySelector(selector) : null;
  1595. for (const options of listeners) {
  1596. if (options.all) {
  1597. if (allElements && allElements.length > 0) {
  1598. options.listener(allElements);
  1599. if (!options.continuous)
  1600. this.removeListener(selector, options);
  1601. }
  1602. } else {
  1603. if (oneElement) {
  1604. options.listener(oneElement);
  1605. if (!options.continuous)
  1606. this.removeListener(selector, options);
  1607. }
  1608. }
  1609. if (((_a = this.listenerMap.get(selector)) == null ? undefined : _a.length) === 0)
  1610. this.listenerMap.delete(selector);
  1611. if (this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
  1612. this.disable();
  1613. }
  1614. }
  1615. /**
  1616. * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
  1617. * @param selector The selector to observe
  1618. * @param options Options for the selector observation
  1619. * @param options.listener Gets called whenever the selector was found in the DOM
  1620. * @param [options.all] Whether to use `querySelectorAll()` instead - default is false
  1621. * @param [options.continuous] Whether to call the listener continuously instead of just once - default is false
  1622. * @param [options.debounce] Whether to debounce the listener to reduce calls to `querySelector` or `querySelectorAll` - set undefined or <=0 to disable (default)
  1623. * @returns Returns a function that can be called to remove this listener more easily
  1624. */
  1625. addListener(selector, options) {
  1626. options = __spreadValues({
  1627. all: false,
  1628. continuous: false,
  1629. debounce: 0
  1630. }, options);
  1631. if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
  1632. options.listener = debounce(
  1633. options.listener,
  1634. options.debounce || this.customOptions.defaultDebounce,
  1635. options.debounceType || this.customOptions.defaultDebounceType
  1636. );
  1637. }
  1638. if (this.listenerMap.has(selector))
  1639. this.listenerMap.get(selector).push(options);
  1640. else
  1641. this.listenerMap.set(selector, [options]);
  1642. if (this.enabled === false && this.customOptions.enableOnAddListener)
  1643. this.enable();
  1644. this.checkSelector(selector, [options]);
  1645. return () => this.removeListener(selector, options);
  1646. }
  1647. /** Disables the observation of the child elements */
  1648. disable() {
  1649. var _a;
  1650. if (!this.enabled)
  1651. return;
  1652. this.enabled = false;
  1653. (_a = this.observer) == null ? undefined : _a.disconnect();
  1654. }
  1655. /**
  1656. * Enables or reenables the observation of the child elements.
  1657. * @param immediatelyCheckSelectors Whether to immediately check if all previously registered selectors exist (default is true)
  1658. * @returns Returns true when the observation was enabled, false otherwise (e.g. when the base element wasn't found)
  1659. */
  1660. enable(immediatelyCheckSelectors = true) {
  1661. var _a;
  1662. const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
  1663. if (this.enabled || !baseElement)
  1664. return false;
  1665. this.enabled = true;
  1666. (_a = this.observer) == null ? undefined : _a.observe(baseElement, this.observerOptions);
  1667. if (immediatelyCheckSelectors)
  1668. this.checkAllSelectors();
  1669. return true;
  1670. }
  1671. /** Returns whether the observation of the child elements is currently enabled */
  1672. isEnabled() {
  1673. return this.enabled;
  1674. }
  1675. /** Removes all listeners that have been registered with {@linkcode addListener()} */
  1676. clearListeners() {
  1677. this.listenerMap.clear();
  1678. }
  1679. /**
  1680. * Removes all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()}
  1681. * @returns Returns true when all listeners for the associated selector were found and removed, false otherwise
  1682. */
  1683. removeAllListeners(selector) {
  1684. return this.listenerMap.delete(selector);
  1685. }
  1686. /**
  1687. * Removes a single listener for the given {@linkcode selector} and {@linkcode options} that has been registered with {@linkcode addListener()}
  1688. * @returns Returns true when the listener was found and removed, false otherwise
  1689. */
  1690. removeListener(selector, options) {
  1691. const listeners = this.listenerMap.get(selector);
  1692. if (!listeners)
  1693. return false;
  1694. const index = listeners.indexOf(options);
  1695. if (index > -1) {
  1696. listeners.splice(index, 1);
  1697. return true;
  1698. }
  1699. return false;
  1700. }
  1701. /** Returns all listeners that have been registered with {@linkcode addListener()} */
  1702. getAllListeners() {
  1703. return this.listenerMap;
  1704. }
  1705. /** Returns all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()} */
  1706. getListeners(selector) {
  1707. return this.listenerMap.get(selector);
  1708. }
  1709. };
  1710.  
  1711. // lib/translation.ts
  1712. var trans = {};
  1713. var valTransforms = [];
  1714. var fallbackLang;
  1715. function translate(language, key, ...trArgs) {
  1716. if (typeof language !== "string")
  1717. language = fallbackLang != null ? fallbackLang : "";
  1718. const trObj = trans[language];
  1719. if (typeof language !== "string" || language.length === 0 || typeof trObj !== "object" || trObj === null)
  1720. return fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
  1721. const transformTrVal = (trKey, trValue) => {
  1722. const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(String(trValue)));
  1723. if (tfs.length === 0)
  1724. return String(trValue);
  1725. let retStr = String(trValue);
  1726. for (const tf of tfs) {
  1727. const re = new RegExp(tf.regex);
  1728. const matches = [];
  1729. let execRes;
  1730. while ((execRes = re.exec(trValue)) !== null) {
  1731. if (matches.some((m) => m[0] === (execRes == null ? undefined : execRes[0])))
  1732. break;
  1733. matches.push(execRes);
  1734. }
  1735. retStr = String(tf.fn({
  1736. language,
  1737. trValue,
  1738. currentValue: retStr,
  1739. matches,
  1740. trKey,
  1741. trArgs
  1742. }));
  1743. }
  1744. return retStr;
  1745. };
  1746. const keyParts = key.split(".");
  1747. let value = trObj;
  1748. for (const part of keyParts) {
  1749. if (typeof value !== "object" || value === null) {
  1750. value = undefined;
  1751. break;
  1752. }
  1753. value = value == null ? undefined : value[part];
  1754. }
  1755. if (typeof value === "string")
  1756. return transformTrVal(key, value);
  1757. value = trObj == null ? undefined : trObj[key];
  1758. if (typeof value === "string")
  1759. return transformTrVal(key, value);
  1760. return fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
  1761. }
  1762. function trFor(language, key, ...args) {
  1763. const txt = translate(language, key, ...args);
  1764. if (txt === key)
  1765. return fallbackLang ? translate(fallbackLang, key, ...args) : key;
  1766. return txt;
  1767. }
  1768. function useTr(language) {
  1769. return (key, ...args) => translate(language, key, ...args);
  1770. }
  1771. function hasKey(language = fallbackLang != null ? fallbackLang : "", key) {
  1772. return tr.for(language, key) !== key;
  1773. }
  1774. function addTranslations(language, translations) {
  1775. trans[language] = JSON.parse(JSON.stringify(translations));
  1776. }
  1777. function getTranslations(language = fallbackLang != null ? fallbackLang : "") {
  1778. return trans[language];
  1779. }
  1780. var deleteTranslations = (language) => {
  1781. if (language in trans) {
  1782. delete trans[language];
  1783. return true;
  1784. }
  1785. return false;
  1786. };
  1787. function setFallbackLanguage(fallbackLanguage) {
  1788. fallbackLang = fallbackLanguage;
  1789. }
  1790. function getFallbackLanguage() {
  1791. return fallbackLang;
  1792. }
  1793. function addTransform(transform) {
  1794. const [pattern, fn] = transform;
  1795. valTransforms.push({
  1796. fn,
  1797. regex: typeof pattern === "string" ? new RegExp(pattern, "gm") : pattern
  1798. });
  1799. }
  1800. function deleteTransform(patternOrFn) {
  1801. const idx = valTransforms.findIndex(
  1802. (t) => typeof patternOrFn === "function" ? t.fn === patternOrFn : typeof patternOrFn === "string" ? t.regex.source === patternOrFn : t.regex === patternOrFn
  1803. );
  1804. if (idx !== -1) {
  1805. valTransforms.splice(idx, 1);
  1806. return true;
  1807. }
  1808. return false;
  1809. }
  1810. var templateLiteralTransform = [
  1811. /\$\{([a-zA-Z0-9$_-]+)\}/gm,
  1812. ({ matches, trArgs, trValue }) => {
  1813. const patternStart = "${", patternEnd = "}", patternRegex = /\$\{.+\}/m;
  1814. let str = String(trValue);
  1815. const eachKeyInTrString = (keys) => keys.every((key) => trValue.includes(`${patternStart}${key}${patternEnd}`));
  1816. const namedMapping = () => {
  1817. var _a;
  1818. if (!str.includes(patternStart) || typeof trArgs[0] === "undefined" || typeof trArgs[0] !== "object" || !eachKeyInTrString(Object.keys((_a = trArgs[0]) != null ? _a : {})))
  1819. return;
  1820. for (const match of matches) {
  1821. const repl = match[1] !== undefined ? trArgs[0][match[1]] : undefined;
  1822. if (typeof repl !== "undefined")
  1823. str = str.replace(match[0], String(repl));
  1824. }
  1825. };
  1826. const positionalMapping = () => {
  1827. if (!patternRegex.test(str) || !trArgs[0])
  1828. return;
  1829. let matchNum = -1;
  1830. for (const match of matches) {
  1831. matchNum++;
  1832. if (typeof trArgs[matchNum] !== "undefined")
  1833. str = str.replace(match[0], String(trArgs[matchNum]));
  1834. }
  1835. };
  1836. const isArgsObject = trArgs[0] && typeof trArgs[0] === "object" && trArgs[0] !== null && String(trArgs[0]).startsWith("[object");
  1837. if (isArgsObject && eachKeyInTrString(Object.keys(trArgs[0])))
  1838. namedMapping();
  1839. else
  1840. positionalMapping();
  1841. return str;
  1842. }
  1843. ];
  1844. var percentTransform = [
  1845. /%(\d+)/gm,
  1846. ({ matches, trArgs, trValue }) => {
  1847. let str = String(trValue);
  1848. for (const match of matches) {
  1849. const repl = match[1] !== undefined ? trArgs == null ? undefined : trArgs[Number(match[1]) - 1] : undefined;
  1850. if (typeof repl !== "undefined")
  1851. str = str.replace(match[0], String(repl));
  1852. }
  1853. return str;
  1854. }
  1855. ];
  1856. var tr = {
  1857. for: (...params) => trFor(...params),
  1858. use: (...params) => useTr(...params),
  1859. hasKey: (language = fallbackLang != null ? fallbackLang : "", key) => hasKey(language, key),
  1860. addTranslations,
  1861. getTranslations,
  1862. deleteTranslations,
  1863. setFallbackLanguage,
  1864. getFallbackLanguage,
  1865. addTransform,
  1866. deleteTransform,
  1867. transforms: {
  1868. templateLiteral: templateLiteralTransform,
  1869. percent: percentTransform
  1870. }
  1871. };
  1872.  
  1873. exports.ChecksumMismatchError = ChecksumMismatchError;
  1874. exports.DataStore = DataStore;
  1875. exports.DataStoreSerializer = DataStoreSerializer;
  1876. exports.Debouncer = Debouncer;
  1877. exports.Dialog = Dialog;
  1878. exports.MigrationError = MigrationError;
  1879. exports.NanoEmitter = NanoEmitter;
  1880. exports.PlatformError = PlatformError;
  1881. exports.SelectorObserver = SelectorObserver;
  1882. exports.UUError = UUError;
  1883. exports.addGlobalStyle = addGlobalStyle;
  1884. exports.addParent = addParent;
  1885. exports.autoPlural = autoPlural;
  1886. exports.bitSetHas = bitSetHas;
  1887. exports.clamp = clamp;
  1888. exports.compress = compress;
  1889. exports.computeHash = computeHash;
  1890. exports.consumeGen = consumeGen;
  1891. exports.consumeStringGen = consumeStringGen;
  1892. exports.darkenColor = darkenColor;
  1893. exports.debounce = debounce;
  1894. exports.decompress = decompress;
  1895. exports.defaultDialogCss = defaultDialogCss;
  1896. exports.defaultStrings = defaultStrings;
  1897. exports.digitCount = digitCount;
  1898. exports.fetchAdvanced = fetchAdvanced;
  1899. exports.getListLength = getListLength;
  1900. exports.getSiblingsFrame = getSiblingsFrame;
  1901. exports.getUnsafeWindow = getUnsafeWindow;
  1902. exports.hexToRgb = hexToRgb;
  1903. exports.insertValues = insertValues;
  1904. exports.interceptEvent = interceptEvent;
  1905. exports.interceptWindowEvent = interceptWindowEvent;
  1906. exports.isDomLoaded = isDomLoaded;
  1907. exports.isScrollable = isScrollable;
  1908. exports.lightenColor = lightenColor;
  1909. exports.mapRange = mapRange;
  1910. exports.observeElementProp = observeElementProp;
  1911. exports.onDomLoad = onDomLoad;
  1912. exports.openDialogs = openDialogs;
  1913. exports.openInNewTab = openInNewTab;
  1914. exports.pauseFor = pauseFor;
  1915. exports.preloadImages = preloadImages;
  1916. exports.probeElementStyle = probeElementStyle;
  1917. exports.purifyObj = purifyObj;
  1918. exports.randRange = randRange;
  1919. exports.randomId = randomId;
  1920. exports.randomItem = randomItem;
  1921. exports.randomItemIndex = randomItemIndex;
  1922. exports.randomizeArray = randomizeArray;
  1923. exports.rgbToHex = rgbToHex;
  1924. exports.roundFixed = roundFixed;
  1925. exports.setInnerHtmlUnsafe = setInnerHtmlUnsafe;
  1926. exports.takeRandomItem = takeRandomItem;
  1927. exports.tr = tr;
  1928.  
  1929. return exports;
  1930.  
  1931. })({});

QingJ © 2025

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