@sv443-network/userutils 9.3.0 → 9.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  // ==UserLibrary==
9
9
  // @name UserUtils
10
10
  // @description General purpose DOM/GreaseMonkey library that allows you to register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and much more
11
- // @version 9.3.0
11
+ // @version 9.4.0
12
12
  // @license MIT
13
13
  // @copyright Sv443 (https://github.com/Sv443)
14
14
 
@@ -21,6 +21,8 @@
21
21
 
22
22
  var UserUtils = (function (exports) {
23
23
  var __defProp = Object.defineProperty;
24
+ var __defProps = Object.defineProperties;
25
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
24
26
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
25
27
  var __hasOwnProp = Object.prototype.hasOwnProperty;
26
28
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -36,6 +38,7 @@ var UserUtils = (function (exports) {
36
38
  }
37
39
  return a;
38
40
  };
41
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
39
42
  var __objRest = (source, exclude) => {
40
43
  var target = {};
41
44
  for (var prop in source)
@@ -268,16 +271,16 @@ var UserUtils = (function (exports) {
268
271
  function preloadImages(srcUrls, rejects = false) {
269
272
  const promises = srcUrls.map((src) => new Promise((res, rej) => {
270
273
  const image = new Image();
271
- image.src = src;
272
274
  image.addEventListener("load", () => res(image));
273
275
  image.addEventListener("error", (evt) => rejects && rej(evt));
276
+ image.src = src;
274
277
  }));
275
278
  return Promise.allSettled(promises);
276
279
  }
277
280
  function openInNewTab(href, background, additionalProps) {
278
- var _a;
279
281
  try {
280
- (_a = GM.openInTab) == null ? void 0 : _a.call(GM, href, background);
282
+ if (typeof window.GM === "object")
283
+ GM.openInTab(href, background);
281
284
  } catch (e) {
282
285
  const openElem = document.createElement("a");
283
286
  Object.assign(openElem, __spreadValues({
@@ -294,12 +297,17 @@ var UserUtils = (function (exports) {
294
297
  });
295
298
  document.body.appendChild(openElem);
296
299
  openElem.click();
297
- setTimeout(openElem.remove, 0);
300
+ setTimeout(() => {
301
+ try {
302
+ openElem.remove();
303
+ } catch (e2) {
304
+ }
305
+ }, 0);
298
306
  }
299
307
  }
300
308
  function interceptEvent(eventObject, eventName, predicate = () => true) {
301
309
  var _a;
302
- if (((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
310
+ if (typeof window.GM === "object" && ((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
303
311
  throw new PlatformError("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
304
312
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
305
313
  if (isNaN(Error.stackTraceLimit))
@@ -459,6 +467,8 @@ var UserUtils = (function (exports) {
459
467
  });
460
468
  }
461
469
  function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase = true) {
470
+ if (length < 1)
471
+ throw new RangeError("The length argument must be at least 1");
462
472
  if (radix < 2 || radix > 36)
463
473
  throw new RangeError("The radix argument must be between 2 and 36");
464
474
  let arr = [];
@@ -803,7 +813,7 @@ var UserUtils = (function (exports) {
803
813
  deserializePartial(stores, data) {
804
814
  return __async(this, null, function* () {
805
815
  const deserStores = typeof data === "string" ? JSON.parse(data) : data;
806
- if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStore))
816
+ if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStoreObj))
807
817
  throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
808
818
  for (const storeData of deserStores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
809
819
  const storeInst = this.stores.find((s) => s.id === storeData.id);
@@ -836,39 +846,56 @@ Has: ${checksum}`);
836
846
  /**
837
847
  * Loads the persistent data of the DataStore instances into the in-memory cache.
838
848
  * Also triggers the migration process if the data format has changed.
849
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be loaded
839
850
  * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
840
851
  */
841
- loadStoresData() {
852
+ loadStoresData(stores) {
842
853
  return __async(this, null, function* () {
843
- return Promise.allSettled(this.stores.map(
844
- (store) => __async(this, null, function* () {
854
+ return Promise.allSettled(
855
+ this.getStoresFiltered(stores).map((store) => __async(this, null, function* () {
845
856
  return {
846
857
  id: store.id,
847
858
  data: yield store.loadData()
848
859
  };
849
- })
850
- ));
860
+ }))
861
+ );
851
862
  });
852
863
  }
853
- /** Resets the persistent data of the DataStore instances to their default values. */
854
- resetStoresData() {
864
+ /**
865
+ * Resets the persistent and in-memory data of the DataStore instances to their default values.
866
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
867
+ */
868
+ resetStoresData(stores) {
855
869
  return __async(this, null, function* () {
856
- return Promise.allSettled(this.stores.map((store) => store.saveDefaultData()));
870
+ return Promise.allSettled(
871
+ this.getStoresFiltered(stores).map((store) => store.saveDefaultData())
872
+ );
857
873
  });
858
874
  }
859
875
  /**
860
876
  * Deletes the persistent data of the DataStore instances.
861
- * Leaves the in-memory data untouched.
877
+ * Leaves the in-memory data untouched.
878
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
862
879
  */
863
- deleteStoresData() {
880
+ deleteStoresData(stores) {
864
881
  return __async(this, null, function* () {
865
- return Promise.allSettled(this.stores.map((store) => store.deleteData()));
882
+ return Promise.allSettled(
883
+ this.getStoresFiltered(stores).map((store) => store.deleteData())
884
+ );
866
885
  });
867
886
  }
887
+ /** Checks if a given value is an array of SerializedDataStore objects */
888
+ static isSerializedDataStoreObjArray(obj) {
889
+ return Array.isArray(obj) && obj.every((o) => typeof o === "object" && o !== null && "id" in o && "data" in o && "formatVersion" in o && "encoded" in o);
890
+ }
868
891
  /** Checks if a given value is a SerializedDataStore object */
869
- static isSerializedDataStore(obj) {
892
+ static isSerializedDataStoreObj(obj) {
870
893
  return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
871
894
  }
895
+ /** Returns the DataStore instances whose IDs match the provided array or function */
896
+ getStoresFiltered(stores) {
897
+ return this.stores.filter((s) => typeof stores === "undefined" ? true : Array.isArray(stores) ? stores.includes(s.id) : stores(s.id));
898
+ }
872
899
  };
873
900
 
874
901
  // node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
@@ -891,6 +918,7 @@ Has: ${checksum}`);
891
918
 
892
919
  // lib/NanoEmitter.ts
893
920
  var NanoEmitter = class {
921
+ /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
894
922
  constructor(options = {}) {
895
923
  __publicField(this, "events", createNanoEvents());
896
924
  __publicField(this, "eventUnsubscribes", []);
@@ -899,7 +927,27 @@ Has: ${checksum}`);
899
927
  publicEmit: false
900
928
  }, options);
901
929
  }
902
- /** Subscribes to an event - returns a function that unsubscribes the event listener */
930
+ /**
931
+ * Subscribes to an event and calls the callback when it's emitted.
932
+ * @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
933
+ * @returns Returns a function that can be called to unsubscribe the event listener
934
+ * @example ```ts
935
+ * const emitter = new NanoEmitter<{
936
+ * foo: (bar: string) => void;
937
+ * }>({
938
+ * publicEmit: true,
939
+ * });
940
+ *
941
+ * let i = 0;
942
+ * const unsub = emitter.on("foo", (bar) => {
943
+ * // unsubscribe after 10 events:
944
+ * if(++i === 10) unsub();
945
+ * console.log(bar);
946
+ * });
947
+ *
948
+ * emitter.emit("foo", "bar");
949
+ * ```
950
+ */
903
951
  on(event, cb) {
904
952
  let unsub;
905
953
  const unsubProxy = () => {
@@ -912,19 +960,43 @@ Has: ${checksum}`);
912
960
  this.eventUnsubscribes.push(unsub);
913
961
  return unsubProxy;
914
962
  }
915
- /** Subscribes to an event and calls the callback or resolves the Promise only once */
963
+ /**
964
+ * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
965
+ * @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
966
+ * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
967
+ * @returns Returns a Promise that resolves with the event arguments when the event is emitted
968
+ * @example ```ts
969
+ * const emitter = new NanoEmitter<{
970
+ * foo: (bar: string) => void;
971
+ * }>();
972
+ *
973
+ * // Promise syntax:
974
+ * const [bar] = await emitter.once("foo");
975
+ * console.log(bar);
976
+ *
977
+ * // Callback syntax:
978
+ * emitter.once("foo", (bar) => console.log(bar));
979
+ * ```
980
+ */
916
981
  once(event, cb) {
917
982
  return new Promise((resolve) => {
918
983
  let unsub;
919
984
  const onceProxy = (...args) => {
920
- unsub();
921
985
  cb == null ? undefined : cb(...args);
986
+ unsub == null ? undefined : unsub();
922
987
  resolve(args);
923
988
  };
924
- unsub = this.on(event, onceProxy);
989
+ unsub = this.events.on(event, onceProxy);
990
+ this.eventUnsubscribes.push(unsub);
925
991
  });
926
992
  }
927
- /** Emits an event on this instance - Needs `publicEmit` to be set to true in the constructor! */
993
+ /**
994
+ * Emits an event on this instance.
995
+ * ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
996
+ * @param event The event to emit
997
+ * @param args The arguments to pass to the event listeners
998
+ * @returns Returns true if `publicEmit` is true and the event was emitted successfully
999
+ */
928
1000
  emit(event, ...args) {
929
1001
  if (this.emitterOptions.publicEmit) {
930
1002
  this.events.emit(event, ...args);
@@ -932,7 +1004,7 @@ Has: ${checksum}`);
932
1004
  }
933
1005
  return false;
934
1006
  }
935
- /** Unsubscribes all event listeners */
1007
+ /** Unsubscribes all event listeners from this instance */
936
1008
  unsubscribeAll() {
937
1009
  for (const unsub of this.eventUnsubscribes)
938
1010
  unsub();
@@ -972,6 +1044,10 @@ Has: ${checksum}`);
972
1044
  removeAllListeners() {
973
1045
  this.listeners = [];
974
1046
  }
1047
+ /** Returns all registered listeners */
1048
+ getListeners() {
1049
+ return this.listeners;
1050
+ }
975
1051
  //#region timeout
976
1052
  /** Sets the timeout for the debouncer */
977
1053
  setTimeout(timeout) {
@@ -1000,7 +1076,7 @@ Has: ${checksum}`);
1000
1076
  const cl = (...a) => {
1001
1077
  this.queuedCall = undefined;
1002
1078
  this.emit("call", ...a);
1003
- this.listeners.forEach((l) => l.apply(this, a));
1079
+ this.listeners.forEach((l) => l.call(this, ...a));
1004
1080
  };
1005
1081
  const setRepeatTimeout = () => {
1006
1082
  this.activeTimeout = setTimeout(() => {
@@ -1474,8 +1550,6 @@ Has: ${checksum}`);
1474
1550
  return `${term}${n === 1 ? "" : "s"}`;
1475
1551
  case "-ies":
1476
1552
  return `${String(term).slice(0, -1)}${n === 1 ? "y" : "ies"}`;
1477
- default:
1478
- return String(term);
1479
1553
  }
1480
1554
  }
1481
1555
  function insertValues(input, ...values) {
@@ -1534,6 +1608,112 @@ Has: ${checksum}`);
1534
1608
  return Object.assign(/* @__PURE__ */ Object.create(null), obj);
1535
1609
  }
1536
1610
 
1611
+ // lib/Mixins.ts
1612
+ var Mixins = class {
1613
+ /**
1614
+ * Creates a new Mixins instance.
1615
+ * @param config Configuration object to customize the behavior.
1616
+ */
1617
+ constructor(config = {}) {
1618
+ /** List of all registered mixins */
1619
+ __publicField(this, "mixins", []);
1620
+ /** Default configuration object for mixins */
1621
+ __publicField(this, "defaultMixinCfg");
1622
+ /** Whether the priorities should auto-increment if not specified */
1623
+ __publicField(this, "autoIncPrioEnabled");
1624
+ /** The current auto-increment priority counter */
1625
+ __publicField(this, "autoIncPrioCounter", /* @__PURE__ */ new Map());
1626
+ var _a, _b, _c;
1627
+ this.defaultMixinCfg = purifyObj({
1628
+ priority: (_a = config.defaultPriority) != null ? _a : 0,
1629
+ stopPropagation: (_b = config.defaultStopPropagation) != null ? _b : false,
1630
+ signal: config.defaultSignal
1631
+ });
1632
+ this.autoIncPrioEnabled = (_c = config.autoIncrementPriority) != null ? _c : false;
1633
+ }
1634
+ //#region public
1635
+ /**
1636
+ * Adds a mixin function to the given {@linkcode mixinKey}.
1637
+ * If no priority is specified, it will be calculated via the protected method {@linkcode calcPriority()} based on the constructor configuration, or fall back to the default priority.
1638
+ * @param mixinKey The key to identify the mixin function.
1639
+ * @param mixinFn The function to be called to apply the mixin. The first argument is the input value, the second argument is the context object (if any).
1640
+ * @param config Configuration object to customize the mixin behavior, or just the priority if a number is passed.
1641
+ * @returns Returns a cleanup function, to be called when this mixin is no longer needed.
1642
+ */
1643
+ add(mixinKey, mixinFn, config = purifyObj({})) {
1644
+ const calcPrio = typeof config === "number" ? config : this.calcPriority(mixinKey, config);
1645
+ const mixin = purifyObj(__spreadValues(__spreadValues(__spreadProps(__spreadValues({}, this.defaultMixinCfg), {
1646
+ key: mixinKey,
1647
+ fn: mixinFn
1648
+ }), typeof config === "object" ? config : {}), typeof calcPrio === "number" && !isNaN(calcPrio) ? { priority: calcPrio } : {}));
1649
+ this.mixins.push(mixin);
1650
+ const rem = () => {
1651
+ this.mixins = this.mixins.filter((m) => m !== mixin);
1652
+ };
1653
+ if (mixin.signal)
1654
+ mixin.signal.addEventListener("abort", rem, { once: true });
1655
+ return rem;
1656
+ }
1657
+ /** Returns a list of all added mixins with their keys and configuration objects, but not their functions */
1658
+ list() {
1659
+ return this.mixins.map((_a) => {
1660
+ var _b = _a, rest = __objRest(_b, ["fn"]);
1661
+ return rest;
1662
+ });
1663
+ }
1664
+ /**
1665
+ * Applies all mixins with the given key to the input value, respecting the priority and stopPropagation settings.
1666
+ * If additional context is set in the MixinMap, it will need to be passed as the third argument.
1667
+ * @returns The modified value after all mixins have been applied.
1668
+ */
1669
+ resolve(mixinKey, inputValue, ...inputCtx) {
1670
+ const mixins = this.mixins.filter((m) => m.key === mixinKey);
1671
+ const sortedMixins = [...mixins].sort((a, b) => b.priority - a.priority);
1672
+ let result = inputValue;
1673
+ for (let i = 0; i < sortedMixins.length; i++) {
1674
+ const mixin = sortedMixins[i];
1675
+ result = mixin.fn(result, ...inputCtx);
1676
+ if (result instanceof Promise) {
1677
+ return (() => __async(this, null, function* () {
1678
+ result = yield result;
1679
+ if (mixin.stopPropagation)
1680
+ return result;
1681
+ for (let j = i + 1; j < sortedMixins.length; j++) {
1682
+ const mixin2 = sortedMixins[j];
1683
+ result = yield mixin2.fn(result, ...inputCtx);
1684
+ if (mixin2.stopPropagation)
1685
+ break;
1686
+ }
1687
+ return result;
1688
+ }))();
1689
+ } else if (mixin.stopPropagation)
1690
+ break;
1691
+ }
1692
+ return result;
1693
+ }
1694
+ //#region protected
1695
+ /** Calculates the priority for a mixin based on the given configuration and the current auto-increment state of the instance */
1696
+ calcPriority(mixinKey, config) {
1697
+ var _a;
1698
+ if (config.priority !== undefined)
1699
+ return undefined;
1700
+ if (!this.autoIncPrioEnabled)
1701
+ return (_a = config.priority) != null ? _a : this.defaultMixinCfg.priority;
1702
+ if (!this.autoIncPrioCounter.has(mixinKey))
1703
+ this.autoIncPrioCounter.set(mixinKey, this.defaultMixinCfg.priority);
1704
+ let prio = this.autoIncPrioCounter.get(mixinKey);
1705
+ while (this.mixins.some((m) => m.key === mixinKey && m.priority === prio))
1706
+ prio++;
1707
+ this.autoIncPrioCounter.set(mixinKey, prio + 1);
1708
+ return prio;
1709
+ }
1710
+ /** Removes all mixins with the given key */
1711
+ removeAll(mixinKey) {
1712
+ this.mixins.filter((m) => m.key === mixinKey);
1713
+ this.mixins = this.mixins.filter((m) => m.key !== mixinKey);
1714
+ }
1715
+ };
1716
+
1537
1717
  // lib/SelectorObserver.ts
1538
1718
  var SelectorObserver = class {
1539
1719
  constructor(baseElement, options = {}) {
@@ -1791,15 +1971,15 @@ Has: ${checksum}`);
1791
1971
  return fallbackLang;
1792
1972
  }
1793
1973
  function addTransform(transform) {
1794
- const [pattern, fn] = transform;
1974
+ const [regex, fn] = transform;
1795
1975
  valTransforms.push({
1796
1976
  fn,
1797
- regex: typeof pattern === "string" ? new RegExp(pattern, "gm") : pattern
1977
+ regex
1798
1978
  });
1799
1979
  }
1800
1980
  function deleteTransform(patternOrFn) {
1801
1981
  const idx = valTransforms.findIndex(
1802
- (t) => typeof patternOrFn === "function" ? t.fn === patternOrFn : typeof patternOrFn === "string" ? t.regex.source === patternOrFn : t.regex === patternOrFn
1982
+ (t) => typeof patternOrFn === "function" ? t.fn === patternOrFn : t.regex === patternOrFn
1803
1983
  );
1804
1984
  if (idx !== -1) {
1805
1985
  valTransforms.splice(idx, 1);
@@ -1876,6 +2056,7 @@ Has: ${checksum}`);
1876
2056
  exports.Debouncer = Debouncer;
1877
2057
  exports.Dialog = Dialog;
1878
2058
  exports.MigrationError = MigrationError;
2059
+ exports.Mixins = Mixins;
1879
2060
  exports.NanoEmitter = NanoEmitter;
1880
2061
  exports.PlatformError = PlatformError;
1881
2062
  exports.SelectorObserver = SelectorObserver;