@sv443-network/userutils 5.0.0 → 6.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 6.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - e921593: Renamed `ConfigManager` to `DataStore` to make its implied purpose as a generic JSON database more clear.
8
+ - the constructor property `defaultConfig` is now called `defaultData`
9
+ - `deleteConfig()` is now called `deleteData()`
10
+ - the internal GM storage keys will still have the prefix `_uucfg` for backwards compatibility
11
+
12
+ ### Minor Changes
13
+
14
+ - da679c6: Added function `getSiblingsFrame()` that returns a frame of an element's siblings, with a given alignment and size
15
+
16
+ ### Patch Changes
17
+
18
+ - 0c716a6: Lowered the `Error.stackTraceLimit` by a multiple of 10 to preserve memory
19
+
20
+ ## 5.0.1
21
+
22
+ ### Patch Changes
23
+
24
+ - 2b885c3: `ConfigManager.loadData()` now returns a copy of the data
25
+
3
26
  ## 5.0.0
4
27
 
5
28
  ### Major Changes
package/README.md CHANGED
@@ -10,7 +10,7 @@ If you like using this library, please consider [supporting the development ❤
10
10
  <br>
11
11
  <sub>
12
12
 
13
- View the documentation of previous major releases: [3.0.0](https://github.com/Sv443-Network/UserUtils/blob/v3.0.0/README.md), [2.0.1](https://github.com/Sv443-Network/UserUtils/blob/v2.0.1/README.md), [1.2.0](https://github.com/Sv443-Network/UserUtils/blob/v1.2.0/README.md), [0.5.3](https://github.com/Sv443-Network/UserUtils/blob/v0.5.3/README.md)
13
+ View the documentation of previous major releases: [4.2.1](https://github.com/Sv443-Network/UserUtils/blob/v4.2.1/README.md), [3.0.0](https://github.com/Sv443-Network/UserUtils/blob/v3.0.0/README.md), [2.0.1](https://github.com/Sv443-Network/UserUtils/blob/v2.0.1/README.md), [1.2.0](https://github.com/Sv443-Network/UserUtils/blob/v1.2.0/README.md), [0.5.3](https://github.com/Sv443-Network/UserUtils/blob/v0.5.3/README.md)
14
14
 
15
15
  </sub>
16
16
  </div>
@@ -34,13 +34,14 @@ View the documentation of previous major releases: [3.0.0](https://github.com/Sv
34
34
  - [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
35
35
  - [isScrollable()](#isscrollable) - check if an element has a horizontal or vertical scroll bar
36
36
  - [observeElementProp()](#observeelementprop) - observe changes to an element's property that can't be observed with MutationObserver
37
+ - [getSiblingsFrame()](#getsiblingsframe) - returns a frame of an element's siblings, with a given alignment and size
37
38
  - [**Math:**](#math)
38
39
  - [clamp()](#clamp) - constrain a number between a min and max value
39
40
  - [mapRange()](#maprange) - map a number from one range to the same spot in another range
40
41
  - [randRange()](#randrange) - generate a random number between a min and max boundary
41
42
  - [randomId()](#randomid) - generate a random ID of a given length and radix
42
43
  - [**Misc:**](#misc)
43
- - [ConfigManager](#configmanager) - class that manages persistent userscript configurations, including data migration
44
+ - [DataStore](#datastore) - class that manages a sync & async persistent JSON database, including data migration
44
45
  - [autoPlural()](#autoplural) - automatically pluralize a string
45
46
  - [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
46
47
  - [debounce()](#debounce) - call a function only once, after a given amount of time
@@ -604,7 +605,7 @@ interceptEvent(
604
605
 
605
606
  Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.
606
607
  If no predicate is specified, all events will be discarded.
607
- Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not already higher) to ensure the stack trace is preserved.
608
+ Calling this function will set the `Error.stackTraceLimit` to 100 (if it's not already higher) to ensure the stack trace is preserved.
608
609
 
609
610
  ⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
610
611
 
@@ -733,6 +734,129 @@ observeElementProp(myInput, "value", (oldValue, newValue) => {
733
734
 
734
735
  </details>
735
736
 
737
+ <br>
738
+
739
+ ### getSiblingsFrame()
740
+ Usage:
741
+ ```ts
742
+ getSiblingsFrame<
743
+ TSiblingType extends Element = HTMLElement
744
+ >(
745
+ refElement: Element,
746
+ siblingAmount: number,
747
+ refElementAlignment: "center-top" | "center-bottom" | "top" | "bottom" = "center-top",
748
+ includeRef = true
749
+ ): TSiblingType[]
750
+ ```
751
+ Returns a "frame" of the closest siblings of the reference element, based on the passed amount of siblings and element alignment.
752
+ The returned type is an array of `HTMLElement` by default but can be changed by specifying the `TSiblingType` generic in TypeScript.
753
+
754
+ These are the parameters:
755
+ - The `refElement` parameter is the reference element to return the relative closest siblings from.
756
+ - The `siblingAmount` parameter is the amount of siblings to return in total (including or excluding the `refElement` based on the `includeRef` parameter).
757
+ - The `refElementAlignment` parameter can be set to `center-top` (default), `center-bottom`, `top`, or `bottom`, which will determine where the relative location of the provided `refElement` is in the returned array.
758
+ `center-top` (default) will try to keep the `refElement` in the center of the returned array, but can shift around by one element. In those cases it will prefer the top spot.
759
+ Same goes for `center-bottom` in reverse.
760
+ `top` will keep the `refElement` at the top of the returned array, and `bottom` will keep it at the bottom.
761
+ - If `includeRef` is set to `true` (default), the provided `refElement` will be included in the returned array at its corresponding position.
762
+
763
+ <details><summary><b>Example - click to view</b></summary>
764
+
765
+ ```ts
766
+ import { getSiblingsFrame } from "@sv443-network/userutils";
767
+
768
+ const refElement = document.querySelector("#ref");
769
+ // ^ structure of the elements:
770
+ // <div id="parent">
771
+ // <div>1</div>
772
+ // <div>2</div>
773
+ // <div id="ref">3</div>
774
+ // <div>4</div>
775
+ // <div>5</div>
776
+ // <div>6</div>
777
+ // </div>
778
+
779
+ // ref element aligned to the top of the frame's center positions and included in the result:
780
+ const siblingsFoo = getSiblingsFrame(refElement, 4, "center-top", true);
781
+ // <div>1</div>
782
+ // <div>2</div> ◄──┐
783
+ // <div id="ref">3</div> │ returned <(ref is here because refElementAlignment = "center-top")
784
+ // <div>4</div> │ frame
785
+ // <div>5</div> ◄──┘
786
+ // <div>6</div>
787
+
788
+ // ref element aligned to the bottom of the frame's center positions and included in the result:
789
+ const siblingsBar = getSiblingsFrame(refElement, 4, "center-bottom", true);
790
+ // <div>1</div> ◄──┐
791
+ // <div>2</div> │ returned
792
+ // <div id="ref">3</div> │ frame <(ref is here because refElementAlignment = "center-bottom")
793
+ // <div>4</div> ◄──┘
794
+ // <div>5</div>
795
+ // <div>6</div>
796
+
797
+ // ref element aligned to the bottom of the frame's center positions, but excluded from the result:
798
+ const siblingsBaz = getSiblingsFrame(refElement, 3, "center-bottom", false);
799
+ // <div>1</div> ◄──┐
800
+ // <div>2</div> ◄──┘ returned...
801
+ // <div id="ref">3</div> <(skipped because includeRef = false)
802
+ // <div>4</div> ◄─── ...frame
803
+ // <div>5</div>
804
+ // <div>6</div>
805
+
806
+ // ref element aligned to the top of the frame, but excluded from the result:
807
+ const siblingsQux = getSiblingsFrame(refElement, 3, "top", false);
808
+ // <div>1</div>
809
+ // <div>2</div>
810
+ // <div id="ref">3</div> <(skipped because includeRef = false)
811
+ // <div>4</div> ◄──┐ returned
812
+ // <div>5</div> │ frame
813
+ // <div>6</div> ◄──┘
814
+
815
+ // ref element aligned to the top of the frame, but this time included in the result:
816
+ const siblingsQuux = getSiblingsFrame(refElement, 3, "top", true);
817
+ // <div>1</div>
818
+ // <div>2</div>
819
+ // <div id="ref">3</div> ◄──┐ returned <(not skipped because includeRef = true)
820
+ // <div>4</div> │ frame
821
+ // <div>5</div> ◄──┘
822
+ // <div>6</div>
823
+ ```
824
+
825
+ More useful examples:
826
+
827
+ ```ts
828
+ const refElement = document.querySelector("#ref");
829
+ // ^ structure of the elements:
830
+ // <div id="parent">
831
+ // <div>1</div>
832
+ // <div>2</div>
833
+ // <div id="ref">3</div>
834
+ // <div>4</div>
835
+ // <div>5</div>
836
+ // <div>6</div>
837
+ // </div>
838
+
839
+ // get all elements above and include the reference element:
840
+ const allAbove = getSiblingsFrame(refElement, Infinity, "top", true);
841
+ // <div>1</div> ◄──┐ returned
842
+ // <div>2</div> │ frame
843
+ // <div id="ref">3</div> ◄──┘
844
+ // <div>4</div>
845
+ // <div>5</div>
846
+ // <div>6</div>
847
+
848
+ // get all elements below and exclude the reference element:
849
+ const allBelowExcl = getSiblingsFrame(refElement, Infinity, "bottom", false);
850
+ // <div>1</div>
851
+ // <div>2</div>
852
+ // <div id="ref">3</div>
853
+ // <div>4</div> ◄──┐ returned
854
+ // <div>5</div> │ frame
855
+ // <div>6</div> ◄──┘
856
+ ```
857
+
858
+ </details>
859
+
736
860
  <br><br>
737
861
 
738
862
  <!-- #SECTION Math -->
@@ -853,26 +977,26 @@ randomId(10, 36); // "z46jfpa37r" (length 10, radix 36)
853
977
  <!-- #SECTION Misc -->
854
978
  ## Misc:
855
979
 
856
- ### ConfigManager
980
+ ### DataStore
857
981
  Usage:
858
982
  ```ts
859
- new ConfigManager(options: ConfigManagerOptions)
983
+ new DataStore(options: DataStoreOptions)
860
984
  ```
861
985
 
862
- A class that manages a userscript's configuration that is persistently saved to and loaded from GM storage.
986
+ A class that manages a sync & async JSON database that is persistently saved to and loaded from GM storage.
863
987
  Also supports automatic migration of outdated data formats via provided migration functions.
864
988
  You may create as many instances as you like as long as they have different IDs.
865
989
 
866
- ⚠️ The configuration is stored as a JSON string, so only JSON-compatible data can be used. Circular structures and complex objects will throw an error on load and save.
990
+ ⚠️ The data is stored as a JSON string, so only JSON-compatible data can be used. Circular structures and complex objects will throw an error on load and save.
867
991
  ⚠️ The directives `@grant GM.getValue` and `@grant GM.setValue` are required for this to work.
868
992
 
869
993
  The options object has the following properties:
870
994
  | Property | Description |
871
995
  | :-- | :-- |
872
- | `id` | A unique internal identification string for this configuration. If two ConfigManagers share the same ID, they will overwrite each other's data. Choose wisely because if it is changed, the previously saved data will not be able to be loaded anymore. |
873
- | `defaultConfig` | The default config data to use if no data is saved in persistent storage yet. Until the data is loaded from persistent storage, this will be the data returned by `getData()`. For TypeScript, the type of the data passed here is what will be used for all other methods of the instance. |
996
+ | `id` | A unique internal identification string for this instance. If two DataStores share the same ID, they will overwrite each other's data. Choose wisely because if it is changed, the previously saved data will not be able to be loaded anymore. |
997
+ | `defaultData` | The default data to use if no data is saved in persistent storage yet. Until the data is loaded from persistent storage, this will be the data returned by `getData()`. For TypeScript, the type of the data passed here is what will be used for all other methods of the instance. |
874
998
  | `formatVersion` | An incremental version of the data format. If the format of the data is changed in any way, this number should be incremented, in which case all necessary functions of the migrations dictionary will be run consecutively. Never decrement this number or skip numbers. |
875
- | `migrations?` | (Optional) A dictionary of functions that can be used to migrate data from older versions of the configuration to newer ones. The keys of the dictionary should be the format version that the functions can migrate to, from the previous whole integer value. The values should be functions that take the data in the old format and return the data in the new format. The functions will be run in order from the oldest to the newest version. If the current format version is not in the dictionary, no migrations will be run. |
999
+ | `migrations?` | (Optional) A dictionary of functions that can be used to migrate data from older versions of the data to newer ones. The keys of the dictionary should be the format version that the functions can migrate to, from the previous whole integer value. The values should be functions that take the data in the old format and return the data in the new format. The functions will be run in order from the oldest to the newest version. If the current format version is not in the dictionary, no migrations will be run. |
876
1000
  | `encodeData?` | (Optional, but required when decodeData is set) Function that encodes the data before saving - you can use [compress()](#compress) here to save space at the cost of a little bit of performance |
877
1001
  | `decodeData?` | (Optional, but required when encodeData is set) Function that decodes the data when loading - you can use [decompress()](#decompress) here to decode data that was previously compressed with [compress()](#compress) |
878
1002
 
@@ -880,24 +1004,24 @@ The options object has the following properties:
880
1004
 
881
1005
  #### Methods:
882
1006
  `loadData(): Promise<TData>`
883
- Asynchronously loads the configuration data from persistent storage and returns it.
884
- If no data was saved in persistent storage before, the value of `options.defaultConfig` will be returned and written to persistent storage.
1007
+ Asynchronously loads the data from persistent storage and returns it.
1008
+ If no data was saved in persistent storage before, the value of `options.defaultData` will be returned and written to persistent storage.
885
1009
  If the formatVersion of the saved data is lower than the current one and the `options.migrations` property is present, the data will be migrated to the latest format before the Promise resolves.
886
1010
 
887
1011
  `getData(): TData`
888
1012
  Synchronously returns the current data that is stored in the internal cache.
889
- If no data was loaded from persistent storage yet using `loadData()`, the value of `options.defaultConfig` will be returned.
1013
+ If no data was loaded from persistent storage yet using `loadData()`, the value of `options.defaultData` will be returned.
890
1014
 
891
1015
  `setData(data: TData): Promise<void>`
892
1016
  Writes the given data synchronously to the internal cache and asynchronously to persistent storage.
893
1017
 
894
1018
  `saveDefaultData(): Promise<void>`
895
- Writes the default configuration given in `options.defaultConfig` synchronously to the internal cache and asynchronously to persistent storage.
1019
+ Writes the default data given in `options.defaultData` synchronously to the internal cache and asynchronously to persistent storage.
896
1020
 
897
- `deleteConfig(): Promise<void>`
898
- Fully deletes the configuration from persistent storage.
1021
+ `deleteData(): Promise<void>`
1022
+ Fully deletes the data from persistent storage.
899
1023
  The internal cache will be left untouched, so any subsequent calls to `getData()` will return the data that was last loaded.
900
- If `loadData()` or `setData()` are called after this, the persistent storage will be populated with the value of `options.defaultConfig` again.
1024
+ If `loadData()` or `setData()` are called after this, the persistent storage will be populated with the value of `options.defaultData` again.
901
1025
  ⚠️ If you want to use this method, the additional directive `@grant GM.deleteValue` is required.
902
1026
 
903
1027
  <br>
@@ -905,8 +1029,9 @@ If `loadData()` or `setData()` are called after this, the persistent storage wil
905
1029
  <details><summary><b>Example - click to view</b></summary>
906
1030
 
907
1031
  ```ts
908
- import { ConfigManager, compress, decompress } from "@sv443-network/userutils";
1032
+ import { DataStore, compress, decompress } from "@sv443-network/userutils";
909
1033
 
1034
+ /** Example: Userscript configuration data */
910
1035
  interface MyConfig {
911
1036
  foo: string;
912
1037
  bar: number;
@@ -914,8 +1039,8 @@ interface MyConfig {
914
1039
  qux: string;
915
1040
  }
916
1041
 
917
- /** Default config data */
918
- const defaultConfig: MyConfig = {
1042
+ /** Default data */
1043
+ const defaultData: MyConfig = {
919
1044
  foo: "hello",
920
1045
  bar: 42,
921
1046
  baz: "xyz",
@@ -946,12 +1071,12 @@ const migrations = {
946
1071
  },
947
1072
  };
948
1073
 
949
- const manager = new ConfigManager({
950
- /** A unique ID for this configuration - choose wisely as changing it is not supported yet! */
951
- id: "my-userscript",
952
- /** Default / fallback configuration data */
953
- defaultConfig,
954
- /** The current version of the script's config data format */
1074
+ const manager = new DataStore({
1075
+ /** A unique ID for this instance - choose wisely as changing it is not supported yet! */
1076
+ id: "my-userscript-config",
1077
+ /** Default / fallback data */
1078
+ defaultData,
1079
+ /** The current version of the data format */
955
1080
  formatVersion,
956
1081
  /** Data format migration functions */
957
1082
  migrations,
@@ -959,29 +1084,30 @@ const manager = new ConfigManager({
959
1084
  // Compression example:
960
1085
  // Adding this will save space at the cost of a little bit of performance while initially loading and saving the data
961
1086
  // Only both of these properties or none of them should be set
962
- // Everything else will be handled by the ConfigManager instance
963
- /** Encode data using the "deflate-raw" algorithm and digests it as a base64 string */
1087
+ // Everything else will be handled by the DataStore instance
1088
+
1089
+ /** Encodes data using the "deflate-raw" algorithm and digests it as a base64 string */
964
1090
  encodeData: (data) => compress(data, "deflate-raw", "base64"),
965
- /** Decode the "deflate-raw" encoded data as a base64 string */
1091
+ /** Decodes the "deflate-raw" encoded data as a base64 string */
966
1092
  decodeData: (data) => decompress(data, "deflate-raw", "base64"),
967
1093
  });
968
1094
 
969
1095
  /** Entrypoint of the userscript */
970
1096
  async function init() {
971
- // wait for the config to be loaded from persistent storage
972
- // if no data was saved in persistent storage before or getData() is called before loadData(), the value of options.defaultConfig will be returned
1097
+ // wait for the data to be loaded from persistent storage
1098
+ // if no data was saved in persistent storage before or getData() is called before loadData(), the value of options.defaultData will be returned
973
1099
  // if the previously saved data needs to be migrated to a newer version, it will happen in this function call
974
1100
  const configData = await manager.loadData();
975
1101
 
976
1102
  console.log(configData.foo); // "hello"
977
1103
 
978
- // update the config
1104
+ // update the data
979
1105
  configData.foo = "world";
980
1106
  configData.bar = 123;
981
1107
 
982
- // save the updated config - synchronously to the cache and asynchronously to persistent storage
1108
+ // save the updated data - synchronously to the cache and asynchronously to persistent storage
983
1109
  manager.saveData(configData).then(() => {
984
- console.log("Config saved to persistent storage!");
1110
+ console.log("Data saved to persistent storage!");
985
1111
  });
986
1112
 
987
1113
  // the internal cache is updated synchronously, so the updated data can be accessed before the Promise resolves:
@@ -3,13 +3,12 @@
3
3
  // @exclude *
4
4
  // @author Sv443
5
5
  // @supportURL https://github.com/Sv443-Network/UserUtils/issues
6
- // @homepageURL https://github.com/Sv443-Network/UserUtils#readme
7
- // @supportURL https://github.com/Sv443-Network/UserUtils/issues
6
+ // @homepageURL https://github.com/Sv443-Network/UserUtils
8
7
 
9
8
  // ==UserLibrary==
10
9
  // @name UserUtils
11
10
  // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more
12
- // @version 5.0.0
11
+ // @version 6.0.0
13
12
  // @license MIT
14
13
  // @copyright Sv443 (https://github.com/Sv443)
15
14
 
@@ -137,30 +136,30 @@ var UserUtils = (function (exports) {
137
136
  return retArray;
138
137
  }
139
138
 
140
- // lib/ConfigManager.ts
141
- var ConfigManager = class {
139
+ // lib/DataStore.ts
140
+ var DataStore = class {
142
141
  /**
143
- * Creates an instance of ConfigManager to manage a user configuration that is cached in memory and persistently saved across sessions.
144
- * Supports migrating data from older versions of the configuration to newer ones and populating the cache with default data if no persistent data is found.
142
+ * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
143
+ * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
145
144
  *
146
145
  * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue`
147
- * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultConfig`
146
+ * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
148
147
  *
149
- * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultConfig`) - this should also be the type of the data format associated with the current `options.formatVersion`
150
- * @param options The options for this ConfigManager instance
148
+ * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultData`) - this should also be the type of the data format associated with the current `options.formatVersion`
149
+ * @param options The options for this DataStore instance
151
150
  */
152
151
  constructor(options) {
153
152
  __publicField(this, "id");
154
153
  __publicField(this, "formatVersion");
155
- __publicField(this, "defaultConfig");
156
- __publicField(this, "cachedConfig");
154
+ __publicField(this, "defaultData");
155
+ __publicField(this, "cachedData");
157
156
  __publicField(this, "migrations");
158
157
  __publicField(this, "encodeData");
159
158
  __publicField(this, "decodeData");
160
159
  this.id = options.id;
161
160
  this.formatVersion = options.formatVersion;
162
- this.defaultConfig = options.defaultConfig;
163
- this.cachedConfig = options.defaultConfig;
161
+ this.defaultData = options.defaultData;
162
+ this.cachedData = options.defaultData;
164
163
  this.migrations = options.migrations;
165
164
  this.encodeData = options.encodeData;
166
165
  this.decodeData = options.decodeData;
@@ -173,11 +172,11 @@ var UserUtils = (function (exports) {
173
172
  loadData() {
174
173
  return __async(this, null, function* () {
175
174
  try {
176
- const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultConfig);
175
+ const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultData);
177
176
  let gmFmtVer = Number(yield GM.getValue(`_uucfgver-${this.id}`));
178
177
  if (typeof gmData !== "string") {
179
178
  yield this.saveDefaultData();
180
- return this.defaultConfig;
179
+ return __spreadValues({}, this.defaultData);
181
180
  }
182
181
  const isEncoded = yield GM.getValue(`_uucfgenc-${this.id}`, false);
183
182
  if (isNaN(gmFmtVer))
@@ -185,11 +184,11 @@ var UserUtils = (function (exports) {
185
184
  let parsed = yield this.deserializeData(gmData, isEncoded);
186
185
  if (gmFmtVer < this.formatVersion && this.migrations)
187
186
  parsed = yield this.runMigrations(parsed, gmFmtVer);
188
- return this.cachedConfig = parsed;
187
+ return __spreadValues({}, this.cachedData = parsed);
189
188
  } catch (err) {
190
- console.warn("Error while loading config data, resetting it to the default value.", err);
189
+ console.warn("Error while parsing JSON data, resetting it to the default value.", err);
191
190
  yield this.saveDefaultData();
192
- return this.defaultConfig;
191
+ return this.defaultData;
193
192
  }
194
193
  });
195
194
  }
@@ -198,11 +197,11 @@ var UserUtils = (function (exports) {
198
197
  * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
199
198
  */
200
199
  getData() {
201
- return this.deepCopy(this.cachedConfig);
200
+ return this.deepCopy(this.cachedData);
202
201
  }
203
202
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
204
203
  setData(data) {
205
- this.cachedConfig = data;
204
+ this.cachedData = data;
206
205
  const useEncoding = Boolean(this.encodeData && this.decodeData);
207
206
  return new Promise((resolve) => __async(this, null, function* () {
208
207
  yield Promise.all([
@@ -216,11 +215,11 @@ var UserUtils = (function (exports) {
216
215
  /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
217
216
  saveDefaultData() {
218
217
  return __async(this, null, function* () {
219
- this.cachedConfig = this.defaultConfig;
218
+ this.cachedData = this.defaultData;
220
219
  const useEncoding = Boolean(this.encodeData && this.decodeData);
221
220
  return new Promise((resolve) => __async(this, null, function* () {
222
221
  yield Promise.all([
223
- GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultConfig, useEncoding)),
222
+ GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
224
223
  GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
225
224
  GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
226
225
  ]);
@@ -229,13 +228,13 @@ var UserUtils = (function (exports) {
229
228
  });
230
229
  }
231
230
  /**
232
- * Call this method to clear all persistently stored data associated with this ConfigManager instance.
231
+ * Call this method to clear all persistently stored data associated with this DataStore instance.
233
232
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
234
233
  * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
235
234
  *
236
235
  * ⚠️ This requires the additional directive `@grant GM.deleteValue`
237
236
  */
238
- deleteConfig() {
237
+ deleteData() {
239
238
  return __async(this, null, function* () {
240
239
  yield Promise.all([
241
240
  GM.deleteValue(`_uucfg-${this.id}`),
@@ -351,9 +350,9 @@ var UserUtils = (function (exports) {
351
350
  setTimeout(openElem.remove, 50);
352
351
  }
353
352
  function interceptEvent(eventObject, eventName, predicate = () => true) {
354
- if (typeof Error.stackTraceLimit === "number" && Error.stackTraceLimit < 1e3) {
355
- Error.stackTraceLimit = 1e3;
356
- }
353
+ Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
354
+ if (isNaN(Error.stackTraceLimit))
355
+ Error.stackTraceLimit = 100;
357
356
  (function(original) {
358
357
  eventObject.__proto__.addEventListener = function(...args) {
359
358
  var _a, _b;
@@ -400,6 +399,27 @@ var UserUtils = (function (exports) {
400
399
  });
401
400
  }
402
401
  }
402
+ function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
403
+ var _a, _b;
404
+ const siblings = [...(_b = (_a = refElement.parentNode) == null ? void 0 : _a.childNodes) != null ? _b : []];
405
+ const elemSiblIdx = siblings.indexOf(refElement);
406
+ if (elemSiblIdx === -1)
407
+ throw new Error("Element doesn't have a parent node");
408
+ if (refElementAlignment === "top")
409
+ return [...siblings.slice(elemSiblIdx + Number(!includeRef), elemSiblIdx + siblingAmount + Number(!includeRef))];
410
+ else if (refElementAlignment.startsWith("center-")) {
411
+ const halfAmount = (refElementAlignment === "center-bottom" ? Math.ceil : Math.floor)(siblingAmount / 2);
412
+ const startIdx = Math.max(0, elemSiblIdx - halfAmount);
413
+ const topOffset = Number(refElementAlignment === "center-top" && siblingAmount % 2 === 0 && includeRef);
414
+ const btmOffset = Number(refElementAlignment === "center-bottom" && siblingAmount % 2 !== 0 && includeRef);
415
+ const startIdxWithOffset = startIdx + topOffset + btmOffset;
416
+ return [
417
+ ...siblings.filter((_, idx) => includeRef || idx !== elemSiblIdx).slice(startIdxWithOffset, startIdxWithOffset + siblingAmount)
418
+ ];
419
+ } else if (refElementAlignment === "bottom")
420
+ return [...siblings.slice(elemSiblIdx - siblingAmount + Number(includeRef), elemSiblIdx + Number(includeRef))];
421
+ return [];
422
+ }
403
423
 
404
424
  // lib/misc.ts
405
425
  function autoPlural(word, num) {
@@ -656,7 +676,7 @@ var UserUtils = (function (exports) {
656
676
  return curLang;
657
677
  };
658
678
 
659
- exports.ConfigManager = ConfigManager;
679
+ exports.DataStore = DataStore;
660
680
  exports.SelectorObserver = SelectorObserver;
661
681
  exports.addGlobalStyle = addGlobalStyle;
662
682
  exports.addParent = addParent;
@@ -666,6 +686,7 @@ var UserUtils = (function (exports) {
666
686
  exports.debounce = debounce;
667
687
  exports.decompress = decompress;
668
688
  exports.fetchAdvanced = fetchAdvanced;
689
+ exports.getSiblingsFrame = getSiblingsFrame;
669
690
  exports.getUnsafeWindow = getUnsafeWindow;
670
691
  exports.insertAfter = insertAfter;
671
692
  exports.insertValues = insertValues;
package/dist/index.js CHANGED
@@ -116,30 +116,30 @@ function randomizeArray(array) {
116
116
  return retArray;
117
117
  }
118
118
 
119
- // lib/ConfigManager.ts
120
- var ConfigManager = class {
119
+ // lib/DataStore.ts
120
+ var DataStore = class {
121
121
  /**
122
- * Creates an instance of ConfigManager to manage a user configuration that is cached in memory and persistently saved across sessions.
123
- * Supports migrating data from older versions of the configuration to newer ones and populating the cache with default data if no persistent data is found.
122
+ * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
123
+ * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
124
124
  *
125
125
  * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue`
126
- * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultConfig`
126
+ * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
127
127
  *
128
- * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultConfig`) - this should also be the type of the data format associated with the current `options.formatVersion`
129
- * @param options The options for this ConfigManager instance
128
+ * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultData`) - this should also be the type of the data format associated with the current `options.formatVersion`
129
+ * @param options The options for this DataStore instance
130
130
  */
131
131
  constructor(options) {
132
132
  __publicField(this, "id");
133
133
  __publicField(this, "formatVersion");
134
- __publicField(this, "defaultConfig");
135
- __publicField(this, "cachedConfig");
134
+ __publicField(this, "defaultData");
135
+ __publicField(this, "cachedData");
136
136
  __publicField(this, "migrations");
137
137
  __publicField(this, "encodeData");
138
138
  __publicField(this, "decodeData");
139
139
  this.id = options.id;
140
140
  this.formatVersion = options.formatVersion;
141
- this.defaultConfig = options.defaultConfig;
142
- this.cachedConfig = options.defaultConfig;
141
+ this.defaultData = options.defaultData;
142
+ this.cachedData = options.defaultData;
143
143
  this.migrations = options.migrations;
144
144
  this.encodeData = options.encodeData;
145
145
  this.decodeData = options.decodeData;
@@ -152,11 +152,11 @@ var ConfigManager = class {
152
152
  loadData() {
153
153
  return __async(this, null, function* () {
154
154
  try {
155
- const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultConfig);
155
+ const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultData);
156
156
  let gmFmtVer = Number(yield GM.getValue(`_uucfgver-${this.id}`));
157
157
  if (typeof gmData !== "string") {
158
158
  yield this.saveDefaultData();
159
- return this.defaultConfig;
159
+ return __spreadValues({}, this.defaultData);
160
160
  }
161
161
  const isEncoded = yield GM.getValue(`_uucfgenc-${this.id}`, false);
162
162
  if (isNaN(gmFmtVer))
@@ -164,11 +164,11 @@ var ConfigManager = class {
164
164
  let parsed = yield this.deserializeData(gmData, isEncoded);
165
165
  if (gmFmtVer < this.formatVersion && this.migrations)
166
166
  parsed = yield this.runMigrations(parsed, gmFmtVer);
167
- return this.cachedConfig = parsed;
167
+ return __spreadValues({}, this.cachedData = parsed);
168
168
  } catch (err) {
169
- console.warn("Error while loading config data, resetting it to the default value.", err);
169
+ console.warn("Error while parsing JSON data, resetting it to the default value.", err);
170
170
  yield this.saveDefaultData();
171
- return this.defaultConfig;
171
+ return this.defaultData;
172
172
  }
173
173
  });
174
174
  }
@@ -177,11 +177,11 @@ var ConfigManager = class {
177
177
  * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
178
178
  */
179
179
  getData() {
180
- return this.deepCopy(this.cachedConfig);
180
+ return this.deepCopy(this.cachedData);
181
181
  }
182
182
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
183
183
  setData(data) {
184
- this.cachedConfig = data;
184
+ this.cachedData = data;
185
185
  const useEncoding = Boolean(this.encodeData && this.decodeData);
186
186
  return new Promise((resolve) => __async(this, null, function* () {
187
187
  yield Promise.all([
@@ -195,11 +195,11 @@ var ConfigManager = class {
195
195
  /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
196
196
  saveDefaultData() {
197
197
  return __async(this, null, function* () {
198
- this.cachedConfig = this.defaultConfig;
198
+ this.cachedData = this.defaultData;
199
199
  const useEncoding = Boolean(this.encodeData && this.decodeData);
200
200
  return new Promise((resolve) => __async(this, null, function* () {
201
201
  yield Promise.all([
202
- GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultConfig, useEncoding)),
202
+ GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
203
203
  GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
204
204
  GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
205
205
  ]);
@@ -208,13 +208,13 @@ var ConfigManager = class {
208
208
  });
209
209
  }
210
210
  /**
211
- * Call this method to clear all persistently stored data associated with this ConfigManager instance.
211
+ * Call this method to clear all persistently stored data associated with this DataStore instance.
212
212
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
213
213
  * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
214
214
  *
215
215
  * ⚠️ This requires the additional directive `@grant GM.deleteValue`
216
216
  */
217
- deleteConfig() {
217
+ deleteData() {
218
218
  return __async(this, null, function* () {
219
219
  yield Promise.all([
220
220
  GM.deleteValue(`_uucfg-${this.id}`),
@@ -330,9 +330,9 @@ function openInNewTab(href) {
330
330
  setTimeout(openElem.remove, 50);
331
331
  }
332
332
  function interceptEvent(eventObject, eventName, predicate = () => true) {
333
- if (typeof Error.stackTraceLimit === "number" && Error.stackTraceLimit < 1e3) {
334
- Error.stackTraceLimit = 1e3;
335
- }
333
+ Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
334
+ if (isNaN(Error.stackTraceLimit))
335
+ Error.stackTraceLimit = 100;
336
336
  (function(original) {
337
337
  eventObject.__proto__.addEventListener = function(...args) {
338
338
  var _a, _b;
@@ -379,6 +379,27 @@ function observeElementProp(element, property, callback) {
379
379
  });
380
380
  }
381
381
  }
382
+ function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
383
+ var _a, _b;
384
+ const siblings = [...(_b = (_a = refElement.parentNode) == null ? void 0 : _a.childNodes) != null ? _b : []];
385
+ const elemSiblIdx = siblings.indexOf(refElement);
386
+ if (elemSiblIdx === -1)
387
+ throw new Error("Element doesn't have a parent node");
388
+ if (refElementAlignment === "top")
389
+ return [...siblings.slice(elemSiblIdx + Number(!includeRef), elemSiblIdx + siblingAmount + Number(!includeRef))];
390
+ else if (refElementAlignment.startsWith("center-")) {
391
+ const halfAmount = (refElementAlignment === "center-bottom" ? Math.ceil : Math.floor)(siblingAmount / 2);
392
+ const startIdx = Math.max(0, elemSiblIdx - halfAmount);
393
+ const topOffset = Number(refElementAlignment === "center-top" && siblingAmount % 2 === 0 && includeRef);
394
+ const btmOffset = Number(refElementAlignment === "center-bottom" && siblingAmount % 2 !== 0 && includeRef);
395
+ const startIdxWithOffset = startIdx + topOffset + btmOffset;
396
+ return [
397
+ ...siblings.filter((_, idx) => includeRef || idx !== elemSiblIdx).slice(startIdxWithOffset, startIdxWithOffset + siblingAmount)
398
+ ];
399
+ } else if (refElementAlignment === "bottom")
400
+ return [...siblings.slice(elemSiblIdx - siblingAmount + Number(includeRef), elemSiblIdx + Number(includeRef))];
401
+ return [];
402
+ }
382
403
 
383
404
  // lib/misc.ts
384
405
  function autoPlural(word, num) {
@@ -635,7 +656,7 @@ tr.getLanguage = () => {
635
656
  return curLang;
636
657
  };
637
658
 
638
- exports.ConfigManager = ConfigManager;
659
+ exports.DataStore = DataStore;
639
660
  exports.SelectorObserver = SelectorObserver;
640
661
  exports.addGlobalStyle = addGlobalStyle;
641
662
  exports.addParent = addParent;
@@ -645,6 +666,7 @@ exports.compress = compress;
645
666
  exports.debounce = debounce;
646
667
  exports.decompress = decompress;
647
668
  exports.fetchAdvanced = fetchAdvanced;
669
+ exports.getSiblingsFrame = getSiblingsFrame;
648
670
  exports.getUnsafeWindow = getUnsafeWindow;
649
671
  exports.insertAfter = insertAfter;
650
672
  exports.insertValues = insertValues;
package/dist/index.mjs CHANGED
@@ -114,30 +114,30 @@ function randomizeArray(array) {
114
114
  return retArray;
115
115
  }
116
116
 
117
- // lib/ConfigManager.ts
118
- var ConfigManager = class {
117
+ // lib/DataStore.ts
118
+ var DataStore = class {
119
119
  /**
120
- * Creates an instance of ConfigManager to manage a user configuration that is cached in memory and persistently saved across sessions.
121
- * Supports migrating data from older versions of the configuration to newer ones and populating the cache with default data if no persistent data is found.
120
+ * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
121
+ * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
122
122
  *
123
123
  * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue`
124
- * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultConfig`
124
+ * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
125
125
  *
126
- * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultConfig`) - this should also be the type of the data format associated with the current `options.formatVersion`
127
- * @param options The options for this ConfigManager instance
126
+ * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultData`) - this should also be the type of the data format associated with the current `options.formatVersion`
127
+ * @param options The options for this DataStore instance
128
128
  */
129
129
  constructor(options) {
130
130
  __publicField(this, "id");
131
131
  __publicField(this, "formatVersion");
132
- __publicField(this, "defaultConfig");
133
- __publicField(this, "cachedConfig");
132
+ __publicField(this, "defaultData");
133
+ __publicField(this, "cachedData");
134
134
  __publicField(this, "migrations");
135
135
  __publicField(this, "encodeData");
136
136
  __publicField(this, "decodeData");
137
137
  this.id = options.id;
138
138
  this.formatVersion = options.formatVersion;
139
- this.defaultConfig = options.defaultConfig;
140
- this.cachedConfig = options.defaultConfig;
139
+ this.defaultData = options.defaultData;
140
+ this.cachedData = options.defaultData;
141
141
  this.migrations = options.migrations;
142
142
  this.encodeData = options.encodeData;
143
143
  this.decodeData = options.decodeData;
@@ -150,11 +150,11 @@ var ConfigManager = class {
150
150
  loadData() {
151
151
  return __async(this, null, function* () {
152
152
  try {
153
- const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultConfig);
153
+ const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultData);
154
154
  let gmFmtVer = Number(yield GM.getValue(`_uucfgver-${this.id}`));
155
155
  if (typeof gmData !== "string") {
156
156
  yield this.saveDefaultData();
157
- return this.defaultConfig;
157
+ return __spreadValues({}, this.defaultData);
158
158
  }
159
159
  const isEncoded = yield GM.getValue(`_uucfgenc-${this.id}`, false);
160
160
  if (isNaN(gmFmtVer))
@@ -162,11 +162,11 @@ var ConfigManager = class {
162
162
  let parsed = yield this.deserializeData(gmData, isEncoded);
163
163
  if (gmFmtVer < this.formatVersion && this.migrations)
164
164
  parsed = yield this.runMigrations(parsed, gmFmtVer);
165
- return this.cachedConfig = parsed;
165
+ return __spreadValues({}, this.cachedData = parsed);
166
166
  } catch (err) {
167
- console.warn("Error while loading config data, resetting it to the default value.", err);
167
+ console.warn("Error while parsing JSON data, resetting it to the default value.", err);
168
168
  yield this.saveDefaultData();
169
- return this.defaultConfig;
169
+ return this.defaultData;
170
170
  }
171
171
  });
172
172
  }
@@ -175,11 +175,11 @@ var ConfigManager = class {
175
175
  * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
176
176
  */
177
177
  getData() {
178
- return this.deepCopy(this.cachedConfig);
178
+ return this.deepCopy(this.cachedData);
179
179
  }
180
180
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
181
181
  setData(data) {
182
- this.cachedConfig = data;
182
+ this.cachedData = data;
183
183
  const useEncoding = Boolean(this.encodeData && this.decodeData);
184
184
  return new Promise((resolve) => __async(this, null, function* () {
185
185
  yield Promise.all([
@@ -193,11 +193,11 @@ var ConfigManager = class {
193
193
  /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
194
194
  saveDefaultData() {
195
195
  return __async(this, null, function* () {
196
- this.cachedConfig = this.defaultConfig;
196
+ this.cachedData = this.defaultData;
197
197
  const useEncoding = Boolean(this.encodeData && this.decodeData);
198
198
  return new Promise((resolve) => __async(this, null, function* () {
199
199
  yield Promise.all([
200
- GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultConfig, useEncoding)),
200
+ GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
201
201
  GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
202
202
  GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
203
203
  ]);
@@ -206,13 +206,13 @@ var ConfigManager = class {
206
206
  });
207
207
  }
208
208
  /**
209
- * Call this method to clear all persistently stored data associated with this ConfigManager instance.
209
+ * Call this method to clear all persistently stored data associated with this DataStore instance.
210
210
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
211
211
  * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
212
212
  *
213
213
  * ⚠️ This requires the additional directive `@grant GM.deleteValue`
214
214
  */
215
- deleteConfig() {
215
+ deleteData() {
216
216
  return __async(this, null, function* () {
217
217
  yield Promise.all([
218
218
  GM.deleteValue(`_uucfg-${this.id}`),
@@ -328,9 +328,9 @@ function openInNewTab(href) {
328
328
  setTimeout(openElem.remove, 50);
329
329
  }
330
330
  function interceptEvent(eventObject, eventName, predicate = () => true) {
331
- if (typeof Error.stackTraceLimit === "number" && Error.stackTraceLimit < 1e3) {
332
- Error.stackTraceLimit = 1e3;
333
- }
331
+ Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
332
+ if (isNaN(Error.stackTraceLimit))
333
+ Error.stackTraceLimit = 100;
334
334
  (function(original) {
335
335
  eventObject.__proto__.addEventListener = function(...args) {
336
336
  var _a, _b;
@@ -377,6 +377,27 @@ function observeElementProp(element, property, callback) {
377
377
  });
378
378
  }
379
379
  }
380
+ function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
381
+ var _a, _b;
382
+ const siblings = [...(_b = (_a = refElement.parentNode) == null ? void 0 : _a.childNodes) != null ? _b : []];
383
+ const elemSiblIdx = siblings.indexOf(refElement);
384
+ if (elemSiblIdx === -1)
385
+ throw new Error("Element doesn't have a parent node");
386
+ if (refElementAlignment === "top")
387
+ return [...siblings.slice(elemSiblIdx + Number(!includeRef), elemSiblIdx + siblingAmount + Number(!includeRef))];
388
+ else if (refElementAlignment.startsWith("center-")) {
389
+ const halfAmount = (refElementAlignment === "center-bottom" ? Math.ceil : Math.floor)(siblingAmount / 2);
390
+ const startIdx = Math.max(0, elemSiblIdx - halfAmount);
391
+ const topOffset = Number(refElementAlignment === "center-top" && siblingAmount % 2 === 0 && includeRef);
392
+ const btmOffset = Number(refElementAlignment === "center-bottom" && siblingAmount % 2 !== 0 && includeRef);
393
+ const startIdxWithOffset = startIdx + topOffset + btmOffset;
394
+ return [
395
+ ...siblings.filter((_, idx) => includeRef || idx !== elemSiblIdx).slice(startIdxWithOffset, startIdxWithOffset + siblingAmount)
396
+ ];
397
+ } else if (refElementAlignment === "bottom")
398
+ return [...siblings.slice(elemSiblIdx - siblingAmount + Number(includeRef), elemSiblIdx + Number(includeRef))];
399
+ return [];
400
+ }
380
401
 
381
402
  // lib/misc.ts
382
403
  function autoPlural(word, num) {
@@ -633,4 +654,4 @@ tr.getLanguage = () => {
633
654
  return curLang;
634
655
  };
635
656
 
636
- export { ConfigManager, SelectorObserver, addGlobalStyle, addParent, autoPlural, clamp, compress, debounce, decompress, fetchAdvanced, getUnsafeWindow, insertAfter, insertValues, interceptEvent, interceptWindowEvent, isScrollable, mapRange, observeElementProp, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, takeRandomItem, tr };
657
+ export { DataStore, SelectorObserver, addGlobalStyle, addParent, autoPlural, clamp, compress, debounce, decompress, fetchAdvanced, getSiblingsFrame, getUnsafeWindow, insertAfter, insertValues, interceptEvent, interceptWindowEvent, isScrollable, mapRange, observeElementProp, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, takeRandomItem, tr };
@@ -2,26 +2,26 @@
2
2
  type MigrationFunc = (oldData: any) => any | Promise<any>;
3
3
  /** Dictionary of format version numbers and the function that migrates to them from the previous whole integer */
4
4
  export type ConfigMigrationsDict = Record<number, MigrationFunc>;
5
- /** Options for the ConfigManager instance */
6
- export type ConfigManagerOptions<TData> = {
7
- /** A unique internal ID for this configuration - choose wisely as changing it is not supported yet. */
5
+ /** Options for the DataStore instance */
6
+ export type DataStoreOptions<TData> = {
7
+ /** A unique internal ID for this data store - choose wisely as changing it is not supported yet. */
8
8
  id: string;
9
9
  /**
10
- * The default config data object to use if no data is saved in persistent storage yet.
10
+ * The default data object to use if no data is saved in persistent storage yet.
11
11
  * Until the data is loaded from persistent storage with `loadData()`, this will be the data returned by `getData()`
12
12
  *
13
13
  * ⚠️ This has to be an object that can be serialized to JSON, so no functions or circular references are allowed, they will cause unexpected behavior.
14
14
  */
15
- defaultConfig: TData;
15
+ defaultData: TData;
16
16
  /**
17
- * An incremental, whole integer version number of the current format of config data.
17
+ * An incremental, whole integer version number of the current format of data.
18
18
  * If the format of the data is changed in any way, this number should be incremented, in which case all necessary functions of the migrations dictionary will be run consecutively.
19
19
  *
20
20
  * ⚠️ Never decrement this number and optimally don't skip any numbers either!
21
21
  */
22
22
  formatVersion: number;
23
23
  /**
24
- * A dictionary of functions that can be used to migrate data from older versions of the configuration to newer ones.
24
+ * A dictionary of functions that can be used to migrate data from older versions to newer ones.
25
25
  * The keys of the dictionary should be the format version that the functions can migrate to, from the previous whole integer value.
26
26
  * The values should be functions that take the data in the old format and return the data in the new format.
27
27
  * The functions will be run in order from the oldest to the newest version.
@@ -50,33 +50,33 @@ export type ConfigManagerOptions<TData> = {
50
50
  decodeData?: never;
51
51
  });
52
52
  /**
53
- * Manages a user configuration that is cached in memory and persistently saved across sessions.
53
+ * Manages a sync & async persistent JSON database that is cached in memory and persistently saved across sessions.
54
54
  * Supports migrating data from older versions of the configuration to newer ones and populating the cache with default data if no persistent data is found.
55
55
  *
56
56
  * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue`
57
- * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultConfig`
57
+ * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
58
58
  *
59
- * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultConfig`) - this should also be the type of the data format associated with the current `options.formatVersion`
59
+ * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `defaultData`) - this should also be the type of the data format associated with the current `formatVersion`
60
60
  */
61
- export declare class ConfigManager<TData = any> {
61
+ export declare class DataStore<TData = any> {
62
62
  readonly id: string;
63
63
  readonly formatVersion: number;
64
- readonly defaultConfig: TData;
65
- private cachedConfig;
64
+ readonly defaultData: TData;
65
+ private cachedData;
66
66
  private migrations?;
67
67
  private encodeData;
68
68
  private decodeData;
69
69
  /**
70
- * Creates an instance of ConfigManager to manage a user configuration that is cached in memory and persistently saved across sessions.
71
- * Supports migrating data from older versions of the configuration to newer ones and populating the cache with default data if no persistent data is found.
70
+ * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
71
+ * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
72
72
  *
73
73
  * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue`
74
- * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultConfig`
74
+ * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
75
75
  *
76
- * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultConfig`) - this should also be the type of the data format associated with the current `options.formatVersion`
77
- * @param options The options for this ConfigManager instance
76
+ * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultData`) - this should also be the type of the data format associated with the current `options.formatVersion`
77
+ * @param options The options for this DataStore instance
78
78
  */
79
- constructor(options: ConfigManagerOptions<TData>);
79
+ constructor(options: DataStoreOptions<TData>);
80
80
  /**
81
81
  * Loads the data saved in persistent storage into the in-memory cache and also returns it.
82
82
  * Automatically populates persistent storage with default data if it doesn't contain any data yet.
@@ -93,13 +93,13 @@ export declare class ConfigManager<TData = any> {
93
93
  /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
94
94
  saveDefaultData(): Promise<void>;
95
95
  /**
96
- * Call this method to clear all persistently stored data associated with this ConfigManager instance.
96
+ * Call this method to clear all persistently stored data associated with this DataStore instance.
97
97
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
98
98
  * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
99
99
  *
100
100
  * ⚠️ This requires the additional directive `@grant GM.deleteValue`
101
101
  */
102
- deleteConfig(): Promise<void>;
102
+ deleteData(): Promise<void>;
103
103
  /** Runs all necessary migration functions consecutively - may be overwritten in a subclass */
104
104
  protected runMigrations(oldData: any, oldFmtVer: number): Promise<TData>;
105
105
  /** Serializes the data using the optional this.encodeData() and returns it as a string */
package/dist/lib/dom.d.ts CHANGED
@@ -36,14 +36,14 @@ export declare function openInNewTab(href: string): void;
36
36
  * Intercepts the specified event on the passed object and prevents it from being called if the called {@linkcode predicate} function returns a truthy value.
37
37
  * If no predicate is specified, all events will be discarded.
38
38
  * This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are added after this function is called.
39
- * Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
39
+ * Calling this function will set `Error.stackTraceLimit = 100` (if not already higher) to ensure the stack trace is preserved.
40
40
  */
41
41
  export declare function interceptEvent<TEvtObj extends EventTarget, TPredicateEvt extends Event>(eventObject: TEvtObj, eventName: Parameters<TEvtObj["addEventListener"]>[0], predicate?: (event: TPredicateEvt) => boolean): void;
42
42
  /**
43
43
  * Intercepts the specified event on the window object and prevents it from being called if the called {@linkcode predicate} function returns a truthy value.
44
44
  * If no predicate is specified, all events will be discarded.
45
45
  * This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are added after this function is called.
46
- * Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
46
+ * Calling this function will set `Error.stackTraceLimit = 100` (if not already higher) to ensure the stack trace is preserved.
47
47
  */
48
48
  export declare function interceptWindowEvent<TEvtKey extends keyof WindowEventMap>(eventName: TEvtKey, predicate?: (event: WindowEventMap[TEvtKey]) => boolean): void;
49
49
  /** Checks if an element is scrollable in the horizontal and vertical directions */
@@ -61,3 +61,13 @@ export declare function isScrollable(element: Element): {
61
61
  * @param callback Callback to execute when the value is changed
62
62
  */
63
63
  export declare function observeElementProp<TElem extends Element = HTMLElement, TPropKey extends keyof TElem = keyof TElem>(element: TElem, property: TPropKey, callback: (oldVal: TElem[TPropKey], newVal: TElem[TPropKey]) => void): void;
64
+ /**
65
+ * Returns a "frame" of the closest siblings of the {@linkcode refElement}, based on the passed amount of siblings and {@linkcode refElementAlignment}
66
+ * @param refElement The reference element to return the relative closest siblings from
67
+ * @param siblingAmount The amount of siblings to return
68
+ * @param refElementAlignment Can be set to `center-top` (default), `center-bottom`, `top`, or `bottom`, which will determine where the relative location of the provided {@linkcode refElement} is in the returned array
69
+ * @param includeRef If set to `true` (default), the provided {@linkcode refElement} will be included in the returned array at the corresponding position
70
+ * @template TSiblingType The type of the sibling elements that are returned
71
+ * @returns An array of sibling elements
72
+ */
73
+ export declare function getSiblingsFrame<TSiblingType extends Element = HTMLElement>(refElement: Element, siblingAmount: number, refElementAlignment?: "center-top" | "center-bottom" | "top" | "bottom", includeRef?: boolean): TSiblingType[];
@@ -1,5 +1,5 @@
1
1
  export * from "./array";
2
- export * from "./ConfigManager";
2
+ export * from "./DataStore";
3
3
  export * from "./dom";
4
4
  export * from "./math";
5
5
  export * from "./misc";
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@sv443-network/userutils",
3
- "version": "5.0.0",
3
+ "libName": "UserUtils",
4
+ "version": "6.0.0",
4
5
  "description": "Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more",
5
6
  "main": "dist/index.js",
6
7
  "module": "dist/index.mjs",