@sv443-network/userutils 0.5.2 → 1.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 +270 -59
- package/dist/index.js +25 -24
- package/dist/index.mjs +2 -2
- package/dist/lib/array.d.ts +11 -0
- package/dist/lib/config.d.ts +83 -0
- package/dist/lib/dom.d.ts +69 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/math.d.ts +11 -0
- package/dist/lib/misc.d.ts +20 -0
- package/dist/lib/onSelector.d.ts +40 -0
- package/package.json +15 -11
- package/dist/index.d.mts +0 -155
- package/dist/index.d.ts +0 -155
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @sv443-network/userutils
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- a500a98: Added ConfigManager to manage persistent user configurations including data versioning and migration
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 6d0a700: Event interceptor can now be toggled at runtime ([#16](https://github.com/Sv443-Network/UserUtils/issues/16))
|
|
12
|
+
- d038b21: Global (IIFE) build now comes with a header
|
|
13
|
+
|
|
14
|
+
## 0.5.3
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- f97dae6: change bundling process
|
|
19
|
+
|
|
3
20
|
## 0.5.2
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div style="text-align: center;" align="center">
|
|
2
2
|
|
|
3
3
|
## UserUtils
|
|
4
|
-
|
|
4
|
+
Zero-dependency 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
5
|
Contains builtin TypeScript declarations. Webpack compatible and supports ESM and CJS.
|
|
6
6
|
If you like using this library, please consider [supporting the development ❤️](https://github.com/sponsors/Sv443)
|
|
7
7
|
|
|
@@ -31,6 +31,7 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
31
31
|
- [mapRange()](#maprange) - map a number from one range to the same spot in another range
|
|
32
32
|
- [randRange()](#randrange) - generate a random number between a min and max boundary
|
|
33
33
|
- [Misc:](#misc)
|
|
34
|
+
- [ConfigManager()](#configmanager) - class that manages persistent userscript configurations, including data migration
|
|
34
35
|
- [autoPlural()](#autoplural) - automatically pluralize a string
|
|
35
36
|
- [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
|
|
36
37
|
- [debounce()](#debounce) - call a function only once, after a given amount of time
|
|
@@ -51,10 +52,12 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
51
52
|
Then, import it in your script as usual:
|
|
52
53
|
```ts
|
|
53
54
|
import { addGlobalStyle } from "@sv443-network/userutils";
|
|
54
|
-
|
|
55
|
-
import
|
|
55
|
+
|
|
56
|
+
// or just import everything (not recommended because this doesn't allow for treeshaking):
|
|
57
|
+
|
|
58
|
+
import * as UserUtils from "@sv443-network/userutils";
|
|
56
59
|
```
|
|
57
|
-
Shameless plug: I
|
|
60
|
+
Shameless plug: I made a [template for userscripts in TypeScript](https://github.com/Sv443/Userscript.ts) that you can use to get started quickly. It also includes this library by default.
|
|
58
61
|
|
|
59
62
|
<br>
|
|
60
63
|
|
|
@@ -79,10 +82,12 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
79
82
|
This library is written in TypeScript and contains builtin TypeScript declarations.
|
|
80
83
|
The usages and examples in this readme are written in TypeScript, but the library can also be used in plain JavaScript.
|
|
81
84
|
|
|
82
|
-
Some
|
|
85
|
+
Some features require the `@run-at` or `@grant` directives to be tweaked in the userscript header or have other requirements.
|
|
83
86
|
Their documentation will contain a section marked by a warning emoji (⚠️) that will go into more detail.
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
Each feature has example code that can be expanded by clicking on the text "Example - click to view".
|
|
89
|
+
|
|
90
|
+
If the usage section contains multiple definitions of the function, each occurrence represents an overload and you can choose which one you want to use.
|
|
86
91
|
|
|
87
92
|
<br><br>
|
|
88
93
|
|
|
@@ -124,7 +129,7 @@ This initialization function has to be called after `DOMContentLoaded` is fired
|
|
|
124
129
|
|
|
125
130
|
Calling onSelector() before `DOMContentLoaded` is fired will not throw an error, but it also won't trigger listeners until the DOM is accessible.
|
|
126
131
|
|
|
127
|
-
<details><summary><
|
|
132
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
128
133
|
|
|
129
134
|
```ts
|
|
130
135
|
document.addEventListener("DOMContentLoaded", initOnSelector);
|
|
@@ -155,7 +160,7 @@ Usage:
|
|
|
155
160
|
```ts
|
|
156
161
|
initOnSelector(options?: MutationObserverInit): void
|
|
157
162
|
```
|
|
158
|
-
|
|
163
|
+
|
|
159
164
|
Initializes the MutationObserver that is used by [`onSelector()`](#onselector) to check for the registered selectors whenever a DOM change occurs on the `<body>`
|
|
160
165
|
By default, this only checks if elements are added or removed (at any depth).
|
|
161
166
|
|
|
@@ -169,7 +174,7 @@ You may see all options [here](https://developer.mozilla.org/en-US/docs/Web/API/
|
|
|
169
174
|
>
|
|
170
175
|
> ⚠️ Using these extra options can have a performance impact on larger sites or sites with a constantly changing DOM.
|
|
171
176
|
|
|
172
|
-
<details><summary><
|
|
177
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
173
178
|
|
|
174
179
|
```ts
|
|
175
180
|
document.addEventListener("DOMContentLoaded", () => {
|
|
@@ -185,12 +190,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
185
190
|
<br>
|
|
186
191
|
|
|
187
192
|
### getSelectorMap()
|
|
188
|
-
Usage:
|
|
193
|
+
Usage:
|
|
194
|
+
```ts
|
|
195
|
+
getSelectorMap(): Map<string, OnSelectorOptions[]>
|
|
196
|
+
```
|
|
189
197
|
|
|
190
198
|
Returns a Map of all currently registered selectors and their options, including listener function.
|
|
191
199
|
Since multiple listeners can be registered for the same selector, the value of the Map is an array of `OnSelectorOptions` objects.
|
|
192
200
|
|
|
193
|
-
<details><summary><
|
|
201
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
194
202
|
|
|
195
203
|
```ts
|
|
196
204
|
document.addEventListener("DOMContentLoaded", initOnSelector);
|
|
@@ -225,12 +233,15 @@ const selectorMap = getSelectorMap();
|
|
|
225
233
|
<br>
|
|
226
234
|
|
|
227
235
|
### getUnsafeWindow()
|
|
228
|
-
Usage:
|
|
236
|
+
Usage:
|
|
237
|
+
```ts
|
|
238
|
+
getUnsafeWindow(): Window
|
|
239
|
+
```
|
|
229
240
|
|
|
230
241
|
Returns the unsafeWindow object or falls back to the regular window object if the `@grant unsafeWindow` is not given.
|
|
231
242
|
Userscripts are sandboxed and do not have access to the regular window object, so this function is useful for websites that reject some events that were dispatched by the userscript.
|
|
232
243
|
|
|
233
|
-
<details><summary><
|
|
244
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
234
245
|
|
|
235
246
|
```ts
|
|
236
247
|
// trick the site into thinking the mouse was moved:
|
|
@@ -249,14 +260,17 @@ document.body.dispatchEvent(mouseEvent);
|
|
|
249
260
|
<br>
|
|
250
261
|
|
|
251
262
|
### insertAfter()
|
|
252
|
-
Usage:
|
|
263
|
+
Usage:
|
|
264
|
+
```ts
|
|
265
|
+
insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement
|
|
266
|
+
```
|
|
253
267
|
|
|
254
268
|
Inserts the element passed as `afterElement` as a sibling after the passed `beforeElement`.
|
|
255
269
|
The passed `afterElement` will be returned.
|
|
256
270
|
|
|
257
271
|
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
258
272
|
|
|
259
|
-
<details><summary><
|
|
273
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
260
274
|
|
|
261
275
|
```ts
|
|
262
276
|
// insert a <div> as a sibling next to an element
|
|
@@ -271,14 +285,17 @@ insertAfter(beforeElement, afterElement);
|
|
|
271
285
|
<br>
|
|
272
286
|
|
|
273
287
|
### addParent()
|
|
274
|
-
Usage:
|
|
288
|
+
Usage:
|
|
289
|
+
```ts
|
|
290
|
+
addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement
|
|
291
|
+
```
|
|
275
292
|
|
|
276
293
|
Adds a parent element around the passed `element` and returns the new parent.
|
|
277
294
|
Previously registered event listeners are kept intact.
|
|
278
295
|
|
|
279
296
|
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
280
297
|
|
|
281
|
-
<details><summary><
|
|
298
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
282
299
|
|
|
283
300
|
```ts
|
|
284
301
|
// add an <a> around an element
|
|
@@ -293,12 +310,15 @@ addParent(element, newParent);
|
|
|
293
310
|
<br>
|
|
294
311
|
|
|
295
312
|
### addGlobalStyle()
|
|
296
|
-
Usage:
|
|
313
|
+
Usage:
|
|
314
|
+
```ts
|
|
315
|
+
addGlobalStyle(css: string): void
|
|
316
|
+
```
|
|
297
317
|
|
|
298
318
|
Adds a global style to the page in form of a `<style>` element that's inserted into the `<head>`.
|
|
299
319
|
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
300
320
|
|
|
301
|
-
<details><summary><
|
|
321
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
302
322
|
|
|
303
323
|
```ts
|
|
304
324
|
document.addEventListener("DOMContentLoaded", () => {
|
|
@@ -315,13 +335,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
315
335
|
<br>
|
|
316
336
|
|
|
317
337
|
### preloadImages()
|
|
318
|
-
Usage:
|
|
338
|
+
Usage:
|
|
339
|
+
```ts
|
|
340
|
+
preloadImages(urls: string[], rejects?: boolean): Promise<void>
|
|
341
|
+
```
|
|
319
342
|
|
|
320
343
|
Preloads images into browser cache by creating an invisible `<img>` element for each URL passed.
|
|
321
344
|
The images will be loaded in parallel and the returned Promise will only resolve once all images have been loaded.
|
|
322
345
|
The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected, but only if `rejects` is set to true.
|
|
323
346
|
|
|
324
|
-
<details><summary><
|
|
347
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
325
348
|
|
|
326
349
|
```ts
|
|
327
350
|
preloadImages([
|
|
@@ -339,7 +362,10 @@ preloadImages([
|
|
|
339
362
|
<br>
|
|
340
363
|
|
|
341
364
|
### openInNewTab()
|
|
342
|
-
Usage:
|
|
365
|
+
Usage:
|
|
366
|
+
```ts
|
|
367
|
+
openInNewTab(url: string): void
|
|
368
|
+
```
|
|
343
369
|
|
|
344
370
|
Creates an invisible anchor with a `_blank` target and clicks it.
|
|
345
371
|
Contrary to `window.open()`, this has a lesser chance to get blocked by the browser's popup blocker and doesn't open the URL as a new window.
|
|
@@ -347,7 +373,7 @@ This function has to be run in response to a user interaction event, else the br
|
|
|
347
373
|
|
|
348
374
|
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
349
375
|
|
|
350
|
-
<details><summary><
|
|
376
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
351
377
|
|
|
352
378
|
```ts
|
|
353
379
|
document.querySelector("#my-button").addEventListener("click", () => {
|
|
@@ -360,14 +386,21 @@ document.querySelector("#my-button").addEventListener("click", () => {
|
|
|
360
386
|
<br>
|
|
361
387
|
|
|
362
388
|
### interceptEvent()
|
|
363
|
-
Usage:
|
|
389
|
+
Usage:
|
|
390
|
+
```ts
|
|
391
|
+
interceptEvent(
|
|
392
|
+
eventObject: EventTarget,
|
|
393
|
+
eventName: string,
|
|
394
|
+
predicate: () => boolean
|
|
395
|
+
): void
|
|
396
|
+
```
|
|
364
397
|
|
|
365
398
|
Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
366
399
|
Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not already higher) to ensure the stack trace is preserved.
|
|
367
400
|
|
|
368
401
|
⚠️ 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.
|
|
369
402
|
|
|
370
|
-
<details><summary><
|
|
403
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
371
404
|
|
|
372
405
|
```ts
|
|
373
406
|
interceptEvent(document.body, "click", () => {
|
|
@@ -380,14 +413,20 @@ interceptEvent(document.body, "click", () => {
|
|
|
380
413
|
<br>
|
|
381
414
|
|
|
382
415
|
### interceptWindowEvent()
|
|
383
|
-
Usage:
|
|
416
|
+
Usage:
|
|
417
|
+
```ts
|
|
418
|
+
interceptWindowEvent(
|
|
419
|
+
eventName: string,
|
|
420
|
+
predicate: () => boolean
|
|
421
|
+
): void
|
|
422
|
+
```
|
|
384
423
|
|
|
385
424
|
Intercepts all events dispatched on the `window` object and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
386
425
|
This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
|
|
387
426
|
|
|
388
427
|
⚠️ 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.
|
|
389
428
|
|
|
390
|
-
<details><summary><
|
|
429
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
391
430
|
|
|
392
431
|
```ts
|
|
393
432
|
interceptWindowEvent("beforeunload", () => {
|
|
@@ -400,7 +439,10 @@ interceptWindowEvent("beforeunload", () => {
|
|
|
400
439
|
<br>
|
|
401
440
|
|
|
402
441
|
### amplifyMedia()
|
|
403
|
-
Usage:
|
|
442
|
+
Usage:
|
|
443
|
+
```ts
|
|
444
|
+
amplifyMedia(mediaElement: HTMLMediaElement, multiplier?: number): AmplifyMediaResult
|
|
445
|
+
```
|
|
404
446
|
|
|
405
447
|
Amplifies the gain of a media element (like `<audio>` or `<video>`) by a given multiplier (defaults to 1.0).
|
|
406
448
|
This is how you can increase the volume of a media element beyond the default maximum volume of 1.0 or 100%.
|
|
@@ -408,7 +450,7 @@ Make sure to limit the multiplier to a reasonable value ([clamp()](#clamp) is go
|
|
|
408
450
|
|
|
409
451
|
⚠️ This function has to be run in response to a user interaction event, else the browser will reject it because of the strict autoplay policy.
|
|
410
452
|
|
|
411
|
-
|
|
453
|
+
The returned AmplifyMediaResult object has the following properties:
|
|
412
454
|
| Property | Description |
|
|
413
455
|
| :-- | :-- |
|
|
414
456
|
| `mediaElement` | The passed media element |
|
|
@@ -418,7 +460,7 @@ Returns an object with the following properties:
|
|
|
418
460
|
| `source` | The MediaElementSourceNode instance |
|
|
419
461
|
| `gain` | The GainNode instance |
|
|
420
462
|
|
|
421
|
-
<details><summary><
|
|
463
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
422
464
|
|
|
423
465
|
```ts
|
|
424
466
|
const audio = document.querySelector<HTMLAudioElement>("audio");
|
|
@@ -448,17 +490,24 @@ button.addEventListener("click", () => {
|
|
|
448
490
|
## Math:
|
|
449
491
|
|
|
450
492
|
### clamp()
|
|
451
|
-
Usage:
|
|
493
|
+
Usage:
|
|
494
|
+
```ts
|
|
495
|
+
clamp(num: number, min: number, max: number): number
|
|
496
|
+
```
|
|
452
497
|
|
|
453
|
-
Clamps a number between a min and max
|
|
498
|
+
Clamps a number between a min and max boundary (inclusive).
|
|
454
499
|
|
|
455
|
-
<details><summary><
|
|
500
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
456
501
|
|
|
457
502
|
```ts
|
|
458
|
-
clamp(
|
|
459
|
-
clamp(-1, 0, 10);
|
|
460
|
-
clamp(
|
|
461
|
-
clamp(
|
|
503
|
+
clamp(7, 0, 10); // 7
|
|
504
|
+
clamp(-1, 0, 10); // 0
|
|
505
|
+
clamp(5, -5, 0); // 0
|
|
506
|
+
clamp(99999, 0, 10); // 10
|
|
507
|
+
|
|
508
|
+
// clamp without a min or max boundary:
|
|
509
|
+
clamp(-99999, -Infinity, 0); // -99999
|
|
510
|
+
clamp(99999, 0, Infinity); // 99999
|
|
462
511
|
```
|
|
463
512
|
|
|
464
513
|
</details>
|
|
@@ -466,16 +515,27 @@ clamp(Infinity, 0, 10); // 10
|
|
|
466
515
|
<br>
|
|
467
516
|
|
|
468
517
|
### mapRange()
|
|
469
|
-
Usage:
|
|
518
|
+
Usage:
|
|
519
|
+
```ts
|
|
520
|
+
mapRange(
|
|
521
|
+
value: number,
|
|
522
|
+
range_1_min: number,
|
|
523
|
+
range_1_max: number,
|
|
524
|
+
range_2_min: number,
|
|
525
|
+
range_2_max: number
|
|
526
|
+
): number
|
|
527
|
+
```
|
|
470
528
|
|
|
471
529
|
Maps a number from one range to the spot it would be in another range.
|
|
472
530
|
|
|
473
|
-
<details><summary><
|
|
531
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
474
532
|
|
|
475
533
|
```ts
|
|
476
534
|
mapRange(5, 0, 10, 0, 100); // 50
|
|
477
535
|
mapRange(5, 0, 10, 0, 50); // 25
|
|
478
|
-
|
|
536
|
+
|
|
537
|
+
// to calculate a percentage from arbitrary values, use 0 and 100 as the second range
|
|
538
|
+
// for example, if 4 files of a total of 13 were downloaded:
|
|
479
539
|
mapRange(4, 0, 13, 0, 100); // 30.76923076923077
|
|
480
540
|
```
|
|
481
541
|
|
|
@@ -493,7 +553,7 @@ randRange(max: number): number
|
|
|
493
553
|
Returns a random number between `min` and `max` (inclusive).
|
|
494
554
|
If only one argument is passed, it will be used as the `max` value and `min` will be set to 0.
|
|
495
555
|
|
|
496
|
-
<details><summary><
|
|
556
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
497
557
|
|
|
498
558
|
```ts
|
|
499
559
|
randRange(0, 10); // 4
|
|
@@ -507,13 +567,145 @@ randRange(10); // 7
|
|
|
507
567
|
|
|
508
568
|
## Misc:
|
|
509
569
|
|
|
570
|
+
### ConfigManager()
|
|
571
|
+
Usage:
|
|
572
|
+
```ts
|
|
573
|
+
new ConfigManager(options: ConfigManagerOptions)
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
A class that manages a userscript's configuration that is persistently saved to and loaded from GM storage.
|
|
577
|
+
Also supports automatic migration of outdated data formats via provided migration functions.
|
|
578
|
+
|
|
579
|
+
⚠️ The configuration is stored as a JSON string, so only JSON-compatible data can be used.
|
|
580
|
+
⚠️ The directives `@grant GM.getValue` and `@grant GM.setValue` are required for this to work.
|
|
581
|
+
|
|
582
|
+
The options object has the following properties:
|
|
583
|
+
| Property | Description |
|
|
584
|
+
| :-- | :-- |
|
|
585
|
+
| `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. |
|
|
586
|
+
| `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. |
|
|
587
|
+
| `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. |
|
|
588
|
+
| `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. |
|
|
589
|
+
|
|
590
|
+
<br>
|
|
591
|
+
|
|
592
|
+
### Methods:
|
|
593
|
+
`loadData(): Promise<TData>`
|
|
594
|
+
Asynchronously loads the configuration data from persistent storage and returns it.
|
|
595
|
+
If no data was saved in persistent storage before, the value of `options.defaultConfig` will be returned and written to persistent storage.
|
|
596
|
+
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.
|
|
597
|
+
|
|
598
|
+
`getData(): TData`
|
|
599
|
+
Synchronously returns the current data that is stored in the internal cache.
|
|
600
|
+
If no data was loaded from persistent storage yet using `loadData()`, the value of `options.defaultConfig` will be returned.
|
|
601
|
+
|
|
602
|
+
`setData(data: TData): Promise<void>`
|
|
603
|
+
Writes the given data synchronously to the internal cache and asynchronously to persistent storage.
|
|
604
|
+
|
|
605
|
+
`saveDefaultData(): Promise<void>`
|
|
606
|
+
Writes the default configuration given in `options.defaultConfig` synchronously to the internal cache and asynchronously to persistent storage.
|
|
607
|
+
|
|
608
|
+
`deleteConfig(): Promise<void>`
|
|
609
|
+
Fully deletes the configuration from persistent storage.
|
|
610
|
+
The internal cache will be left untouched, so any subsequent calls to `getData()` will return the data that was last loaded.
|
|
611
|
+
If `loadData()` or `setData()` are called after this, the persistent storage will be populated again.
|
|
612
|
+
|
|
613
|
+
<br>
|
|
614
|
+
|
|
615
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
616
|
+
|
|
617
|
+
```ts
|
|
618
|
+
import { ConfigManager } from "@sv443-network/userutils";
|
|
619
|
+
|
|
620
|
+
interface MyConfig {
|
|
621
|
+
foo: string;
|
|
622
|
+
bar: number;
|
|
623
|
+
baz: string;
|
|
624
|
+
qux: string;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** Default config data */
|
|
628
|
+
const defaultConfig: MyConfig = {
|
|
629
|
+
foo: "hello",
|
|
630
|
+
bar: 42,
|
|
631
|
+
baz: "xyz",
|
|
632
|
+
qux: "something",
|
|
633
|
+
};
|
|
634
|
+
/** If any properties are added to, removed from or renamed in MyConfig, increment this number */
|
|
635
|
+
const formatVersion = 2;
|
|
636
|
+
/** Functions that migrate outdated data to the latest format - make sure a function exists for every previously used formatVersion and that no numbers are skipped! */
|
|
637
|
+
const migrations = {
|
|
638
|
+
// migrate from format version 0 to 1
|
|
639
|
+
1: (oldData: any) => {
|
|
640
|
+
return {
|
|
641
|
+
foo: oldData.foo,
|
|
642
|
+
bar: oldData.bar,
|
|
643
|
+
baz: "world",
|
|
644
|
+
};
|
|
645
|
+
},
|
|
646
|
+
// asynchronously migrate from format version 1 to 2
|
|
647
|
+
2: async (oldData: any) => {
|
|
648
|
+
// arbitrary async operation required for the new format
|
|
649
|
+
const qux = JSON.parse(await (await fetch("https://api.example.org/some-data")).text());
|
|
650
|
+
return {
|
|
651
|
+
foo: oldData.foo,
|
|
652
|
+
bar: oldData.bar,
|
|
653
|
+
baz: oldData.baz,
|
|
654
|
+
qux,
|
|
655
|
+
};
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const configMgr = new ConfigManager({
|
|
660
|
+
/** A unique ID for this configuration - choose wisely as changing it is not supported yet! */
|
|
661
|
+
id: "my-userscript",
|
|
662
|
+
/** Default / fallback configuration data */
|
|
663
|
+
defaultConfig,
|
|
664
|
+
/** The current version of the script's config data format */
|
|
665
|
+
formatVersion,
|
|
666
|
+
/** Data format migration functions */
|
|
667
|
+
migrations,
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
/** Entrypoint of the userscript */
|
|
671
|
+
async function init() {
|
|
672
|
+
// wait for the config to be loaded from persistent storage
|
|
673
|
+
// if no data was saved in persistent storage before or getData() is called before loadData(), the value of options.defaultConfig will be returned
|
|
674
|
+
// if the previously saved data needs to be migrated to a newer version, it will happen in this function call
|
|
675
|
+
const configData = await configMgr.loadData();
|
|
676
|
+
|
|
677
|
+
console.log(configData.foo); // "hello"
|
|
678
|
+
|
|
679
|
+
// update the config
|
|
680
|
+
configData.foo = "world";
|
|
681
|
+
configData.bar = 123;
|
|
682
|
+
|
|
683
|
+
// save the updated config - synchronously to the cache and asynchronously to persistent storage
|
|
684
|
+
configMgr.saveData(configData).then(() => {
|
|
685
|
+
console.log("Config saved to persistent storage!");
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// the internal cache is updated synchronously, so the updated data can be accessed before the Promise resolves:
|
|
689
|
+
console.log(configMgr.getData().foo); // "world"
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
init();
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
</details>
|
|
696
|
+
|
|
697
|
+
<br><br>
|
|
698
|
+
|
|
510
699
|
### autoPlural()
|
|
511
|
-
Usage:
|
|
700
|
+
Usage:
|
|
701
|
+
```ts
|
|
702
|
+
autoPlural(str: string, num: number | Array | NodeList): string
|
|
703
|
+
```
|
|
512
704
|
|
|
513
705
|
Automatically pluralizes a string if the given number is not 1.
|
|
514
|
-
If an array or NodeList is passed, the
|
|
706
|
+
If an array or NodeList is passed, the amount of contained items will be used.
|
|
515
707
|
|
|
516
|
-
<details><summary><
|
|
708
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
517
709
|
|
|
518
710
|
```ts
|
|
519
711
|
autoPlural("apple", 0); // "apples"
|
|
@@ -532,11 +724,14 @@ console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 i
|
|
|
532
724
|
<br>
|
|
533
725
|
|
|
534
726
|
### pauseFor()
|
|
535
|
-
Usage:
|
|
727
|
+
Usage:
|
|
728
|
+
```ts
|
|
729
|
+
pauseFor(ms: number): Promise<void>
|
|
730
|
+
```
|
|
536
731
|
|
|
537
732
|
Pauses async execution for a given amount of time.
|
|
538
733
|
|
|
539
|
-
<details><summary><
|
|
734
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
540
735
|
|
|
541
736
|
```ts
|
|
542
737
|
async function run() {
|
|
@@ -551,13 +746,17 @@ async function run() {
|
|
|
551
746
|
<br>
|
|
552
747
|
|
|
553
748
|
### debounce()
|
|
554
|
-
Usage:
|
|
749
|
+
Usage:
|
|
750
|
+
```ts
|
|
751
|
+
debounce(func: Function, timeout?: number): Function
|
|
752
|
+
```
|
|
555
753
|
|
|
556
754
|
Debounces a function, meaning that it will only be called once after a given amount of time.
|
|
557
755
|
This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.
|
|
756
|
+
All passed properties will be passed down to the debounced function.
|
|
558
757
|
The timeout will default to 300ms if left undefined.
|
|
559
758
|
|
|
560
|
-
<details><summary><
|
|
759
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
561
760
|
|
|
562
761
|
```ts
|
|
563
762
|
window.addEventListener("resize", debounce((event) => {
|
|
@@ -581,7 +780,7 @@ fetchAdvanced(url: string, options?: {
|
|
|
581
780
|
A wrapper around the native `fetch()` function that adds options like a timeout property.
|
|
582
781
|
The timeout will default to 10 seconds if left undefined.
|
|
583
782
|
|
|
584
|
-
<details><summary><
|
|
783
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
585
784
|
|
|
586
785
|
```ts
|
|
587
786
|
fetchAdvanced("https://api.example.org/data", {
|
|
@@ -602,12 +801,15 @@ fetchAdvanced("https://api.example.org/data", {
|
|
|
602
801
|
## Arrays:
|
|
603
802
|
|
|
604
803
|
### randomItem()
|
|
605
|
-
Usage:
|
|
804
|
+
Usage:
|
|
805
|
+
```ts
|
|
806
|
+
randomItem(array: Array): any
|
|
807
|
+
```
|
|
606
808
|
|
|
607
809
|
Returns a random item from an array.
|
|
608
810
|
Returns undefined if the array is empty.
|
|
609
811
|
|
|
610
|
-
<details><summary><
|
|
812
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
611
813
|
|
|
612
814
|
```ts
|
|
613
815
|
randomItem(["foo", "bar", "baz"]); // "bar"
|
|
@@ -619,12 +821,15 @@ randomItem([ ]); // undefined
|
|
|
619
821
|
<br>
|
|
620
822
|
|
|
621
823
|
### randomItemIndex()
|
|
622
|
-
Usage:
|
|
824
|
+
Usage:
|
|
825
|
+
```ts
|
|
826
|
+
randomItemIndex(array: Array): [item: any, index: number]
|
|
827
|
+
```
|
|
623
828
|
|
|
624
829
|
Returns a tuple of a random item and its index from an array.
|
|
625
830
|
If the array is empty, it will return undefined for both values.
|
|
626
831
|
|
|
627
|
-
<details><summary><
|
|
832
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
628
833
|
|
|
629
834
|
```ts
|
|
630
835
|
randomItemIndex(["foo", "bar", "baz"]); // ["bar", 1]
|
|
@@ -640,12 +845,15 @@ const [, index] = randomItemIndex(["foo", "bar", "baz"]);
|
|
|
640
845
|
<br>
|
|
641
846
|
|
|
642
847
|
### takeRandomItem()
|
|
643
|
-
Usage:
|
|
848
|
+
Usage:
|
|
849
|
+
```ts
|
|
850
|
+
takeRandomItem(array: Array): any
|
|
851
|
+
```
|
|
644
852
|
|
|
645
853
|
Returns a random item from an array and mutates the array by removing the item.
|
|
646
854
|
Returns undefined if the array is empty.
|
|
647
855
|
|
|
648
|
-
<details><summary><
|
|
856
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
649
857
|
|
|
650
858
|
```ts
|
|
651
859
|
const arr = ["foo", "bar", "baz"];
|
|
@@ -658,12 +866,15 @@ console.log(arr); // ["foo", "baz"]
|
|
|
658
866
|
<br>
|
|
659
867
|
|
|
660
868
|
### randomizeArray()
|
|
661
|
-
Usage:
|
|
869
|
+
Usage:
|
|
870
|
+
```ts
|
|
871
|
+
randomizeArray(array: Array): Array
|
|
872
|
+
```
|
|
662
873
|
|
|
663
|
-
Returns a copy of
|
|
664
|
-
If the array is empty, the originally passed array will be returned.
|
|
874
|
+
Returns a copy of an array with its items in a random order.
|
|
875
|
+
If the array is empty, the originally passed empty array will be returned without copying.
|
|
665
876
|
|
|
666
|
-
<details><summary><
|
|
877
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
667
878
|
|
|
668
879
|
```ts
|
|
669
880
|
randomizeArray([1, 2, 3, 4, 5, 6]); // [3, 1, 5, 2, 4, 6]
|