@sv443-network/userutils 5.0.1 → 6.0.1

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.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 08248c1: Fixed terminology in JSDoc comments of the `DataStore` class
8
+
9
+ ## 6.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - e921593: Renamed `ConfigManager` to `DataStore` to make its implied purpose as a generic JSON database more clear.
14
+ - the constructor property `defaultConfig` is now called `defaultData`
15
+ - `deleteConfig()` is now called `deleteData()`
16
+ - the internal GM storage keys will still have the prefix `_uucfg` for backwards compatibility
17
+
18
+ ### Minor Changes
19
+
20
+ - da679c6: Added function `getSiblingsFrame()` that returns a frame of an element's siblings, with a given alignment and size
21
+
22
+ ### Patch Changes
23
+
24
+ - 0c716a6: Lowered the `Error.stackTraceLimit` by a multiple of 10 to preserve memory
25
+
3
26
  ## 5.0.1
4
27
 
5
28
  ### Patch Changes
package/README.md CHANGED
@@ -8,9 +8,12 @@ Contains builtin TypeScript declarations. Fully web compatible and supports ESM
8
8
  If you like using this library, please consider [supporting the development ❤️](https://github.com/sponsors/Sv443)
9
9
 
10
10
  <br>
11
+ <sup>
12
+ View the documentation of previous major releases:
13
+ </sup>
11
14
  <sub>
12
15
 
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)
16
+ <a href="https://github.com/Sv443-Network/UserUtils/blob/v5.0.1/README.md" rel="noopener noreferrer">5.0.1</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v4.2.1/README.md" rel="noopener noreferrer">4.2.1</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v3.0.0/README.md" rel="noopener noreferrer">3.0.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v2.0.1/README.md" rel="noopener noreferrer">2.0.1</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v1.2.0/README.md" rel="noopener noreferrer">1.2.0</a>, <a href="https://github.com/Sv443-Network/UserUtils/blob/v0.5.3/README.md" rel="noopener noreferrer">0.5.3</a>
14
17
 
15
18
  </sub>
16
19
  </div>
