@sv443-network/userutils 9.2.1 → 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.2.1
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 = [];
@@ -765,14 +775,15 @@ var UserUtils = (function (exports) {
765
775
  });
766
776
  }
767
777
  /**
768
- * Serializes the data stores into a string.
778
+ * Serializes only a subset of the data stores into a string.
779
+ * @param stores An array of store IDs or functions that take a store ID and return a boolean
769
780
  * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
770
781
  * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
771
782
  */
772
- serialize(useEncoding = true, stringified = true) {
783
+ serializePartial(stores, useEncoding = true, stringified = true) {
773
784
  return __async(this, null, function* () {
774
785
  const serData = [];
775
- for (const storeInst of this.stores) {
786
+ for (const storeInst of this.stores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
776
787
  const data = useEncoding && storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
777
788
  serData.push({
778
789
  id: storeInst.id,
@@ -786,15 +797,25 @@ var UserUtils = (function (exports) {
786
797
  });
787
798
  }
788
799
  /**
789
- * Deserializes the data exported via {@linkcode serialize()} and imports it into the DataStore instances.
800
+ * Serializes the data stores into a string.
801
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
802
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
803
+ */
804
+ serialize(useEncoding = true, stringified = true) {
805
+ return __async(this, null, function* () {
806
+ return this.serializePartial(this.stores.map((s) => s.id), useEncoding, stringified);
807
+ });
808
+ }
809
+ /**
810
+ * Deserializes the data exported via {@linkcode serialize()} and imports only a subset into the DataStore instances.
790
811
  * Also triggers the migration process if the data format has changed.
791
812
  */
792
- deserialize(serializedData) {
813
+ deserializePartial(stores, data) {
793
814
  return __async(this, null, function* () {
794
- const deserStores = typeof serializedData === "string" ? JSON.parse(serializedData) : serializedData;
795
- if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStore))
815
+ const deserStores = typeof data === "string" ? JSON.parse(data) : data;
816
+ if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStoreObj))
796
817
  throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
797
- for (const storeData of deserStores) {
818
+ for (const storeData of deserStores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
798
819
  const storeInst = this.stores.find((s) => s.id === storeData.id);
799
820
  if (!storeInst)
800
821
  throw new Error(`DataStore instance with ID "${storeData.id}" not found! Make sure to provide it in the DataStoreSerializer constructor.`);
@@ -813,42 +834,68 @@ Has: ${checksum}`);
813
834
  }
814
835
  });
815
836
  }
837
+ /**
838
+ * Deserializes the data exported via {@linkcode serialize()} and imports the data into all matching DataStore instances.
839
+ * Also triggers the migration process if the data format has changed.
840
+ */
841
+ deserialize(data) {
842
+ return __async(this, null, function* () {
843
+ return this.deserializePartial(this.stores.map((s) => s.id), data);
844
+ });
845
+ }
816
846
  /**
817
847
  * Loads the persistent data of the DataStore instances into the in-memory cache.
818
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
819
850
  * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
820
851
  */
821
- loadStoresData() {
852
+ loadStoresData(stores) {
822
853
  return __async(this, null, function* () {
823
- return Promise.allSettled(this.stores.map(
824
- (store) => __async(this, null, function* () {
854
+ return Promise.allSettled(
855
+ this.getStoresFiltered(stores).map((store) => __async(this, null, function* () {
825
856
  return {
826
857
  id: store.id,
827
858
  data: yield store.loadData()
828
859
  };
829
- })
830
- ));
860
+ }))
861
+ );
831
862
  });
832
863
  }
833
- /** Resets the persistent data of the DataStore instances to their default values. */
834
- 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) {
835
869
  return __async(this, null, function* () {
836
- return Promise.allSettled(this.stores.map((store) => store.saveDefaultData()));
870
+ return Promise.allSettled(
871
+ this.getStoresFiltered(stores).map((store) => store.saveDefaultData())
872
+ );
837
873
  });
838
874
  }
839
875
  /**
840
876
  * Deletes the persistent data of the DataStore instances.
841
- * 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
842
879
  */
843
- deleteStoresData() {
880
+ deleteStoresData(stores) {
844
881
  return __async(this, null, function* () {
845
- return Promise.allSettled(this.stores.map((store) => store.deleteData()));
882
+ return Promise.allSettled(
883
+ this.getStoresFiltered(stores).map((store) => store.deleteData())
884
+ );
846
885
  });
847
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
+ }
848
891
  /** Checks if a given value is a SerializedDataStore object */
849
- static isSerializedDataStore(obj) {
892
+ static isSerializedDataStoreObj(obj) {
850
893
  return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
851
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
+ }
852
899
  };
853
900
 
854
901
  // node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
@@ -871,6 +918,7 @@ Has: ${checksum}`);
871
918
 
872
919
  // lib/NanoEmitter.ts
873
920
  var NanoEmitter = class {
921
+ /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
874
922
  constructor(options = {}) {
875
923
  __publicField(this, "events", createNanoEvents());
876
924
  __publicField(this, "eventUnsubscribes", []);
@@ -879,7 +927,27 @@ Has: ${checksum}`);
879
927
  publicEmit: false
880
928
  }, options);
881
929
  }
882
- /** 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
+ */
883
951
  on(event, cb) {
884
952
  let unsub;
885
953
  const unsubProxy = () => {
@@ -892,19 +960,43 @@ Has: ${checksum}`);
892
960
  this.eventUnsubscribes.push(unsub);
893
961
  return unsubProxy;
894
962
  }
895
- /** 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
+ */
896
981
  once(event, cb) {
897
982
  return new Promise((resolve) => {
898
983
  let unsub;
899
984
  const onceProxy = (...args) => {
900
- unsub();
901
985
  cb == null ? undefined : cb(...args);
986
+ unsub == null ? undefined : unsub();
902
987
  resolve(args);
903
988
  };
904
- unsub = this.on(event, onceProxy);
989
+ unsub = this.events.on(event, onceProxy);
990
+ this.eventUnsubscribes.push(unsub);
905
991
  });
906
992
  }
907
- /** 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
+ */
908
1000
  emit(event, ...args) {
909
1001
  if (this.emitterOptions.publicEmit) {
910
1002
  this.events.emit(event, ...args);
@@ -912,7 +1004,7 @@ Has: ${checksum}`);
912
1004
  }
913
1005
  return false;
914
1006
  }
915
- /** Unsubscribes all event listeners */
1007
+ /** Unsubscribes all event listeners from this instance */
916
1008
  unsubscribeAll() {
917
1009
  for (const unsub of this.eventUnsubscribes)
918
1010
  unsub();
@@ -952,6 +1044,10 @@ Has: ${checksum}`);
952
1044
  removeAllListeners() {
953
1045
  this.listeners = [];
954
1046
  }
1047
+ /** Returns all registered listeners */
1048
+ getListeners() {
1049
+ return this.listeners;
1050
+ }
955
1051
  //#region timeout
956
1052
  /** Sets the timeout for the debouncer */
957
1053
  setTimeout(timeout) {
@@ -980,7 +1076,7 @@ Has: ${checksum}`);
980
1076
  const cl = (...a) => {
981
1077
  this.queuedCall = undefined;
982
1078
  this.emit("call", ...a);
983
- this.listeners.forEach((l) => l.apply(this, a));
1079
+ this.listeners.forEach((l) => l.call(this, ...a));
984
1080
  };
985
1081
  const setRepeatTimeout = () => {
986
1082
  this.activeTimeout = setTimeout(() => {
@@ -1454,8 +1550,6 @@ Has: ${checksum}`);
1454
1550
  return `${term}${n === 1 ? "" : "s"}`;
1455
1551
  case "-ies":
1456
1552
  return `${String(term).slice(0, -1)}${n === 1 ? "y" : "ies"}`;
1457
- default:
1458
- return String(term);
1459
1553
  }
1460
1554
  }
1461
1555
  function insertValues(input, ...values) {
@@ -1510,6 +1604,115 @@ Has: ${checksum}`);
1510
1604
  function getListLength(obj, zeroOnInvalid = true) {
1511
1605
  return "length" in obj ? obj.length : "size" in obj ? obj.size : "count" in obj ? obj.count : zeroOnInvalid ? 0 : NaN;
1512
1606
  }
1607
+ function purifyObj(obj) {
1608
+ return Object.assign(/* @__PURE__ */ Object.create(null), obj);
1609
+ }
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
+ };
1513
1716
 
1514
1717
  // lib/SelectorObserver.ts
1515
1718
  var SelectorObserver = class {
@@ -1768,15 +1971,15 @@ Has: ${checksum}`);
1768
1971
  return fallbackLang;
1769
1972
  }
1770
1973
  function addTransform(transform) {
1771
- const [pattern, fn] = transform;
1974
+ const [regex, fn] = transform;
1772
1975
  valTransforms.push({
1773
1976
  fn,
1774
- regex: typeof pattern === "string" ? new RegExp(pattern, "gm") : pattern
1977
+ regex
1775
1978
  });
1776
1979
  }
1777
1980
  function deleteTransform(patternOrFn) {
1778
1981
  const idx = valTransforms.findIndex(
1779
- (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
1780
1983
  );
1781
1984
  if (idx !== -1) {
1782
1985
  valTransforms.splice(idx, 1);
@@ -1853,6 +2056,7 @@ Has: ${checksum}`);
1853
2056
  exports.Debouncer = Debouncer;
1854
2057
  exports.Dialog = Dialog;
1855
2058
  exports.MigrationError = MigrationError;
2059
+ exports.Mixins = Mixins;
1856
2060
  exports.NanoEmitter = NanoEmitter;
1857
2061
  exports.PlatformError = PlatformError;
1858
2062
  exports.SelectorObserver = SelectorObserver;
@@ -1891,6 +2095,7 @@ Has: ${checksum}`);
1891
2095
  exports.pauseFor = pauseFor;
1892
2096
  exports.preloadImages = preloadImages;
1893
2097
  exports.probeElementStyle = probeElementStyle;
2098
+ exports.purifyObj = purifyObj;
1894
2099
  exports.randRange = randRange;
1895
2100
  exports.randomId = randomId;
1896
2101
  exports.randomItem = randomItem;