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-14 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/472956/1553689/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.2.1
  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 the data stores into a string.
  769. * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
  770. * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
  771. */
  772. serialize(useEncoding = true, stringified = true) {
  773. return __async(this, null, function* () {
  774. const serData = [];
  775. for (const storeInst of this.stores) {
  776. const data = useEncoding && storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
  777. serData.push({
  778. id: storeInst.id,
  779. data,
  780. formatVersion: storeInst.formatVersion,
  781. encoded: useEncoding && storeInst.encodingEnabled(),
  782. checksum: this.options.addChecksum ? yield this.calcChecksum(data) : undefined
  783. });
  784. }
  785. return stringified ? JSON.stringify(serData) : serData;
  786. });
  787. }
  788. /**
  789. * Deserializes the data exported via {@linkcode serialize()} and imports it into the DataStore instances.
  790. * Also triggers the migration process if the data format has changed.
  791. */
  792. deserialize(serializedData) {
  793. return __async(this, null, function* () {
  794. const deserStores = typeof serializedData === "string" ? JSON.parse(serializedData) : serializedData;
  795. if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStore))
  796. throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
  797. for (const storeData of deserStores) {
  798. const storeInst = this.stores.find((s) => s.id === storeData.id);
  799. if (!storeInst)
  800. throw new Error(`DataStore instance with ID "${storeData.id}" not found! Make sure to provide it in the DataStoreSerializer constructor.`);
  801. if (this.options.ensureIntegrity && typeof storeData.checksum === "string") {
  802. const checksum = yield this.calcChecksum(storeData.data);
  803. if (checksum !== storeData.checksum)
  804. throw new ChecksumMismatchError(`Checksum mismatch for DataStore with ID "${storeData.id}"!
  805. Expected: ${storeData.checksum}
  806. Has: ${checksum}`);
  807. }
  808. const decodedData = storeData.encoded && storeInst.encodingEnabled() ? yield storeInst.decodeData(storeData.data) : storeData.data;
  809. if (storeData.formatVersion && !isNaN(Number(storeData.formatVersion)) && Number(storeData.formatVersion) < storeInst.formatVersion)
  810. yield storeInst.runMigrations(JSON.parse(decodedData), Number(storeData.formatVersion), false);
  811. else
  812. yield storeInst.setData(JSON.parse(decodedData));
  813. }
  814. });
  815. }
  816. /**
  817. * Loads the persistent data of the DataStore instances into the in-memory cache.
  818. * Also triggers the migration process if the data format has changed.
  819. * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
  820. */
  821. loadStoresData() {
  822. return __async(this, null, function* () {
  823. return Promise.allSettled(this.stores.map(
  824. (store) => __async(this, null, function* () {
  825. return {
  826. id: store.id,
  827. data: yield store.loadData()
  828. };
  829. })
  830. ));
  831. });
  832. }
  833. /** Resets the persistent data of the DataStore instances to their default values. */
  834. resetStoresData() {
  835. return __async(this, null, function* () {
  836. return Promise.allSettled(this.stores.map((store) => store.saveDefaultData()));
  837. });
  838. }
  839. /**
  840. * Deletes the persistent data of the DataStore instances.
  841. * Leaves the in-memory data untouched.
  842. */
  843. deleteStoresData() {
  844. return __async(this, null, function* () {
  845. return Promise.allSettled(this.stores.map((store) => store.deleteData()));
  846. });
  847. }
  848. /** Checks if a given value is a SerializedDataStore object */
  849. static isSerializedDataStore(obj) {
  850. return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
  851. }
  852. };
  853.  
  854. // node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
  855. var createNanoEvents = () => ({
  856. emit(event, ...args) {
  857. for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
  858. callbacks[i](...args);
  859. }
  860. },
  861. events: {},
  862. on(event, cb) {
  863. var _a;
  864. ((_a = this.events)[event] || (_a[event] = [])).push(cb);
  865. return () => {
  866. var _a2;
  867. this.events[event] = (_a2 = this.events[event]) == null ? undefined : _a2.filter((i) => cb !== i);
  868. };
  869. }
  870. });
  871.  
  872. // lib/NanoEmitter.ts
  873. var NanoEmitter = class {
  874. constructor(options = {}) {
  875. __publicField(this, "events", createNanoEvents());
  876. __publicField(this, "eventUnsubscribes", []);
  877. __publicField(this, "emitterOptions");
  878. this.emitterOptions = __spreadValues({
  879. publicEmit: false
  880. }, options);
  881. }
  882. /** Subscribes to an event - returns a function that unsubscribes the event listener */
  883. on(event, cb) {
  884. let unsub;
  885. const unsubProxy = () => {
  886. if (!unsub)
  887. return;
  888. unsub();
  889. this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
  890. };
  891. unsub = this.events.on(event, cb);
  892. this.eventUnsubscribes.push(unsub);
  893. return unsubProxy;
  894. }
  895. /** Subscribes to an event and calls the callback or resolves the Promise only once */
  896. once(event, cb) {
  897. return new Promise((resolve) => {
  898. let unsub;
  899. const onceProxy = (...args) => {
  900. unsub();
  901. cb == null ? undefined : cb(...args);
  902. resolve(args);
  903. };
  904. unsub = this.on(event, onceProxy);
  905. });
  906. }
  907. /** Emits an event on this instance - Needs `publicEmit` to be set to true in the constructor! */
  908. emit(event, ...args) {
  909. if (this.emitterOptions.publicEmit) {
  910. this.events.emit(event, ...args);
  911. return true;
  912. }
  913. return false;
  914. }
  915. /** Unsubscribes all event listeners */
  916. unsubscribeAll() {
  917. for (const unsub of this.eventUnsubscribes)
  918. unsub();
  919. this.eventUnsubscribes = [];
  920. }
  921. };
  922.  
  923. // lib/Debouncer.ts
  924. var Debouncer = class extends NanoEmitter {
  925. /**
  926. * Creates a new debouncer with the specified timeout and edge type.
  927. * @param timeout Timeout in milliseconds between letting through calls - defaults to 200
  928. * @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"
  929. */
  930. constructor(timeout = 200, type = "immediate") {
  931. super();
  932. this.timeout = timeout;
  933. this.type = type;
  934. /** All registered listener functions and the time they were attached */
  935. __publicField(this, "listeners", []);
  936. /** The currently active timeout */
  937. __publicField(this, "activeTimeout");
  938. /** The latest queued call */
  939. __publicField(this, "queuedCall");
  940. }
  941. //#region listeners
  942. /** Adds a listener function that will be called on timeout */
  943. addListener(fn) {
  944. this.listeners.push(fn);
  945. }
  946. /** Removes the listener with the specified function reference */
  947. removeListener(fn) {
  948. const idx = this.listeners.findIndex((l) => l === fn);
  949. idx !== -1 && this.listeners.splice(idx, 1);
  950. }
  951. /** Removes all listeners */
  952. removeAllListeners() {
  953. this.listeners = [];
  954. }
  955. //#region timeout
  956. /** Sets the timeout for the debouncer */
  957. setTimeout(timeout) {
  958. this.emit("change", this.timeout = timeout, this.type);
  959. }
  960. /** Returns the current timeout */
  961. getTimeout() {
  962. return this.timeout;
  963. }
  964. /** Whether the timeout is currently active, meaning any latest call to the {@linkcode call()} method will be queued */
  965. isTimeoutActive() {
  966. return typeof this.activeTimeout !== "undefined";
  967. }
  968. //#region type
  969. /** Sets the edge type for the debouncer */
  970. setType(type) {
  971. this.emit("change", this.timeout, this.type = type);
  972. }
  973. /** Returns the current edge type */
  974. getType() {
  975. return this.type;
  976. }
  977. //#region call
  978. /** Use this to call the debouncer with the specified arguments that will be passed to all listener functions registered with {@linkcode addListener()} */
  979. call(...args) {
  980. const cl = (...a) => {
  981. this.queuedCall = undefined;
  982. this.emit("call", ...a);
  983. this.listeners.forEach((l) => l.apply(this, a));
  984. };
  985. const setRepeatTimeout = () => {
  986. this.activeTimeout = setTimeout(() => {
  987. if (this.queuedCall) {
  988. this.queuedCall();
  989. setRepeatTimeout();
  990. } else
  991. this.activeTimeout = undefined;
  992. }, this.timeout);
  993. };
  994. switch (this.type) {
  995. case "immediate":
  996. if (typeof this.activeTimeout === "undefined") {
  997. cl(...args);
  998. setRepeatTimeout();
  999. } else
  1000. this.queuedCall = () => cl(...args);
  1001. break;
  1002. case "idle":
  1003. if (this.activeTimeout)
  1004. clearTimeout(this.activeTimeout);
  1005. this.activeTimeout = setTimeout(() => {
  1006. cl(...args);
  1007. this.activeTimeout = undefined;
  1008. }, this.timeout);
  1009. break;
  1010. default:
  1011. throw new TypeError(`Invalid debouncer type: ${this.type}`);
  1012. }
  1013. }
  1014. };
  1015. function debounce(fn, timeout = 200, type = "immediate") {
  1016. const debouncer = new Debouncer(timeout, type);
  1017. debouncer.addListener(fn);
  1018. const func = (...args) => debouncer.call(...args);
  1019. func.debouncer = debouncer;
  1020. return func;
  1021. }
  1022.  
  1023. // lib/Dialog.ts
  1024. var defaultDialogCss = `.uu-no-select {
  1025. user-select: none;
  1026. }
  1027.  
  1028. .uu-dialog-bg {
  1029. --uu-dialog-bg: #333333;
  1030. --uu-dialog-bg-highlight: #252525;
  1031. --uu-scroll-indicator-bg: rgba(10, 10, 10, 0.7);
  1032. --uu-dialog-separator-color: #797979;
  1033. --uu-dialog-border-radius: 10px;
  1034. }
  1035.  
  1036. .uu-dialog-bg {
  1037. display: block;
  1038. position: fixed;
  1039. width: 100%;
  1040. height: 100%;
  1041. top: 0;
  1042. left: 0;
  1043. z-index: 5;
  1044. background-color: rgba(0, 0, 0, 0.6);
  1045. }
  1046.  
  1047. .uu-dialog {
  1048. --uu-calc-dialog-height: calc(min(100vh - 40px, var(--uu-dialog-height-max)));
  1049. position: absolute;
  1050. display: flex;
  1051. flex-direction: column;
  1052. width: calc(min(100% - 60px, var(--uu-dialog-width-max)));
  1053. border-radius: var(--uu-dialog-border-radius);
  1054. height: auto;
  1055. max-height: var(--uu-calc-dialog-height);
  1056. left: 50%;
  1057. top: 50%;
  1058. transform: translate(-50%, -50%);
  1059. z-index: 6;
  1060. color: #fff;
  1061. background-color: var(--uu-dialog-bg);
  1062. }
  1063.  
  1064. .uu-dialog.align-top {
  1065. top: 0;
  1066. transform: translate(-50%, 40px);
  1067. }
  1068.  
  1069. .uu-dialog.align-bottom {
  1070. top: 100%;
  1071. transform: translate(-50%, -100%);
  1072. }
  1073.  
  1074. .uu-dialog-body {
  1075. font-size: 1.5rem;
  1076. padding: 20px;
  1077. }
  1078.  
  1079. .uu-dialog-body.small {
  1080. padding: 15px;
  1081. }
  1082.  
  1083. #uu-dialog-opts {
  1084. display: flex;
  1085. flex-direction: column;
  1086. position: relative;
  1087. padding: 30px 0px;
  1088. overflow-y: auto;
  1089. }
  1090.  
  1091. .uu-dialog-header {
  1092. display: flex;
  1093. justify-content: space-between;
  1094. align-items: center;
  1095. margin-bottom: 6px;
  1096. padding: 15px 20px 15px 20px;
  1097. background-color: var(--uu-dialog-bg);
  1098. border: 2px solid var(--uu-dialog-separator-color);
  1099. border-style: none none solid none !important;
  1100. border-radius: var(--uu-dialog-border-radius) var(--uu-dialog-border-radius) 0px 0px;
  1101. }
  1102.  
  1103. .uu-dialog-header.small {
  1104. padding: 10px 15px;
  1105. border-style: none none solid none !important;
  1106. }
  1107.  
  1108. .uu-dialog-header-pad {
  1109. content: " ";
  1110. min-height: 32px;
  1111. }
  1112.  
  1113. .uu-dialog-header-pad.small {
  1114. min-height: 24px;
  1115. }
  1116.  
  1117. .uu-dialog-titlecont {
  1118. display: flex;
  1119. align-items: center;
  1120. }
  1121.  
  1122. .uu-dialog-titlecont-no-title {
  1123. display: flex;
  1124. justify-content: flex-end;
  1125. align-items: center;
  1126. }
  1127.  
  1128. .uu-dialog-title {
  1129. position: relative;
  1130. display: inline-block;
  1131. font-size: 22px;
  1132. }
  1133.  
  1134. .uu-dialog-close {
  1135. cursor: pointer;
  1136. }
  1137.  
  1138. .uu-dialog-header-img,
  1139. .uu-dialog-close
  1140. {
  1141. width: 32px;
  1142. height: 32px;
  1143. }
  1144.  
  1145. .uu-dialog-header-img.small,
  1146. .uu-dialog-close.small
  1147. {
  1148. width: 24px;
  1149. height: 24px;
  1150. }
  1151.  
  1152. .uu-dialog-footer {
  1153. font-size: 17px;
  1154. text-decoration: underline;
  1155. }
  1156.  
  1157. .uu-dialog-footer.hidden {
  1158. display: none;
  1159. }
  1160.  
  1161. .uu-dialog-footer-cont {
  1162. margin-top: 6px;
  1163. padding: 15px 20px;
  1164. background: var(--uu-dialog-bg);
  1165. background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, var(--uu-dialog-bg) 30%, var(--uu-dialog-bg) 100%);
  1166. border: 2px solid var(--uu-dialog-separator-color);
  1167. border-style: solid none none none !important;
  1168. border-radius: 0px 0px var(--uu-dialog-border-radius) var(--uu-dialog-border-radius);
  1169. }
  1170.  
  1171. .uu-dialog-footer-buttons-cont button:not(:last-of-type) {
  1172. margin-right: 15px;
  1173. }`;
  1174. exports.currentDialogId = null;
  1175. var openDialogs = [];
  1176. var defaultStrings = {
  1177. closeDialogTooltip: "Click to close the dialog"
  1178. };
  1179. var Dialog = class _Dialog extends NanoEmitter {
  1180. constructor(options) {
  1181. super();
  1182. /** Options passed to the dialog in the constructor */
  1183. __publicField(this, "options");
  1184. /** ID that gets added to child element IDs - has to be unique and conform to HTML ID naming rules! */
  1185. __publicField(this, "id");
  1186. /** Strings used in the dialog (used for translations) */
  1187. __publicField(this, "strings");
  1188. __publicField(this, "dialogOpen", false);
  1189. __publicField(this, "dialogMounted", false);
  1190. const _a = options, { strings } = _a, opts = __objRest(_a, ["strings"]);
  1191. this.strings = __spreadValues(__spreadValues({}, defaultStrings), strings != null ? strings : {});
  1192. this.options = __spreadValues({
  1193. closeOnBgClick: true,
  1194. closeOnEscPress: true,
  1195. destroyOnClose: false,
  1196. unmountOnClose: true,
  1197. removeListenersOnDestroy: true,
  1198. small: false,
  1199. verticalAlign: "center"
  1200. }, opts);
  1201. this.id = opts.id;
  1202. }
  1203. //#region public
  1204. /** Call after DOMContentLoaded to pre-render the dialog and invisibly mount it in the DOM */
  1205. mount() {
  1206. return __async(this, null, function* () {
  1207. var _a;
  1208. if (this.dialogMounted)
  1209. return;
  1210. this.dialogMounted = true;
  1211. if (!document.querySelector("style.uu-dialog-css"))
  1212. addGlobalStyle((_a = this.options.dialogCss) != null ? _a : defaultDialogCss).classList.add("uu-dialog-css");
  1213. const bgElem = document.createElement("div");
  1214. bgElem.id = `uu-${this.id}-dialog-bg`;
  1215. bgElem.classList.add("uu-dialog-bg");
  1216. if (this.options.closeOnBgClick)
  1217. bgElem.ariaLabel = bgElem.title = this.getString("closeDialogTooltip");
  1218. bgElem.style.setProperty("--uu-dialog-width-max", `${this.options.width}px`);
  1219. bgElem.style.setProperty("--uu-dialog-height-max", `${this.options.height}px`);
  1220. bgElem.style.visibility = "hidden";
  1221. bgElem.style.display = "none";
  1222. bgElem.inert = true;
  1223. bgElem.appendChild(yield this.getDialogContent());
  1224. document.body.appendChild(bgElem);
  1225. this.attachListeners(bgElem);
  1226. this.events.emit("render");
  1227. return bgElem;
  1228. });
  1229. }
  1230. /** Closes the dialog and clears all its contents (unmounts elements from the DOM) in preparation for a new rendering call */
  1231. unmount() {
  1232. var _a;
  1233. this.close();
  1234. this.dialogMounted = false;
  1235. const clearSelectors = [
  1236. `#uu-${this.id}-dialog-bg`,
  1237. `#uu-style-dialog-${this.id}`
  1238. ];
  1239. for (const sel of clearSelectors)
  1240. (_a = document.querySelector(sel)) == null ? undefined : _a.remove();
  1241. this.events.emit("clear");
  1242. }
  1243. /** Clears the DOM of the dialog and then renders it again */
  1244. remount() {
  1245. return __async(this, null, function* () {
  1246. this.unmount();
  1247. yield this.mount();
  1248. });
  1249. }
  1250. /**
  1251. * Opens the dialog - also mounts it if it hasn't been mounted yet
  1252. * Prevents default action and immediate propagation of the passed event
  1253. */
  1254. open(e) {
  1255. return __async(this, null, function* () {
  1256. var _a;
  1257. e == null ? undefined : e.preventDefault();
  1258. e == null ? undefined : e.stopImmediatePropagation();
  1259. if (this.isOpen())
  1260. return;
  1261. this.dialogOpen = true;
  1262. if (openDialogs.includes(this.id))
  1263. throw new Error(`A dialog with the same ID of '${this.id}' already exists and is open!`);
  1264. if (!this.isMounted())
  1265. yield this.mount();
  1266. const dialogBg = document.querySelector(`#uu-${this.id}-dialog-bg`);
  1267. if (!dialogBg)
  1268. return console.warn(`Couldn't find background element for dialog with ID '${this.id}'`);
  1269. dialogBg.style.visibility = "visible";
  1270. dialogBg.style.display = "block";
  1271. dialogBg.inert = false;
  1272. exports.currentDialogId = this.id;
  1273. openDialogs.unshift(this.id);
  1274. for (const dialogId of openDialogs)
  1275. if (dialogId !== this.id)
  1276. (_a = document.querySelector(`#uu-${dialogId}-dialog-bg`)) == null ? undefined : _a.setAttribute("inert", "true");
  1277. document.body.classList.remove("uu-no-select");
  1278. document.body.setAttribute("inert", "true");
  1279. this.events.emit("open");
  1280. return dialogBg;
  1281. });
  1282. }
  1283. /** Closes the dialog - prevents default action and immediate propagation of the passed event */
  1284. close(e) {
  1285. var _a, _b;
  1286. e == null ? undefined : e.preventDefault();
  1287. e == null ? undefined : e.stopImmediatePropagation();
  1288. if (!this.isOpen())
  1289. return;
  1290. this.dialogOpen = false;
  1291. const dialogBg = document.querySelector(`#uu-${this.id}-dialog-bg`);
  1292. if (!dialogBg)
  1293. return console.warn(`Couldn't find background element for dialog with ID '${this.id}'`);
  1294. dialogBg.style.visibility = "hidden";
  1295. dialogBg.style.display = "none";
  1296. dialogBg.inert = true;
  1297. openDialogs.splice(openDialogs.indexOf(this.id), 1);
  1298. exports.currentDialogId = (_a = openDialogs[0]) != null ? _a : null;
  1299. if (exports.currentDialogId)
  1300. (_b = document.querySelector(`#uu-${exports.currentDialogId}-dialog-bg`)) == null ? undefined : _b.removeAttribute("inert");
  1301. if (openDialogs.length === 0) {
  1302. document.body.classList.add("uu-no-select");
  1303. document.body.removeAttribute("inert");
  1304. }
  1305. this.events.emit("close");
  1306. if (this.options.destroyOnClose)
  1307. this.destroy();
  1308. else if (this.options.unmountOnClose)
  1309. this.unmount();
  1310. }
  1311. /** Returns true if the dialog is currently open */
  1312. isOpen() {
  1313. return this.dialogOpen;
  1314. }
  1315. /** Returns true if the dialog is currently mounted */
  1316. isMounted() {
  1317. return this.dialogMounted;
  1318. }
  1319. /** Clears the DOM of the dialog and removes all event listeners */
  1320. destroy() {
  1321. this.unmount();
  1322. this.events.emit("destroy");
  1323. this.options.removeListenersOnDestroy && this.unsubscribeAll();
  1324. }
  1325. //#region static
  1326. /** Returns the ID of the top-most dialog (the dialog that has been opened last) */
  1327. static getCurrentDialogId() {
  1328. return exports.currentDialogId;
  1329. }
  1330. /** Returns the IDs of all currently open dialogs, top-most first */
  1331. static getOpenDialogs() {
  1332. return openDialogs;
  1333. }
  1334. //#region protected
  1335. getString(key) {
  1336. var _a;
  1337. return (_a = this.strings[key]) != null ? _a : defaultStrings[key];
  1338. }
  1339. /** Called once to attach all generic event listeners */
  1340. attachListeners(bgElem) {
  1341. if (this.options.closeOnBgClick) {
  1342. bgElem.addEventListener("click", (e) => {
  1343. var _a;
  1344. if (this.isOpen() && ((_a = e.target) == null ? undefined : _a.id) === `uu-${this.id}-dialog-bg`)
  1345. this.close(e);
  1346. });
  1347. }
  1348. if (this.options.closeOnEscPress) {
  1349. document.body.addEventListener("keydown", (e) => {
  1350. if (e.key === "Escape" && this.isOpen() && _Dialog.getCurrentDialogId() === this.id)
  1351. this.close(e);
  1352. });
  1353. }
  1354. }
  1355. //#region protected
  1356. /**
  1357. * Adds generic, accessible interaction listeners to the passed element.
  1358. * All listeners have the default behavior prevented and stop propagation (for keyboard events only as long as the captured key is valid).
  1359. * @param listenerOptions Provide a {@linkcode listenerOptions} object to configure the listeners
  1360. */
  1361. onInteraction(elem, listener, listenerOptions) {
  1362. const _a = listenerOptions != null ? listenerOptions : {}, { preventDefault = true, stopPropagation = true } = _a, listenerOpts = __objRest(_a, ["preventDefault", "stopPropagation"]);
  1363. const interactionKeys = ["Enter", " ", "Space"];
  1364. const proxListener = (e) => {
  1365. if (e instanceof KeyboardEvent) {
  1366. if (interactionKeys.includes(e.key)) {
  1367. preventDefault && e.preventDefault();
  1368. stopPropagation && e.stopPropagation();
  1369. } else return;
  1370. } else if (e instanceof MouseEvent) {
  1371. preventDefault && e.preventDefault();
  1372. stopPropagation && e.stopPropagation();
  1373. }
  1374. (listenerOpts == null ? undefined : listenerOpts.once) && e.type === "keydown" && elem.removeEventListener("click", proxListener, listenerOpts);
  1375. (listenerOpts == null ? undefined : listenerOpts.once) && e.type === "click" && elem.removeEventListener("keydown", proxListener, listenerOpts);
  1376. listener(e);
  1377. };
  1378. elem.addEventListener("click", proxListener, listenerOpts);
  1379. elem.addEventListener("keydown", proxListener, listenerOpts);
  1380. }
  1381. /** Returns the dialog content element and all its children */
  1382. getDialogContent() {
  1383. return __async(this, null, function* () {
  1384. var _a, _b, _c, _d;
  1385. const header = (_b = (_a = this.options).renderHeader) == null ? undefined : _b.call(_a);
  1386. const footer = (_d = (_c = this.options).renderFooter) == null ? undefined : _d.call(_c);
  1387. const dialogWrapperEl = document.createElement("div");
  1388. dialogWrapperEl.id = `uu-${this.id}-dialog`;
  1389. dialogWrapperEl.classList.add("uu-dialog");
  1390. dialogWrapperEl.ariaLabel = dialogWrapperEl.title = "";
  1391. dialogWrapperEl.role = "dialog";
  1392. dialogWrapperEl.setAttribute("aria-labelledby", `uu-${this.id}-dialog-title`);
  1393. dialogWrapperEl.setAttribute("aria-describedby", `uu-${this.id}-dialog-body`);
  1394. if (this.options.verticalAlign !== "center")
  1395. dialogWrapperEl.classList.add(`align-${this.options.verticalAlign}`);
  1396. const headerWrapperEl = document.createElement("div");
  1397. headerWrapperEl.classList.add("uu-dialog-header");
  1398. this.options.small && headerWrapperEl.classList.add("small");
  1399. if (header) {
  1400. const headerTitleWrapperEl = document.createElement("div");
  1401. headerTitleWrapperEl.id = `uu-${this.id}-dialog-title`;
  1402. headerTitleWrapperEl.classList.add("uu-dialog-title-wrapper");
  1403. headerTitleWrapperEl.role = "heading";
  1404. headerTitleWrapperEl.ariaLevel = "1";
  1405. headerTitleWrapperEl.appendChild(header instanceof Promise ? yield header : header);
  1406. headerWrapperEl.appendChild(headerTitleWrapperEl);
  1407. } else {
  1408. const padEl = document.createElement("div");
  1409. padEl.classList.add("uu-dialog-header-pad", this.options.small ? "small" : "");
  1410. headerWrapperEl.appendChild(padEl);
  1411. }
  1412. if (this.options.renderCloseBtn) {
  1413. const closeBtnEl = yield this.options.renderCloseBtn();
  1414. closeBtnEl.classList.add("uu-dialog-close");
  1415. this.options.small && closeBtnEl.classList.add("small");
  1416. closeBtnEl.tabIndex = 0;
  1417. if (closeBtnEl.hasAttribute("alt"))
  1418. closeBtnEl.setAttribute("alt", this.getString("closeDialogTooltip"));
  1419. closeBtnEl.title = closeBtnEl.ariaLabel = this.getString("closeDialogTooltip");
  1420. this.onInteraction(closeBtnEl, () => this.close());
  1421. headerWrapperEl.appendChild(closeBtnEl);
  1422. }
  1423. dialogWrapperEl.appendChild(headerWrapperEl);
  1424. const dialogBodyElem = document.createElement("div");
  1425. dialogBodyElem.id = `uu-${this.id}-dialog-body`;
  1426. dialogBodyElem.classList.add("uu-dialog-body");
  1427. this.options.small && dialogBodyElem.classList.add("small");
  1428. const body = this.options.renderBody();
  1429. dialogBodyElem.appendChild(body instanceof Promise ? yield body : body);
  1430. dialogWrapperEl.appendChild(dialogBodyElem);
  1431. if (footer) {
  1432. const footerWrapper = document.createElement("div");
  1433. footerWrapper.classList.add("uu-dialog-footer-cont");
  1434. dialogWrapperEl.appendChild(footerWrapper);
  1435. footerWrapper.appendChild(footer instanceof Promise ? yield footer : footer);
  1436. }
  1437. return dialogWrapperEl;
  1438. });
  1439. }
  1440. };
  1441.  
  1442. // lib/misc.ts
  1443. function autoPlural(term, num, pluralType = "auto") {
  1444. let n = num;
  1445. if (typeof n !== "number")
  1446. n = getListLength(n, false);
  1447. if (!["-s", "-ies"].includes(pluralType))
  1448. pluralType = "auto";
  1449. if (isNaN(n))
  1450. n = 2;
  1451. const pType = pluralType === "auto" ? String(term).endsWith("y") ? "-ies" : "-s" : pluralType;
  1452. switch (pType) {
  1453. case "-s":
  1454. return `${term}${n === 1 ? "" : "s"}`;
  1455. case "-ies":
  1456. return `${String(term).slice(0, -1)}${n === 1 ? "y" : "ies"}`;
  1457. default:
  1458. return String(term);
  1459. }
  1460. }
  1461. function insertValues(input, ...values) {
  1462. return input.replace(/%\d/gm, (match) => {
  1463. var _a, _b;
  1464. const argIndex = Number(match.substring(1)) - 1;
  1465. return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? undefined : _b.toString();
  1466. });
  1467. }
  1468. function pauseFor(time, signal, rejectOnAbort = false) {
  1469. return new Promise((res, rej) => {
  1470. const timeout = setTimeout(() => res(), time);
  1471. signal == null ? undefined : signal.addEventListener("abort", () => {
  1472. clearTimeout(timeout);
  1473. rejectOnAbort ? rej(new Error("The pause was aborted")) : res();
  1474. });
  1475. });
  1476. }
  1477. function fetchAdvanced(_0) {
  1478. return __async(this, arguments, function* (input, options = {}) {
  1479. const { timeout = 1e4 } = options;
  1480. const ctl = new AbortController();
  1481. const _a = options, { signal } = _a, restOpts = __objRest(_a, ["signal"]);
  1482. signal == null ? undefined : signal.addEventListener("abort", () => ctl.abort());
  1483. let sigOpts = {}, id = undefined;
  1484. if (timeout >= 0) {
  1485. id = setTimeout(() => ctl.abort(), timeout);
  1486. sigOpts = { signal: ctl.signal };
  1487. }
  1488. try {
  1489. const res = yield fetch(input, __spreadValues(__spreadValues({}, restOpts), sigOpts));
  1490. typeof id !== "undefined" && clearTimeout(id);
  1491. return res;
  1492. } catch (err) {
  1493. typeof id !== "undefined" && clearTimeout(id);
  1494. throw new Error("Error while calling fetch", { cause: err });
  1495. }
  1496. });
  1497. }
  1498. function consumeGen(valGen) {
  1499. return __async(this, null, function* () {
  1500. return yield typeof valGen === "function" ? valGen() : valGen;
  1501. });
  1502. }
  1503. function consumeStringGen(strGen) {
  1504. return __async(this, null, function* () {
  1505. return typeof strGen === "string" ? strGen : String(
  1506. typeof strGen === "function" ? yield strGen() : strGen
  1507. );
  1508. });
  1509. }
  1510. function getListLength(obj, zeroOnInvalid = true) {
  1511. return "length" in obj ? obj.length : "size" in obj ? obj.size : "count" in obj ? obj.count : zeroOnInvalid ? 0 : NaN;
  1512. }
  1513.  
  1514. // lib/SelectorObserver.ts
  1515. var SelectorObserver = class {
  1516. constructor(baseElement, options = {}) {
  1517. __publicField(this, "enabled", false);
  1518. __publicField(this, "baseElement");
  1519. __publicField(this, "observer");
  1520. __publicField(this, "observerOptions");
  1521. __publicField(this, "customOptions");
  1522. __publicField(this, "listenerMap");
  1523. this.baseElement = baseElement;
  1524. this.listenerMap = /* @__PURE__ */ new Map();
  1525. const _a = options, {
  1526. defaultDebounce,
  1527. defaultDebounceType,
  1528. disableOnNoListeners,
  1529. enableOnAddListener
  1530. } = _a, observerOptions = __objRest(_a, [
  1531. "defaultDebounce",
  1532. "defaultDebounceType",
  1533. "disableOnNoListeners",
  1534. "enableOnAddListener"
  1535. ]);
  1536. this.observerOptions = __spreadValues({
  1537. childList: true,
  1538. subtree: true
  1539. }, observerOptions);
  1540. this.customOptions = {
  1541. defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
  1542. defaultDebounceType: defaultDebounceType != null ? defaultDebounceType : "immediate",
  1543. disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
  1544. enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
  1545. };
  1546. if (typeof this.customOptions.checkInterval !== "number") {
  1547. this.observer = new MutationObserver(() => this.checkAllSelectors());
  1548. } else {
  1549. this.checkAllSelectors();
  1550. setInterval(() => this.checkAllSelectors(), this.customOptions.checkInterval);
  1551. }
  1552. }
  1553. /** Call to check all selectors in the {@linkcode listenerMap} using {@linkcode checkSelector()} */
  1554. checkAllSelectors() {
  1555. if (!this.enabled || !isDomLoaded())
  1556. return;
  1557. for (const [selector, listeners] of this.listenerMap.entries())
  1558. this.checkSelector(selector, listeners);
  1559. }
  1560. /** Checks if the element(s) with the given {@linkcode selector} exist in the DOM and calls the respective {@linkcode listeners} accordingly */
  1561. checkSelector(selector, listeners) {
  1562. var _a;
  1563. if (!this.enabled)
  1564. return;
  1565. const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
  1566. if (!baseElement)
  1567. return;
  1568. const all = listeners.some((listener) => listener.all);
  1569. const one = listeners.some((listener) => !listener.all);
  1570. const allElements = all ? baseElement.querySelectorAll(selector) : null;
  1571. const oneElement = one ? baseElement.querySelector(selector) : null;
  1572. for (const options of listeners) {
  1573. if (options.all) {
  1574. if (allElements && allElements.length > 0) {
  1575. options.listener(allElements);
  1576. if (!options.continuous)
  1577. this.removeListener(selector, options);
  1578. }
  1579. } else {
  1580. if (oneElement) {
  1581. options.listener(oneElement);
  1582. if (!options.continuous)
  1583. this.removeListener(selector, options);
  1584. }
  1585. }
  1586. if (((_a = this.listenerMap.get(selector)) == null ? undefined : _a.length) === 0)
  1587. this.listenerMap.delete(selector);
  1588. if (this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
  1589. this.disable();
  1590. }
  1591. }
  1592. /**
  1593. * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
  1594. * @param selector The selector to observe
  1595. * @param options Options for the selector observation
  1596. * @param options.listener Gets called whenever the selector was found in the DOM
  1597. * @param [options.all] Whether to use `querySelectorAll()` instead - default is false
  1598. * @param [options.continuous] Whether to call the listener continuously instead of just once - default is false
  1599. * @param [options.debounce] Whether to debounce the listener to reduce calls to `querySelector` or `querySelectorAll` - set undefined or <=0 to disable (default)
  1600. * @returns Returns a function that can be called to remove this listener more easily
  1601. */
  1602. addListener(selector, options) {
  1603. options = __spreadValues({
  1604. all: false,
  1605. continuous: false,
  1606. debounce: 0
  1607. }, options);
  1608. if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
  1609. options.listener = debounce(
  1610. options.listener,
  1611. options.debounce || this.customOptions.defaultDebounce,
  1612. options.debounceType || this.customOptions.defaultDebounceType
  1613. );
  1614. }
  1615. if (this.listenerMap.has(selector))
  1616. this.listenerMap.get(selector).push(options);
  1617. else
  1618. this.listenerMap.set(selector, [options]);
  1619. if (this.enabled === false && this.customOptions.enableOnAddListener)
  1620. this.enable();
  1621. this.checkSelector(selector, [options]);
  1622. return () => this.removeListener(selector, options);
  1623. }
  1624. /** Disables the observation of the child elements */
  1625. disable() {
  1626. var _a;
  1627. if (!this.enabled)
  1628. return;
  1629. this.enabled = false;
  1630. (_a = this.observer) == null ? undefined : _a.disconnect();
  1631. }
  1632. /**
  1633. * Enables or reenables the observation of the child elements.
  1634. * @param immediatelyCheckSelectors Whether to immediately check if all previously registered selectors exist (default is true)
  1635. * @returns Returns true when the observation was enabled, false otherwise (e.g. when the base element wasn't found)
  1636. */
  1637. enable(immediatelyCheckSelectors = true) {
  1638. var _a;
  1639. const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
  1640. if (this.enabled || !baseElement)
  1641. return false;
  1642. this.enabled = true;
  1643. (_a = this.observer) == null ? undefined : _a.observe(baseElement, this.observerOptions);
  1644. if (immediatelyCheckSelectors)
  1645. this.checkAllSelectors();
  1646. return true;
  1647. }
  1648. /** Returns whether the observation of the child elements is currently enabled */
  1649. isEnabled() {
  1650. return this.enabled;
  1651. }
  1652. /** Removes all listeners that have been registered with {@linkcode addListener()} */
  1653. clearListeners() {
  1654. this.listenerMap.clear();
  1655. }
  1656. /**
  1657. * Removes all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()}
  1658. * @returns Returns true when all listeners for the associated selector were found and removed, false otherwise
  1659. */
  1660. removeAllListeners(selector) {
  1661. return this.listenerMap.delete(selector);
  1662. }
  1663. /**
  1664. * Removes a single listener for the given {@linkcode selector} and {@linkcode options} that has been registered with {@linkcode addListener()}
  1665. * @returns Returns true when the listener was found and removed, false otherwise
  1666. */
  1667. removeListener(selector, options) {
  1668. const listeners = this.listenerMap.get(selector);
  1669. if (!listeners)
  1670. return false;
  1671. const index = listeners.indexOf(options);
  1672. if (index > -1) {
  1673. listeners.splice(index, 1);
  1674. return true;
  1675. }
  1676. return false;
  1677. }
  1678. /** Returns all listeners that have been registered with {@linkcode addListener()} */
  1679. getAllListeners() {
  1680. return this.listenerMap;
  1681. }
  1682. /** Returns all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()} */
  1683. getListeners(selector) {
  1684. return this.listenerMap.get(selector);
  1685. }
  1686. };
  1687.  
  1688. // lib/translation.ts
  1689. var trans = {};
  1690. var valTransforms = [];
  1691. var fallbackLang;
  1692. function translate(language, key, ...trArgs) {
  1693. if (typeof language !== "string")
  1694. language = fallbackLang != null ? fallbackLang : "";
  1695. const trObj = trans[language];
  1696. if (typeof language !== "string" || language.length === 0 || typeof trObj !== "object" || trObj === null)
  1697. return fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
  1698. const transformTrVal = (trKey, trValue) => {
  1699. const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(String(trValue)));
  1700. if (tfs.length === 0)
  1701. return String(trValue);
  1702. let retStr = String(trValue);
  1703. for (const tf of tfs) {
  1704. const re = new RegExp(tf.regex);
  1705. const matches = [];
  1706. let execRes;
  1707. while ((execRes = re.exec(trValue)) !== null) {
  1708. if (matches.some((m) => m[0] === (execRes == null ? undefined : execRes[0])))
  1709. break;
  1710. matches.push(execRes);
  1711. }
  1712. retStr = String(tf.fn({
  1713. language,
  1714. trValue,
  1715. currentValue: retStr,
  1716. matches,
  1717. trKey,
  1718. trArgs
  1719. }));
  1720. }
  1721. return retStr;
  1722. };
  1723. const keyParts = key.split(".");
  1724. let value = trObj;
  1725. for (const part of keyParts) {
  1726. if (typeof value !== "object" || value === null) {
  1727. value = undefined;
  1728. break;
  1729. }
  1730. value = value == null ? undefined : value[part];
  1731. }
  1732. if (typeof value === "string")
  1733. return transformTrVal(key, value);
  1734. value = trObj == null ? undefined : trObj[key];
  1735. if (typeof value === "string")
  1736. return transformTrVal(key, value);
  1737. return fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
  1738. }
  1739. function trFor(language, key, ...args) {
  1740. const txt = translate(language, key, ...args);
  1741. if (txt === key)
  1742. return fallbackLang ? translate(fallbackLang, key, ...args) : key;
  1743. return txt;
  1744. }
  1745. function useTr(language) {
  1746. return (key, ...args) => translate(language, key, ...args);
  1747. }
  1748. function hasKey(language = fallbackLang != null ? fallbackLang : "", key) {
  1749. return tr.for(language, key) !== key;
  1750. }
  1751. function addTranslations(language, translations) {
  1752. trans[language] = JSON.parse(JSON.stringify(translations));
  1753. }
  1754. function getTranslations(language = fallbackLang != null ? fallbackLang : "") {
  1755. return trans[language];
  1756. }
  1757. var deleteTranslations = (language) => {
  1758. if (language in trans) {
  1759. delete trans[language];
  1760. return true;
  1761. }
  1762. return false;
  1763. };
  1764. function setFallbackLanguage(fallbackLanguage) {
  1765. fallbackLang = fallbackLanguage;
  1766. }
  1767. function getFallbackLanguage() {
  1768. return fallbackLang;
  1769. }
  1770. function addTransform(transform) {
  1771. const [pattern, fn] = transform;
  1772. valTransforms.push({
  1773. fn,
  1774. regex: typeof pattern === "string" ? new RegExp(pattern, "gm") : pattern
  1775. });
  1776. }
  1777. function deleteTransform(patternOrFn) {
  1778. const idx = valTransforms.findIndex(
  1779. (t) => typeof patternOrFn === "function" ? t.fn === patternOrFn : typeof patternOrFn === "string" ? t.regex.source === patternOrFn : t.regex === patternOrFn
  1780. );
  1781. if (idx !== -1) {
  1782. valTransforms.splice(idx, 1);
  1783. return true;
  1784. }
  1785. return false;
  1786. }
  1787. var templateLiteralTransform = [
  1788. /\$\{([a-zA-Z0-9$_-]+)\}/gm,
  1789. ({ matches, trArgs, trValue }) => {
  1790. const patternStart = "${", patternEnd = "}", patternRegex = /\$\{.+\}/m;
  1791. let str = String(trValue);
  1792. const eachKeyInTrString = (keys) => keys.every((key) => trValue.includes(`${patternStart}${key}${patternEnd}`));
  1793. const namedMapping = () => {
  1794. var _a;
  1795. if (!str.includes(patternStart) || typeof trArgs[0] === "undefined" || typeof trArgs[0] !== "object" || !eachKeyInTrString(Object.keys((_a = trArgs[0]) != null ? _a : {})))
  1796. return;
  1797. for (const match of matches) {
  1798. const repl = match[1] !== undefined ? trArgs[0][match[1]] : undefined;
  1799. if (typeof repl !== "undefined")
  1800. str = str.replace(match[0], String(repl));
  1801. }
  1802. };
  1803. const positionalMapping = () => {
  1804. if (!patternRegex.test(str) || !trArgs[0])
  1805. return;
  1806. let matchNum = -1;
  1807. for (const match of matches) {
  1808. matchNum++;
  1809. if (typeof trArgs[matchNum] !== "undefined")
  1810. str = str.replace(match[0], String(trArgs[matchNum]));
  1811. }
  1812. };
  1813. const isArgsObject = trArgs[0] && typeof trArgs[0] === "object" && trArgs[0] !== null && String(trArgs[0]).startsWith("[object");
  1814. if (isArgsObject && eachKeyInTrString(Object.keys(trArgs[0])))
  1815. namedMapping();
  1816. else
  1817. positionalMapping();
  1818. return str;
  1819. }
  1820. ];
  1821. var percentTransform = [
  1822. /%(\d+)/gm,
  1823. ({ matches, trArgs, trValue }) => {
  1824. let str = String(trValue);
  1825. for (const match of matches) {
  1826. const repl = match[1] !== undefined ? trArgs == null ? undefined : trArgs[Number(match[1]) - 1] : undefined;
  1827. if (typeof repl !== "undefined")
  1828. str = str.replace(match[0], String(repl));
  1829. }
  1830. return str;
  1831. }
  1832. ];
  1833. var tr = {
  1834. for: (...params) => trFor(...params),
  1835. use: (...params) => useTr(...params),
  1836. hasKey: (language = fallbackLang != null ? fallbackLang : "", key) => hasKey(language, key),
  1837. addTranslations,
  1838. getTranslations,
  1839. deleteTranslations,
  1840. setFallbackLanguage,
  1841. getFallbackLanguage,
  1842. addTransform,
  1843. deleteTransform,
  1844. transforms: {
  1845. templateLiteral: templateLiteralTransform,
  1846. percent: percentTransform
  1847. }
  1848. };
  1849.  
  1850. exports.ChecksumMismatchError = ChecksumMismatchError;
  1851. exports.DataStore = DataStore;
  1852. exports.DataStoreSerializer = DataStoreSerializer;
  1853. exports.Debouncer = Debouncer;
  1854. exports.Dialog = Dialog;
  1855. exports.MigrationError = MigrationError;
  1856. exports.NanoEmitter = NanoEmitter;
  1857. exports.PlatformError = PlatformError;
  1858. exports.SelectorObserver = SelectorObserver;
  1859. exports.UUError = UUError;
  1860. exports.addGlobalStyle = addGlobalStyle;
  1861. exports.addParent = addParent;
  1862. exports.autoPlural = autoPlural;
  1863. exports.bitSetHas = bitSetHas;
  1864. exports.clamp = clamp;
  1865. exports.compress = compress;
  1866. exports.computeHash = computeHash;
  1867. exports.consumeGen = consumeGen;
  1868. exports.consumeStringGen = consumeStringGen;
  1869. exports.darkenColor = darkenColor;
  1870. exports.debounce = debounce;
  1871. exports.decompress = decompress;
  1872. exports.defaultDialogCss = defaultDialogCss;
  1873. exports.defaultStrings = defaultStrings;
  1874. exports.digitCount = digitCount;
  1875. exports.fetchAdvanced = fetchAdvanced;
  1876. exports.getListLength = getListLength;
  1877. exports.getSiblingsFrame = getSiblingsFrame;
  1878. exports.getUnsafeWindow = getUnsafeWindow;
  1879. exports.hexToRgb = hexToRgb;
  1880. exports.insertValues = insertValues;
  1881. exports.interceptEvent = interceptEvent;
  1882. exports.interceptWindowEvent = interceptWindowEvent;
  1883. exports.isDomLoaded = isDomLoaded;
  1884. exports.isScrollable = isScrollable;
  1885. exports.lightenColor = lightenColor;
  1886. exports.mapRange = mapRange;
  1887. exports.observeElementProp = observeElementProp;
  1888. exports.onDomLoad = onDomLoad;
  1889. exports.openDialogs = openDialogs;
  1890. exports.openInNewTab = openInNewTab;
  1891. exports.pauseFor = pauseFor;
  1892. exports.preloadImages = preloadImages;
  1893. exports.probeElementStyle = probeElementStyle;
  1894. exports.randRange = randRange;
  1895. exports.randomId = randomId;
  1896. exports.randomItem = randomItem;
  1897. exports.randomItemIndex = randomItemIndex;
  1898. exports.randomizeArray = randomizeArray;
  1899. exports.rgbToHex = rgbToHex;
  1900. exports.roundFixed = roundFixed;
  1901. exports.setInnerHtmlUnsafe = setInnerHtmlUnsafe;
  1902. exports.takeRandomItem = takeRandomItem;
  1903. exports.tr = tr;
  1904.  
  1905. return exports;
  1906.  
  1907. })({});