@@ -34,13 +37,14 @@ View the documentation of previous major releases: [4.2.1](https://github.com/Sv
34
37
  - [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
35
38
  - [isScrollable()](#isscrollable) - check if an element has a horizontal or vertical scroll bar
36
39
  - [observeElementProp()](#observeelementprop) - observe changes to an element's property that can't be observed with MutationObserver
40
+ - [getSiblingsFrame()](#getsiblingsframe) - returns a frame of an element's siblings, with a given alignment and size
37
41
  - [**Math:**](#math)
38
42
  - [clamp()](#clamp) - constrain a number between a min and max value
39
43
  - [mapRange()](#maprange) - map a number from one range to the same spot in another range
40
44
  - [randRange()](#randrange) - generate a random number between a min and max boundary
41
45
  - [randomId()](#randomid) - generate a random ID of a given length and radix
42
46
  - [**Misc:**](#misc)
43
- - [ConfigManager](#configmanager) - class that manages persistent userscript configurations, including data migration
47
+ - [DataStore](#datastore) - class that manages a sync & async persistent JSON database, including data migration
44
48
  - [autoPlural()](#autoplural) - automatically pluralize a string
45
49
  - [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
46
50
  - [debounce()](#debounce) - call a function only once, after a given amount of time
@@ -604,7 +608,7 @@ interceptEvent(
604
608
 
605
609
  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
610
  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.
611
+ Calling this function will set the `Error.stackTraceLimit` to 100 (if it's not already higher) to ensure the stack trace is preserved.
608
612
 
609
613
  ⚠️ 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
614
 
@@ -733,6 +737,129 @@ observeElementProp(myInput, "value", (oldValue, newValue) => {
733
737
 
734
738
  </details>
735
739
 
740
+ <br>
741
+
742
+ ### getSiblingsFrame()
743
+ Usage:
744
+ ```ts
745
+ getSiblingsFrame<
746
+ TSiblingType extends Element = HTMLElement
747
+ >(
748
+ refElement: Element,
749
+ siblingAmount: number,
750
+ refElementAlignment: "center-top" | "center-bottom" | "top" | "bottom" = "center-top",
751
+ includeRef = true
752
+ ): TSiblingType[]
753
+ ```
754
+ Returns a "frame" of the closest siblings of the reference element, based on the passed amount of siblings and element alignment.
755
+ The returned type is an array of `HTMLElement` by default but can be changed by specifying the `TSiblingType` generic in TypeScript.
756
+
757
+ These are the parameters:
758
+ - The `refElement` parameter is the reference element to return the relative closest siblings from.
759
+ - The `siblingAmount` parameter is the amount of siblings to return in total (including or excluding the `refElement` based on the `includeRef` parameter).
760
+ - 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.
761
+ `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.
762
+ Same goes for `center-bottom` in reverse.
763
+ `top` will keep the `refElement` at the top of the returned array, and `bottom` will keep it at the bottom.
764
+ - If `includeRef` is set to `true` (default), the provided `refElement` will be included in the returned array at its corresponding position.
765
+
766
+ <details><summary><b>Example - click to view</b></summary>
767
+
768
+ ```ts
769
+ import { getSiblingsFrame } from "@sv443-network/userutils";
770
+
771
+ const refElement = document.querySelector("#ref");
772
+ // ^ structure of the elements:
773
+ // <div id="parent">
774
+ // <div>1</div>
775
+ // <div>2</div>
776
+ // <div id="ref">3</div>
777
+ // <div>4</div>
778
+ // <div>5</div>
779
+ // <div>6</div>
780
+ // </div>
781
+
782
+ // ref element aligned to the top of the frame's center positions and included in the result:
783
+ const siblingsFoo = getSiblingsFrame(refElement, 4, "center-top", true);
784
+ // <div>1</div>
785
+ // <div>2</div> ◄──┐
786
+ // <div id="ref">3</div> │ returned <(ref is here because refElementAlignment = "center-top")
787
+ // <div>4</div> │ frame
788
+ // <div>5</div> ◄──┘
789
+ // <div>6</div>
790
+
791
+ // ref element aligned to the bottom of the frame's center positions and included in the result:
792
+ const siblingsBar = getSiblingsFrame(refElement, 4, "center-bottom", true);
793
+ // <div>1</div> ◄──┐
794
+ // <div>2</div> │ returned
795
+ // <div id="ref">3</div> │ frame <(ref is here because refElementAlignment = "center-bottom")
796
+ // <div>4</div> ◄──┘
797
+ // <div>5</div>
798
+ // <div>6</div>
799
+
800
+ // ref element aligned to the bottom of the frame's center positions, but excluded from the result:
801
+ const siblingsBaz = getSiblingsFrame(refElement, 3, "center-bottom", false);
802
+ // <div>1</div> ◄──┐
803
+ // <div>2</div> ◄──┘ returned...
804
+ // <div id="ref">3</div> <(skipped because includeRef = false)
805
+ // <div>4</div> ◄─── ...frame
806
+ // <div>5</div>
807
+ // <div>6</div>
808
+
809
+ // ref element aligned to the top of the frame, but excluded from the result:
810
+ const siblingsQux = getSiblingsFrame(refElement, 3, "top", false);
811
+ // <div>1</div>
812
+ // <div>2</div>
813
+ // <div id="ref">3</div> <(skipped because includeRef = false)
814
+ // <div>4</div> ◄──┐ returned
815
+ // <div>5</div> │ frame
816
+ // <div>6</div> ◄──┘
817
+
818
+ // ref element aligned to the top of the frame, but this time included in the result:
819
+ const siblingsQuux = getSiblingsFrame(refElement, 3, "top", true);
820
+ // <div>1</div>
821
+ // <div>2</div>
822
+ // <div id="ref">3</div> ◄──┐ returned <(not skipped because includeRef = true)
823
+ // <div>4</div> │ frame
824
+ // <div>5</div> ◄──┘
825
+ // <div>6</div>
826
+ ```
827
+
828
+ More useful examples:
829
+
830
+ ```ts
831
+ const refElement = document.querySelector("#ref");
832
+ // ^ structure of the elements:
833
+ // <div id="parent">
834
+ // <div>1</div>
835
+ // <div>2</div>
836
+ // <div id="ref">3</div>
837
+ // <div>4</div>
838
+ // <div>5</div>
839
+ // <div>6</div>
840
+ // </div>
841
+
842
+ // get all elements above and include the reference element:
843
+ const allAbove = getSiblingsFrame(refElement, Infinity, "top", true);
844
+ // <div>1</div> ◄──┐ returned
845
+ // <div>2</div> │ frame
846
+ // <div id="ref">3</div> ◄──┘
847
+ // <div>4</div>
848
+ // <div>5</div>
849
+ // <div>6</div>
850
+
851
+ // get all elements below and exclude the reference element:
852
+ const allBelowExcl = getSiblingsFrame(refElement, Infinity, "bottom", false);
853
+ // <div>1</div>
854
+ // <div>2</div>
855
+ // <div id="ref">3</div>
856
+ // <div>4</div> ◄──┐ returned
857
+ // <div>5</div> │ frame
858
+ // <div>6</div> ◄──┘
859
+ ```
860
+
861
+ </details>
862
+
736
863
  <br><br>
737
864
 
738
865
  <!-- #SECTION Math -->
@@ -853,26 +980,26 @@ randomId(10, 36); // "z46jfpa37r" (length 10, radix 36)
853
980
  <!-- #SECTION Misc -->
854
981
  ## Misc:
855
982
 
856
- ### ConfigManager
983
+ ### DataStore
857
984
  Usage:
858
985
  ```ts
859
- new ConfigManager(options: ConfigManagerOptions)
986
+ new DataStore(options: DataStoreOptions)
860
987
  ```
861
988
 
862
- A class that manages a userscript's configuration that is persistently saved to and loaded from GM storage.
989
+ A class that manages a sync & async JSON database that is persistently saved to and loaded from GM storage.
863
990
  Also supports automatic migration of outdated data formats via provided migration functions.
864
991
  You may create as many instances as you like as long as they have different IDs.
865
992
 
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.
993
+ ⚠️ 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
994
  ⚠️ The directives `@grant GM.getValue` and `@grant GM.setValue` are required for this to work.
868
995
 
869
996
  The options object has the following properties:
870
997
  | Property | Description |
871
998
  | :-- | :-- |
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. |
999
+ | `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. |
1000
+ | `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
1001
  | `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. |
1002
+ | `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
1003
  | `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
1004
  | `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
1005
 
@@ -880,24 +1007,24 @@ The options object has the following properties:
880
1007
 
881
1008
  #### Methods:
882
1009
  `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.
1010
+ Asynchronously loads the data from persistent storage and returns it.
1011
+ If no data was saved in persistent storage before, the value of `options.defaultData` will be returned and written to persistent storage.
885
1012
  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
1013
 
887
1014
  `getData(): TData`
888
1015
  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.
1016
+ If no data was loaded from persistent storage yet using `loadData()`, the value of `options.defaultData` will be returned.
890
1017
 
891
1018
  `setData(data: TData): Promise<void>`
892
1019
  Writes the given data synchronously to the internal cache and asynchronously to persistent storage.
893
1020
 
894
1021
  `saveDefaultData(): Promise<void>`
895
- Writes the default configuration given in `options.defaultConfig` synchronously to the internal cache and asynchronously to persistent storage.
1022
+ Writes the default data given in `options.defaultData` synchronously to the internal cache and asynchronously to persistent storage.
896
1023
 
897
- `deleteConfig(): Promise<void>`
898
- Fully deletes the configuration from persistent storage.
1024
+ `deleteData(): Promise<void>`
1025
+ Fully deletes the data from persistent storage.
899
1026
  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.
1027
+ If `loadData()` or `setData()` are called after this, the persistent storage will be populated with the value of `options.defaultData` again.
901
1028
  ⚠️ If you want to use this method, the additional directive `@grant GM.deleteValue` is required.
902
1029
 
903
1030
  <br>
@@ -905,8 +1032,9 @@ If `loadData()` or `setData()` are called after this, the persistent storage wil
905
1032
  <details><summary><b>Example - click to view</b></summary>
906
1033
 
907
1034
  ```ts
908
- import { ConfigManager, compress, decompress } from "@sv443-network/userutils";
1035
+ import { DataStore, compress, decompress } from "@sv443-network/userutils";
909
1036
 
1037
+ /** Example: Userscript configuration data */
910
1038
  interface MyConfig {
911
1039
  foo: string;
912
1040
  bar: number;
@@ -914,8 +1042,8 @@ interface MyConfig {
914
1042
  qux: string;
915
1043
  }
916
1044
 
917
- /** Default config data */
918
- const defaultConfig: MyConfig = {
1045
+ /** Default data */
1046
+ const defaultData: MyConfig = {
919
1047
  foo: "hello",
920
1048
  bar: 42,
921
1049
  baz: "xyz",
@@ -946,12 +1074,12 @@ const migrations = {
946
1074
  },
947
1075
  };
948
1076
 
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 */
1077
+ const manager = new DataStore({
1078
+ /** A unique ID for this instance - choose wisely as changing it is not supported yet! */
1079
+ id: "my-userscript-config",
1080
+ /** Default / fallback data */
1081
+ defaultData,
1082
+ /** The current version of the data format */
955
1083
  formatVersion,
956
1084
  /** Data format migration functions */
957
1085
  migrations,
@@ -959,29 +1087,30 @@ const manager = new ConfigManager({
959
1087
  // Compression example:
960
1088
  // Adding this will save space at the cost of a little bit of performance while initially loading and saving the data
961
1089
  // 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 */
1090
+ // Everything else will be handled by the DataStore instance
1091
+
1092
+ /** Encodes data using the "deflate-raw" algorithm and digests it as a base64 string */
964
1093
  encodeData: (data) => compress(data, "deflate-raw", "base64"),
965
- /** Decode the "deflate-raw" encoded data as a base64 string */
1094
+ /** Decodes the "deflate-raw" encoded data as a base64 string */
966
1095
  decodeData: (data) => decompress(data, "deflate-raw", "base64"),
967
1096
  });
968
1097
 
969
1098
  /** Entrypoint of the userscript */
970
1099
  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
1100
+ // wait for the data to be loaded from persistent storage
1101
+ // if no data was saved in persistent storage before or getData() is called before loadData(), the value of options.defaultData will be returned
973
1102
  // if the previously saved data needs to be migrated to a newer version, it will happen in this function call
974
1103
  const configData = await manager.loadData();
975
1104
 
976
1105
  console.log(configData.foo); // "hello"
977
1106
 
978
- // update the config
1107
+ // update the data
979
1108
  configData.foo = "world";
980
1109
  configData.bar = 123;
981
1110
 
982
- // save the updated config - synchronously to the cache and asynchronously to persistent storage
1111
+ // save the updated data - synchronously to the cache and asynchronously to persistent storage
983
1112
  manager.saveData(configData).then(() => {
984
- console.log("Config saved to persistent storage!");
1113
+ console.log("Data saved to persistent storage!");
985
1114
  });
986
1115
 
987
1116
  // 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.1
11
+ // @version 6.0.1
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 `options.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");
154
+ __publicField(this, "defaultData");
156
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.cachedData = 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 __spreadValues({}, 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))
@@ -187,9 +186,9 @@ var UserUtils = (function (exports) {
187
186
  parsed = yield this.runMigrations(parsed, gmFmtVer);
188
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
  }
@@ -213,14 +212,14 @@ var UserUtils = (function (exports) {
213
212
  resolve();
214
213
  }));
215
214
  }
216
- /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
215
+ /** Saves the default 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.cachedData = 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 `options.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");
134
+ __publicField(this, "defaultData");
135
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.cachedData = 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 __spreadValues({}, 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))
@@ -166,9 +166,9 @@ var ConfigManager = class {
166
166
  parsed = yield this.runMigrations(parsed, gmFmtVer);
167
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
  }
@@ -192,14 +192,14 @@ var ConfigManager = class {
192
192
  resolve();
193
193
  }));
194
194
  }
195
- /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
195
+ /** Saves the default 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.cachedData = 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 `options.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");
132
+ __publicField(this, "defaultData");
133
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.cachedData = 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 __spreadValues({}, 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))
@@ -164,9 +164,9 @@ var ConfigManager = class {
164
164
  parsed = yield this.runMigrations(parsed, gmFmtVer);
165
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
  }
@@ -190,14 +190,14 @@ var ConfigManager = class {
190
190
  resolve();
191
191
  }));
192
192
  }
193
- /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
193
+ /** Saves the default 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.cachedData = 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 };
@@ -1,33 +1,33 @@
1
1
  /** Function that takes the data in the old format and returns the data in the new format. Also supports an asynchronous migration. */
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
- 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. */
4
+ export type DataMigrationsDict = Record<number, MigrationFunc>;
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.
28
28
  * If the current format version is not in the dictionary, no migrations will be run.
29
29
  */
30
- migrations?: ConfigMigrationsDict;
30
+ migrations?: DataMigrationsDict;
31
31
  } & ({
32
32
  /**
33
33
  * Function to use to encode the data prior to saving it in persistent storage.
@@ -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.
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.
53
+ * Manages a sync & async persistent JSON database that is cached in memory and persistently saved across sessions.
54
+ * Supports migrating data from older format versions 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;
64
+ readonly defaultData: TData;
65
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 `options.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.
@@ -90,16 +90,16 @@ export declare class ConfigManager<TData = any> {
90
90
  getData(): TData;
91
91
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
92
92
  setData(data: TData): Promise<void>;
93
- /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
93
+ /** Saves the default 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 TSibling The type of the sibling elements that are returned
71
+ * @returns An array of sibling elements
72
+ */
73
+ export declare function getSiblingsFrame<TSibling extends Element = HTMLElement>(refElement: Element, siblingAmount: number, refElementAlignment?: "center-top" | "center-bottom" | "top" | "bottom", includeRef?: boolean): TSibling[];
@@ -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.1",
3
+ "libName": "UserUtils",
4
+ "version": "6.0.1",
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",
@@ -9,7 +10,7 @@
9
10
  "lint": "tsc --noEmit && eslint .",
10
11
  "build-types": "tsc --emitDeclarationOnly --declaration --outDir dist",
11
12
  "build-common": "tsup lib/index.ts --format cjs,esm --clean --treeshake",
12
- "build-global": "tsup lib/index.ts --format cjs,esm,iife --treeshake --onSuccess \"npm run post-build-global && echo Finished building.\"",
13
+ "build-all": "tsup lib/index.ts --format cjs,esm,iife --treeshake --onSuccess \"npm run post-build-global && echo Finished building.\"",
13
14
  "build": "npm run build-common -- && npm run build-types",
14
15
  "post-build-global": "npm run node-ts -- ./tools/post-build-global.mts",
15
16
  "dev": "npm run build-common -- --sourcemap --watch --onSuccess \"npm run build-types && echo Finished building.\"",