@sv443-network/userutils 5.0.1 → 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 +17 -0
- package/README.md +159 -33
- package/dist/index.global.js +46 -25
- package/dist/index.js +44 -22
- package/dist/index.mjs +43 -22
- package/dist/lib/{ConfigManager.d.ts → DataStore.d.ts} +20 -20
- package/dist/lib/dom.d.ts +12 -2
- package/dist/lib/index.d.ts +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
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
|
+
|
|
3
20
|
## 5.0.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -34,13 +34,14 @@ View the documentation of previous major releases: [4.2.1](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
|
-
- [
|
|
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
|
|
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
|
-
###
|
|
980
|
+
### DataStore
|
|
857
981
|
Usage:
|
|
858
982
|
```ts
|
|
859
|
-
new
|
|
983
|
+
new DataStore(options: DataStoreOptions)
|
|
860
984
|
```
|
|
861
985
|
|
|
862
|
-
A class that manages a
|
|
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
|
|
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
|
|
873
|
-
| `
|
|
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
|
|
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
|
|
884
|
-
If no data was saved in persistent storage before, the value of `options.
|
|
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.
|
|
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
|
|
1019
|
+
Writes the default data given in `options.defaultData` synchronously to the internal cache and asynchronously to persistent storage.
|
|
896
1020
|
|
|
897
|
-
`
|
|
898
|
-
Fully deletes the
|
|
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.
|
|
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 {
|
|
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
|
|
918
|
-
const
|
|
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
|
|
950
|
-
/** A unique ID for this
|
|
951
|
-
id: "my-userscript",
|
|
952
|
-
/** Default / fallback
|
|
953
|
-
|
|
954
|
-
/** The current version of the
|
|
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
|
|
963
|
-
|
|
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
|
-
/**
|
|
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
|
|
972
|
-
// if no data was saved in persistent storage before or getData() is called before loadData(), the value of options.
|
|
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
|
|
1104
|
+
// update the data
|
|
979
1105
|
configData.foo = "world";
|
|
980
1106
|
configData.bar = 123;
|
|
981
1107
|
|
|
982
|
-
// save the updated
|
|
1108
|
+
// save the updated data - synchronously to the cache and asynchronously to persistent storage
|
|
983
1109
|
manager.saveData(configData).then(() => {
|
|
984
|
-
console.log("
|
|
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:
|
package/dist/index.global.js
CHANGED
|
@@ -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
|
|
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
|
|
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/
|
|
141
|
-
var
|
|
139
|
+
// lib/DataStore.ts
|
|
140
|
+
var DataStore = class {
|
|
142
141
|
/**
|
|
143
|
-
* Creates an instance of
|
|
144
|
-
* Supports migrating data from older versions
|
|
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.
|
|
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.
|
|
150
|
-
* @param options The options for this
|
|
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, "
|
|
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.
|
|
163
|
-
this.cachedData = options.
|
|
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.
|
|
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.
|
|
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
|
|
189
|
+
console.warn("Error while parsing JSON data, resetting it to the default value.", err);
|
|
191
190
|
yield this.saveDefaultData();
|
|
192
|
-
return this.
|
|
191
|
+
return this.defaultData;
|
|
193
192
|
}
|
|
194
193
|
});
|
|
195
194
|
}
|
|
@@ -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.cachedData = this.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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.
|
|
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/
|
|
120
|
-
var
|
|
119
|
+
// lib/DataStore.ts
|
|
120
|
+
var DataStore = class {
|
|
121
121
|
/**
|
|
122
|
-
* Creates an instance of
|
|
123
|
-
* Supports migrating data from older versions
|
|
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.
|
|
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.
|
|
129
|
-
* @param options The options for this
|
|
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, "
|
|
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.
|
|
142
|
-
this.cachedData = options.
|
|
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.
|
|
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.
|
|
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
|
|
169
|
+
console.warn("Error while parsing JSON data, resetting it to the default value.", err);
|
|
170
170
|
yield this.saveDefaultData();
|
|
171
|
-
return this.
|
|
171
|
+
return this.defaultData;
|
|
172
172
|
}
|
|
173
173
|
});
|
|
174
174
|
}
|
|
@@ -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.cachedData = this.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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.
|
|
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/
|
|
118
|
-
var
|
|
117
|
+
// lib/DataStore.ts
|
|
118
|
+
var DataStore = class {
|
|
119
119
|
/**
|
|
120
|
-
* Creates an instance of
|
|
121
|
-
* Supports migrating data from older versions
|
|
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.
|
|
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.
|
|
127
|
-
* @param options The options for this
|
|
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, "
|
|
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.
|
|
140
|
-
this.cachedData = options.
|
|
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.
|
|
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.
|
|
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
|
|
167
|
+
console.warn("Error while parsing JSON data, resetting it to the default value.", err);
|
|
168
168
|
yield this.saveDefaultData();
|
|
169
|
-
return this.
|
|
169
|
+
return this.defaultData;
|
|
170
170
|
}
|
|
171
171
|
});
|
|
172
172
|
}
|
|
@@ -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.cachedData = this.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
332
|
-
|
|
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 {
|
|
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
|
|
6
|
-
export type
|
|
7
|
-
/** A unique internal ID for this
|
|
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
|
|
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
|
-
|
|
15
|
+
defaultData: TData;
|
|
16
16
|
/**
|
|
17
|
-
* An incremental, whole integer version number of the current format of
|
|
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
|
|
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
|
|
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.
|
|
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 `
|
|
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
|
|
61
|
+
export declare class DataStore<TData = any> {
|
|
62
62
|
readonly id: string;
|
|
63
63
|
readonly formatVersion: number;
|
|
64
|
-
readonly
|
|
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
|
|
71
|
-
* Supports migrating data from older versions
|
|
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.
|
|
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.
|
|
77
|
-
* @param options The options for this
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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[];
|
package/dist/lib/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sv443-network/userutils",
|
|
3
|
-
"
|
|
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",
|