UserUtils

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

当前为 2024-11-16 提交的版本,查看 最新版本

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

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

QingJ © 2025

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