UserUtils

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

目前为 2025-01-22 提交的版本。查看 最新版本

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

QingJ © 2025

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