@sv443-network/userutils 1.1.3 → 2.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 +320 -49
- package/dist/index.global.js +76 -12
- package/dist/index.js +75 -11
- package/dist/index.mjs +74 -12
- package/dist/lib/dom.d.ts +34 -11
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/misc.d.ts +12 -1
- package/dist/lib/translation.d.ts +16 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @sv443-network/userutils
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- b53a946: Added compression to `amplifyMedia()` to prevent audio clipping and distortion and modified return type accordingly:
|
|
8
|
+
- Renamed: `amplify()` to `setGain()` and `getAmpLevel()` to `getGain()`
|
|
9
|
+
- Added properties: `enable()`, `disable()`, `setLimiterOptions()` and `limiterNode`
|
|
10
|
+
- Other changes: Amplification is no longer enabled automatically, `enable()` must now be called manually after initializing
|
|
11
|
+
|
|
12
|
+
## 1.2.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- 84b37f1: Added utility type Stringifiable to describe a string or any value that can be converted to one
|
|
17
|
+
- 142c5e2: Added function insertValues() to insert values into a string with placeholders
|
|
18
|
+
- 16ce257: Added lightweight translation system
|
|
19
|
+
|
|
3
20
|
## 1.1.3
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
14
14
|
- [**Preamble**](#preamble)
|
|
15
15
|
- [**License**](#license)
|
|
16
16
|
- [**Features**](#features)
|
|
17
|
-
- [DOM
|
|
17
|
+
- [**DOM:**](#dom)
|
|
18
18
|
- [onSelector()](#onselector) - call a listener once a selector is found in the DOM
|
|
19
19
|
- [initOnSelector()](#initonselector) - needs to be called once to be able to use `onSelector()`
|
|
20
20
|
- [getSelectorMap()](#getselectormap) - returns all currently registered selectors, listeners and options
|
|
@@ -28,21 +28,29 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
28
28
|
- [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
|
|
29
29
|
- [amplifyMedia()](#amplifymedia) - amplify an audio or video element's volume past the maximum of 100%
|
|
30
30
|
- [isScrollable()](#isscrollable) - check if an element has a horizontal or vertical scroll bar
|
|
31
|
-
- [Math
|
|
31
|
+
- [**Math:**](#math)
|
|
32
32
|
- [clamp()](#clamp) - constrain a number between a min and max value
|
|
33
33
|
- [mapRange()](#maprange) - map a number from one range to the same spot in another range
|
|
34
34
|
- [randRange()](#randrange) - generate a random number between a min and max boundary
|
|
35
|
-
- [Misc
|
|
35
|
+
- [**Misc:**](#misc)
|
|
36
36
|
- [ConfigManager()](#configmanager) - class that manages persistent userscript configurations, including data migration
|
|
37
37
|
- [autoPlural()](#autoplural) - automatically pluralize a string
|
|
38
38
|
- [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
|
|
39
39
|
- [debounce()](#debounce) - call a function only once, after a given amount of time
|
|
40
40
|
- [fetchAdvanced()](#fetchadvanced) - wrapper around the fetch API with a timeout option
|
|
41
|
-
|
|
41
|
+
- [insertValues()](#insertvalues) - insert values into a string at specified placeholders
|
|
42
|
+
- [**Arrays:**](#arrays)
|
|
42
43
|
- [randomItem()](#randomitem) - returns a random item from an array
|
|
43
44
|
- [randomItemIndex()](#randomitemindex) - returns a tuple of a random item and its index from an array
|
|
44
45
|
- [takeRandomItem()](#takerandomitem) - returns a random item from an array and mutates it to remove the item
|
|
45
46
|
- [randomizeArray()](#randomizearray) - returns a copy of the array with its items in a random order
|
|
47
|
+
- [**Translation:**](#translation)
|
|
48
|
+
- [tr()](#tr) - simple translation of a string to another language
|
|
49
|
+
- [tr.addLanguage()](#traddlanguage) - add a language and its translations
|
|
50
|
+
- [tr.setLanguage()](#trsetlanguage) - set the currently active language for translations
|
|
51
|
+
- [tr.getLanguage()](#trgetlanguage) - returns the currently active language
|
|
52
|
+
- [**Utility types for TypeScript:**](#utility-types)
|
|
53
|
+
- [Stringifiable](#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
|
|
46
54
|
|
|
47
55
|
<br><br>
|
|
48
56
|
|
|
@@ -131,7 +139,7 @@ This initialization function has to be called after `DOMContentLoaded` is fired
|
|
|
131
139
|
|
|
132
140
|
Calling onSelector() before `DOMContentLoaded` is fired will not throw an error, but it also won't trigger listeners until the DOM is accessible.
|
|
133
141
|
|
|
134
|
-
<details><summary><
|
|
142
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
135
143
|
|
|
136
144
|
```ts
|
|
137
145
|
import { initOnSelector, onSelector } from "@sv443-network/userutils";
|
|
@@ -178,7 +186,7 @@ You may see all options [here](https://developer.mozilla.org/en-US/docs/Web/API/
|
|
|
178
186
|
>
|
|
179
187
|
> ⚠️ Using these extra options can have a performance impact on larger sites or sites with a constantly changing DOM.
|
|
180
188
|
|
|
181
|
-
<details><summary><
|
|
189
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
182
190
|
|
|
183
191
|
```ts
|
|
184
192
|
import { initOnSelector } from "@sv443-network/userutils";
|
|
@@ -204,7 +212,7 @@ getSelectorMap(): Map<string, OnSelectorOptions[]>
|
|
|
204
212
|
Returns a Map of all currently registered selectors and their options, including listener function.
|
|
205
213
|
Since multiple listeners can be registered for the same selector, the value of the Map is an array of `OnSelectorOptions` objects.
|
|
206
214
|
|
|
207
|
-
<details><summary><
|
|
215
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
208
216
|
|
|
209
217
|
```ts
|
|
210
218
|
import { initOnSelector, onSelector, getSelectorMap } from "@sv443-network/userutils";
|
|
@@ -249,7 +257,7 @@ getUnsafeWindow(): Window
|
|
|
249
257
|
Returns the unsafeWindow object or falls back to the regular window object if the `@grant unsafeWindow` is not given.
|
|
250
258
|
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.
|
|
251
259
|
|
|
252
|
-
<details><summary><
|
|
260
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
253
261
|
|
|
254
262
|
```ts
|
|
255
263
|
import { getUnsafeWindow } from "@sv443-network/userutils";
|
|
@@ -281,7 +289,7 @@ The passed `afterElement` will be returned.
|
|
|
281
289
|
|
|
282
290
|
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
283
291
|
|
|
284
|
-
<details><summary><
|
|
292
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
285
293
|
|
|
286
294
|
```ts
|
|
287
295
|
import { insertAfter } from "@sv443-network/userutils";
|
|
@@ -309,7 +317,7 @@ Previously registered event listeners are kept intact.
|
|
|
309
317
|
|
|
310
318
|
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
311
319
|
|
|
312
|
-
<details><summary><
|
|
320
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
313
321
|
|
|
314
322
|
```ts
|
|
315
323
|
import { addParent } from "@sv443-network/userutils";
|
|
@@ -335,7 +343,7 @@ addGlobalStyle(css: string): void
|
|
|
335
343
|
Adds a global style to the page in form of a `<style>` element that's inserted into the `<head>`.
|
|
336
344
|
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
337
345
|
|
|
338
|
-
<details><summary><
|
|
346
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
339
347
|
|
|
340
348
|
```ts
|
|
341
349
|
import { addGlobalStyle } from "@sv443-network/userutils";
|
|
@@ -363,7 +371,7 @@ Preloads images into browser cache by creating an invisible `<img>` element for
|
|
|
363
371
|
The images will be loaded in parallel and the returned Promise will only resolve once all images have been loaded.
|
|
364
372
|
The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected, but only if `rejects` is set to true.
|
|
365
373
|
|
|
366
|
-
<details><summary><
|
|
374
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
367
375
|
|
|
368
376
|
```ts
|
|
369
377
|
import { preloadImages } from "@sv443-network/userutils";
|
|
@@ -394,7 +402,7 @@ This function has to be run in response to a user interaction event, else the br
|
|
|
394
402
|
|
|
395
403
|
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
396
404
|
|
|
397
|
-
<details><summary><
|
|
405
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
398
406
|
|
|
399
407
|
```ts
|
|
400
408
|
import { openInNewTab } from "@sv443-network/userutils";
|
|
@@ -423,7 +431,7 @@ Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not
|
|
|
423
431
|
|
|
424
432
|
⚠️ 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.
|
|
425
433
|
|
|
426
|
-
<details><summary><
|
|
434
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
427
435
|
|
|
428
436
|
```ts
|
|
429
437
|
import { interceptEvent } from "@sv443-network/userutils";
|
|
@@ -453,7 +461,7 @@ This is essentially the same as [`interceptEvent()`](#interceptevent), but autom
|
|
|
453
461
|
⚠️ 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.
|
|
454
462
|
⚠️ In order for all events to be interceptable, the directive `@grant unsafeWindow` should be set.
|
|
455
463
|
|
|
456
|
-
<details><summary><
|
|
464
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
457
465
|
|
|
458
466
|
```ts
|
|
459
467
|
import { interceptWindowEvent } from "@sv443-network/userutils";
|
|
@@ -470,47 +478,98 @@ interceptWindowEvent("beforeunload", (event) => {
|
|
|
470
478
|
### amplifyMedia()
|
|
471
479
|
Usage:
|
|
472
480
|
```ts
|
|
473
|
-
amplifyMedia(mediaElement: HTMLMediaElement,
|
|
481
|
+
amplifyMedia(mediaElement: HTMLMediaElement, initialMultiplier?: number): AmplifyMediaResult
|
|
474
482
|
```
|
|
475
483
|
|
|
476
484
|
Amplifies the gain of a media element (like `<audio>` or `<video>`) by a given multiplier (defaults to 1.0).
|
|
477
485
|
This is how you can increase the volume of a media element beyond the default maximum volume of 1.0 or 100%.
|
|
478
|
-
Make sure to limit the multiplier to a reasonable value ([clamp()](#clamp) is good for this), as it may cause
|
|
486
|
+
Make sure to limit the multiplier to a reasonable value ([clamp()](#clamp) is good for this), as it may cause bleeding eardrums.
|
|
487
|
+
|
|
488
|
+
This is the processing workflow applied to the media element:
|
|
489
|
+
`MediaElement (source)` => `DynamicsCompressorNode (limiter)` => `GainNode` => `AudioDestination (output)`
|
|
490
|
+
|
|
491
|
+
A limiter (compression) is applied to the audio to prevent clipping.
|
|
492
|
+
Its properties can be changed by calling the returned function `setLimiterOptions()`
|
|
493
|
+
The default props are `{ threshold: -2, knee: 40, ratio: 12, attack: 0.003, release: 0.25 }`
|
|
479
494
|
|
|
480
495
|
⚠️ 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.
|
|
496
|
+
⚠️ Make sure to call the returned function `enable()` after calling this function to actually enable the amplification.
|
|
481
497
|
|
|
482
|
-
The returned AmplifyMediaResult
|
|
498
|
+
The returned object of the type `AmplifyMediaResult` has the following properties:
|
|
483
499
|
| Property | Description |
|
|
484
500
|
| :-- | :-- |
|
|
485
|
-
| `
|
|
486
|
-
| `
|
|
487
|
-
| `
|
|
501
|
+
| `setGain()` | Used to change the gain multiplier |
|
|
502
|
+
| `getGain()` | Returns the current gain multiplier |
|
|
503
|
+
| `enable()` | Call to enable the amplification for the first time or if it was disabled before |
|
|
504
|
+
| `disable()` | Call to disable amplification |
|
|
505
|
+
| `setLimiterOptions()` | Used for changing the [options of the DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options) - the default is `{ threshold: -2, knee: 40, ratio: 12, attack: 0.003, release: 0.25 }` |
|
|
488
506
|
| `context` | The AudioContext instance |
|
|
489
507
|
| `source` | The MediaElementSourceNode instance |
|
|
490
|
-
| `
|
|
508
|
+
| `gainNode` | The GainNode instance used for actually boosting the gain |
|
|
509
|
+
| `limiterNode` | The DynamicsCompressorNode instance used for limiting clipping and distortion |
|
|
491
510
|
|
|
492
|
-
<details><summary><
|
|
511
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
493
512
|
|
|
494
513
|
```ts
|
|
495
|
-
import { amplifyMedia } from "@sv443-network/userutils";
|
|
514
|
+
import { amplifyMedia, clamp } from "@sv443-network/userutils";
|
|
515
|
+
import type { AmplifyMediaResult } from "@sv443-network/userutils";
|
|
516
|
+
|
|
517
|
+
const audioElement = document.querySelector<HTMLAudioElement>("audio");
|
|
518
|
+
|
|
519
|
+
let ampResult: AmplifyMediaResult | undefined;
|
|
520
|
+
|
|
521
|
+
function setGain(newValue: number) {
|
|
522
|
+
if(!ampResult)
|
|
523
|
+
return;
|
|
524
|
+
// constrain the value to between 0 and 3 for safety
|
|
525
|
+
ampResult.setGain(clamp(newValue, 0, 3));
|
|
526
|
+
console.log("Gain set to", ampResult.getGain());
|
|
527
|
+
}
|
|
528
|
+
|
|
496
529
|
|
|
497
|
-
const
|
|
498
|
-
const button = document.querySelector<HTMLButtonElement>("button");
|
|
530
|
+
const amplifyButton = document.querySelector<HTMLButtonElement>("button#amplify");
|
|
499
531
|
|
|
500
|
-
// amplifyMedia needs to be called in response to a user interaction event:
|
|
501
|
-
|
|
502
|
-
|
|
532
|
+
// amplifyMedia() needs to be called in response to a user interaction event:
|
|
533
|
+
amplifyButton.addEventListener("click", () => {
|
|
534
|
+
// only needs to be initialized once, afterwards the returned object
|
|
535
|
+
// can be used to change settings and enable/disable the amplification
|
|
536
|
+
if(!ampResult) {
|
|
537
|
+
// initialize amplification and set gain to 2x
|
|
538
|
+
ampResult = amplifyMedia(audioElement, 2);
|
|
539
|
+
// enable the amplification
|
|
540
|
+
ampResult.enable();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
setGain(2.5); // set gain to 2.5x
|
|
544
|
+
|
|
545
|
+
console.log(ampResult.getGain()); // 2.5
|
|
546
|
+
});
|
|
503
547
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
548
|
+
|
|
549
|
+
const disableButton = document.querySelector<HTMLButtonElement>("button#disable");
|
|
550
|
+
|
|
551
|
+
disableButton.addEventListener("click", () => {
|
|
552
|
+
if(ampResult) {
|
|
553
|
+
// disable the amplification
|
|
554
|
+
ampResult.disable();
|
|
508
555
|
}
|
|
556
|
+
});
|
|
509
557
|
|
|
510
|
-
setGain(2); // set gain to 2x
|
|
511
|
-
setGain(3.5); // set gain to 3.5x
|
|
512
558
|
|
|
513
|
-
|
|
559
|
+
const limiterButton = document.querySelector<HTMLButtonElement>("button#limiter");
|
|
560
|
+
|
|
561
|
+
limiterButton.addEventListener("click", () => {
|
|
562
|
+
if(ampResult) {
|
|
563
|
+
// change the limiter options to a more aggressive setting
|
|
564
|
+
// see https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options
|
|
565
|
+
ampResult.setLimiterOptions({
|
|
566
|
+
threshold: -10,
|
|
567
|
+
knee: 20,
|
|
568
|
+
ratio: 20,
|
|
569
|
+
attack: 0.001,
|
|
570
|
+
release: 0.1,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
514
573
|
});
|
|
515
574
|
```
|
|
516
575
|
|
|
@@ -527,7 +586,7 @@ isScrollable(element: Element): { horizontal: boolean, vertical: boolean }
|
|
|
527
586
|
Checks if an element has a horizontal or vertical scroll bar.
|
|
528
587
|
This uses the computed style of the element, so it will also work if the element is hidden.
|
|
529
588
|
|
|
530
|
-
<details><summary><
|
|
589
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
531
590
|
|
|
532
591
|
```ts
|
|
533
592
|
import { isScrollable } from "@sv443-network/userutils";
|
|
@@ -553,7 +612,7 @@ clamp(num: number, min: number, max: number): number
|
|
|
553
612
|
|
|
554
613
|
Clamps a number between a min and max boundary (inclusive).
|
|
555
614
|
|
|
556
|
-
<details><summary><
|
|
615
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
557
616
|
|
|
558
617
|
```ts
|
|
559
618
|
import { clamp } from "@sv443-network/userutils";
|
|
@@ -586,7 +645,7 @@ mapRange(
|
|
|
586
645
|
|
|
587
646
|
Maps a number from one range to the spot it would be in another range.
|
|
588
647
|
|
|
589
|
-
<details><summary><
|
|
648
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
590
649
|
|
|
591
650
|
```ts
|
|
592
651
|
import { mapRange } from "@sv443-network/userutils";
|
|
@@ -613,7 +672,7 @@ randRange(max: number): number
|
|
|
613
672
|
Returns a random number between `min` and `max` (inclusive).
|
|
614
673
|
If only one argument is passed, it will be used as the `max` value and `min` will be set to 0.
|
|
615
674
|
|
|
616
|
-
<details><summary><
|
|
675
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
617
676
|
|
|
618
677
|
```ts
|
|
619
678
|
import { randRange } from "@sv443-network/userutils";
|
|
@@ -676,7 +735,7 @@ If `loadData()` or `setData()` are called after this, the persistent storage wil
|
|
|
676
735
|
|
|
677
736
|
<br>
|
|
678
737
|
|
|
679
|
-
<details><summary><
|
|
738
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
680
739
|
|
|
681
740
|
```ts
|
|
682
741
|
import { ConfigManager } from "@sv443-network/userutils";
|
|
@@ -769,7 +828,7 @@ autoPlural(str: string, num: number | Array | NodeList): string
|
|
|
769
828
|
Automatically pluralizes a string if the given number is not 1.
|
|
770
829
|
If an array or NodeList is passed, the amount of contained items will be used.
|
|
771
830
|
|
|
772
|
-
<details><summary><
|
|
831
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
773
832
|
|
|
774
833
|
```ts
|
|
775
834
|
import { autoPlural } from "@sv443-network/userutils";
|
|
@@ -797,7 +856,7 @@ pauseFor(ms: number): Promise<void>
|
|
|
797
856
|
|
|
798
857
|
Pauses async execution for a given amount of time.
|
|
799
858
|
|
|
800
|
-
<details><summary><
|
|
859
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
801
860
|
|
|
802
861
|
```ts
|
|
803
862
|
import { pauseFor } from "@sv443-network/userutils";
|
|
@@ -824,7 +883,7 @@ This is very useful for functions that are called repeatedly, like event listene
|
|
|
824
883
|
All passed properties will be passed down to the debounced function.
|
|
825
884
|
The timeout will default to 300ms if left undefined.
|
|
826
885
|
|
|
827
|
-
<details><summary><
|
|
886
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
828
887
|
|
|
829
888
|
```ts
|
|
830
889
|
import { debounce } from "@sv443-network/userutils";
|
|
@@ -850,7 +909,7 @@ fetchAdvanced(url: string, options?: {
|
|
|
850
909
|
A wrapper around the native `fetch()` function that adds options like a timeout property.
|
|
851
910
|
The timeout will default to 10 seconds if left undefined.
|
|
852
911
|
|
|
853
|
-
<details><summary><
|
|
912
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
854
913
|
|
|
855
914
|
```ts
|
|
856
915
|
import { fetchAdvanced } from "@sv443-network/userutils";
|
|
@@ -881,7 +940,7 @@ randomItem(array: Array): any
|
|
|
881
940
|
Returns a random item from an array.
|
|
882
941
|
Returns undefined if the array is empty.
|
|
883
942
|
|
|
884
|
-
<details><summary><
|
|
943
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
885
944
|
|
|
886
945
|
```ts
|
|
887
946
|
import { randomItem } from "@sv443-network/userutils";
|
|
@@ -903,7 +962,7 @@ randomItemIndex(array: Array): [item: any, index: number]
|
|
|
903
962
|
Returns a tuple of a random item and its index from an array.
|
|
904
963
|
If the array is empty, it will return undefined for both values.
|
|
905
964
|
|
|
906
|
-
<details><summary><
|
|
965
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
907
966
|
|
|
908
967
|
```ts
|
|
909
968
|
import { randomItemIndex } from "@sv443-network/userutils";
|
|
@@ -930,7 +989,7 @@ takeRandomItem(array: Array): any
|
|
|
930
989
|
Returns a random item from an array and mutates the array by removing the item.
|
|
931
990
|
Returns undefined if the array is empty.
|
|
932
991
|
|
|
933
|
-
<details><summary><
|
|
992
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
934
993
|
|
|
935
994
|
```ts
|
|
936
995
|
import { takeRandomItem } from "@sv443-network/userutils";
|
|
@@ -953,7 +1012,7 @@ randomizeArray(array: Array): Array
|
|
|
953
1012
|
Returns a copy of an array with its items in a random order.
|
|
954
1013
|
If the array is empty, the originally passed empty array will be returned without copying.
|
|
955
1014
|
|
|
956
|
-
<details><summary><
|
|
1015
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
957
1016
|
|
|
958
1017
|
```ts
|
|
959
1018
|
import { randomizeArray } from "@sv443-network/userutils";
|
|
@@ -968,8 +1027,220 @@ console.log(foo); // [1, 2, 3, 4, 5, 6] - original array is not mutated
|
|
|
968
1027
|
|
|
969
1028
|
</details>
|
|
970
1029
|
|
|
1030
|
+
<br><br>
|
|
1031
|
+
|
|
1032
|
+
## Translation:
|
|
1033
|
+
This is a very lightweight translation function that can be used to translate simple strings.
|
|
1034
|
+
Pluralization is not supported but can be achieved manually by adding variations to the translations, identified by a different suffix. See the example section of [`tr.addLanguage()`](#traddlanguage) for an example on how this might be done.
|
|
1035
|
+
|
|
1036
|
+
<br>
|
|
1037
|
+
|
|
1038
|
+
### tr()
|
|
1039
|
+
Usage:
|
|
1040
|
+
```ts
|
|
1041
|
+
tr(key: string, ...values: Stringifiable[]): string
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
The function returns the translation of the passed key in the language added by [`tr.addLanguage()`](#traddlanguage) and set by [`tr.setLanguage()`](#trsetlanguage)
|
|
1045
|
+
Should the translation contain placeholders in the format `%n`, where `n` is the number of the value starting at 1, they will be replaced with the respective item of the `values` rest parameter.
|
|
1046
|
+
The items of the `values` rest parameter will be stringified using `toString()` (see [Stringifiable](#stringifiable)) before being inserted into the translation.
|
|
1047
|
+
|
|
1048
|
+
If the key is not found or no language has been added or set before calling this function, it will return the key itself.
|
|
1049
|
+
If the key is found and the translation contains placeholders but no values are passed, it will return the translation as-is, including unmodified placeholders.
|
|
1050
|
+
If the key is found, the translation doesn't contain placeholders but values are still passed, they will be ignored and the translation will be returned as-is.
|
|
1051
|
+
|
|
1052
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1053
|
+
|
|
1054
|
+
```ts
|
|
1055
|
+
import { tr } from "@sv443-network/userutils";
|
|
1056
|
+
|
|
1057
|
+
tr.addLanguage("en", {
|
|
1058
|
+
"welcome": "Welcome",
|
|
1059
|
+
"welcome_name": "Welcome, %1",
|
|
1060
|
+
});
|
|
1061
|
+
tr.addLanguage("de", {
|
|
1062
|
+
"welcome": "Willkommen",
|
|
1063
|
+
"welcome_name": "Willkommen, %1",
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// this has to be called at least once before calling tr()
|
|
1067
|
+
tr.setLanguage("en");
|
|
1068
|
+
|
|
1069
|
+
console.log(tr("welcome")); // "Welcome"
|
|
1070
|
+
console.log(tr("welcome_name", "John")); // "Welcome, John"
|
|
1071
|
+
console.log(tr("non_existent_key")); // "non_existent_key"
|
|
1072
|
+
|
|
1073
|
+
// language can be changed at any time, synchronously
|
|
1074
|
+
tr.setLanguage("de");
|
|
1075
|
+
|
|
1076
|
+
console.log(tr("welcome")); // "Willkommen"
|
|
1077
|
+
console.log(tr("welcome_name", "John")); // "Willkommen, John"
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
</details>
|
|
1081
|
+
|
|
971
1082
|
<br>
|
|
972
1083
|
|
|
1084
|
+
### tr.addLanguage()
|
|
1085
|
+
Usage:
|
|
1086
|
+
```ts
|
|
1087
|
+
tr.addLanguage(language: string, translations: Record<string, string>): void
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
Adds a language and its associated translations to the translation function.
|
|
1091
|
+
The passed language can be any unique identifier, though I recommend sticking to the [ISO 639-1 standard.](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
|
1092
|
+
The passed translations should be an object where the key is the translation key used in `tr()` and the value is the translation itself.
|
|
1093
|
+
If `tr.addLanguage()` is called multiple times with the same language, the previous translations of that language will be overwritten.
|
|
1094
|
+
|
|
1095
|
+
The translation values may contain placeholders in the format `%n`, where `n` is the number of the value starting at 1.
|
|
1096
|
+
These can be used to inject values into the translation when calling `tr()`
|
|
1097
|
+
|
|
1098
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1099
|
+
|
|
1100
|
+
```ts
|
|
1101
|
+
import { tr } from "@sv443-network/userutils";
|
|
1102
|
+
|
|
1103
|
+
// add a language with associated translations:
|
|
1104
|
+
|
|
1105
|
+
tr.addLanguage("de", {
|
|
1106
|
+
"color": "Farbe",
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
// with placeholders:
|
|
1111
|
+
|
|
1112
|
+
tr.addLanguage("en", {
|
|
1113
|
+
"welcome_generic": "Welcome!",
|
|
1114
|
+
"welcome_name": "Welcome, %1!",
|
|
1115
|
+
"welcome_extended": "Welcome, %1!\nYour last login was on %2\nYou have %3 unread messages",
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
// can be used for different locales too:
|
|
1120
|
+
|
|
1121
|
+
tr.addLanguage("en-US", {
|
|
1122
|
+
"fries": "french fries",
|
|
1123
|
+
});
|
|
1124
|
+
tr.addLanguage("en-GB", {
|
|
1125
|
+
"fries": "chips",
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
// apply default values for different locales to reduce redundancy in shared translation values:
|
|
1130
|
+
|
|
1131
|
+
const translation_de = {
|
|
1132
|
+
"greeting": "Guten Tag!",
|
|
1133
|
+
"foo": "Foo",
|
|
1134
|
+
};
|
|
1135
|
+
tr.addLanguage("de-DE", translation_de);
|
|
1136
|
+
tr.addLanguage("de-CH", {
|
|
1137
|
+
...translation_de,
|
|
1138
|
+
// overwrite the "greeting" but keep other keys as they are
|
|
1139
|
+
"greeting": "Grüezi!",
|
|
1140
|
+
});
|
|
1141
|
+
tr.addLanguage("de-AT", {
|
|
1142
|
+
...translation_de,
|
|
1143
|
+
// overwrite "greeting" again but keep other keys as they are
|
|
1144
|
+
"greeting": "Grüß Gott!",
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
// example for custom pluralization:
|
|
1149
|
+
|
|
1150
|
+
tr.addLanguage("en", {
|
|
1151
|
+
"items_added-0": "Added %1 items to your cart",
|
|
1152
|
+
"items_added-1": "Added %1 item to your cart",
|
|
1153
|
+
"items_added-n": "Added all %1 items to your cart",
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
/** Returns the custom pluralization identifier for the given number of items (or size of Array/NodeList) */
|
|
1157
|
+
function pl(num: number | unknown[] | NodeList) {
|
|
1158
|
+
if(typeof num !== "number")
|
|
1159
|
+
num = num.length;
|
|
1160
|
+
|
|
1161
|
+
if(num === 0)
|
|
1162
|
+
return "0";
|
|
1163
|
+
else if(num === 1)
|
|
1164
|
+
return "1";
|
|
1165
|
+
else
|
|
1166
|
+
return "n";
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const items = [];
|
|
1170
|
+
tr(`items_added-${pl(items)}`, items.length); // "Added 0 items to your cart"
|
|
1171
|
+
|
|
1172
|
+
items.push("foo");
|
|
1173
|
+
tr(`items_added-${pl(items)}`, items.length); // "Added 1 item to your cart"
|
|
1174
|
+
|
|
1175
|
+
items.push("bar");
|
|
1176
|
+
tr(`items_added-${pl(items)}`, items.length); // "Added all 2 items to your cart"
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
</details>
|
|
1180
|
+
|
|
1181
|
+
<br>
|
|
1182
|
+
|
|
1183
|
+
### tr.setLanguage()
|
|
1184
|
+
Usage:
|
|
1185
|
+
```ts
|
|
1186
|
+
tr.setLanguage(language: string): void
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
Synchronously sets the language that will be used for translations.
|
|
1190
|
+
No validation is done on the passed language, so make sure it is correct and it has been added with `tr.addLanguage()` before calling `tr()`
|
|
1191
|
+
|
|
1192
|
+
For an example, see [`tr()`](#tr)
|
|
1193
|
+
|
|
1194
|
+
<br>
|
|
1195
|
+
|
|
1196
|
+
### tr.getLanguage()
|
|
1197
|
+
Usage:
|
|
1198
|
+
```ts
|
|
1199
|
+
tr.getLanguage(): string | undefined
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
Returns the currently active language set by [`tr.setLanguage()`](#trsetlanguage)
|
|
1203
|
+
If no language has been set yet, it will return undefined.
|
|
1204
|
+
|
|
1205
|
+
<br><br>
|
|
1206
|
+
|
|
1207
|
+
## Utility types:
|
|
1208
|
+
UserUtils also offers some utility types that can be used in TypeScript projects.
|
|
1209
|
+
They don't alter the runtime behavior of the code, but they can be used to make the code more readable and to prevent errors.
|
|
1210
|
+
|
|
1211
|
+
### Stringifiable
|
|
1212
|
+
This type describes any value that either is a string itself or can be converted to a string.
|
|
1213
|
+
To be considered stringifiable, the object needs to have a `toString()` method that returns a string (all primitive types have this method).
|
|
1214
|
+
This method allows not just explicit conversion by calling it, but also implicit conversion by passing it into the `String()` constructor or by interpolating it in a template string.
|
|
1215
|
+
|
|
1216
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1217
|
+
|
|
1218
|
+
```ts
|
|
1219
|
+
import type { Stringifiable } from "@sv443-network/userutils";
|
|
1220
|
+
|
|
1221
|
+
function logSomething(value: Stringifiable) {
|
|
1222
|
+
console.log(`Log: ${value}`); // implicit conversion using `value.toString()`
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const fooObject = {
|
|
1226
|
+
toString: () => "hello world",
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
const barObject = {
|
|
1230
|
+
baz: "",
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
logSomething("foo"); // "Log: foo"
|
|
1234
|
+
logSomething(42); // "Log: 42"
|
|
1235
|
+
logSomething(true); // "Log: true"
|
|
1236
|
+
logSomething({}); // "Log: [object Object]"
|
|
1237
|
+
logSomething(Symbol(1)); // "Log: Symbol(1)"
|
|
1238
|
+
logSomething(fooObject); // "Log: hello world"
|
|
1239
|
+
logSomething(barObject); // type error
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
</details>
|
|
1243
|
+
|
|
973
1244
|
<br><br><br><br>
|
|
974
1245
|
|
|
975
1246
|
<div style="text-align: center;" align="center">
|
package/dist/index.global.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// ==UserLibrary==
|
|
10
10
|
// @name UserUtils
|
|
11
11
|
// @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
|
|
12
|
+
// @version 2.0.0
|
|
13
13
|
// @license MIT
|
|
14
14
|
// @copyright Sv443 (https://github.com/Sv443)
|
|
15
15
|
|
|
@@ -315,22 +315,52 @@ var UserUtils = (function (exports) {
|
|
|
315
315
|
function interceptWindowEvent(eventName, predicate) {
|
|
316
316
|
return interceptEvent(getUnsafeWindow(), eventName, predicate);
|
|
317
317
|
}
|
|
318
|
-
function amplifyMedia(mediaElement,
|
|
318
|
+
function amplifyMedia(mediaElement, initialMultiplier = 1) {
|
|
319
319
|
const context = new (window.AudioContext || window.webkitAudioContext)();
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
320
|
+
const props = {
|
|
321
|
+
/** Sets the gain multiplier */
|
|
322
|
+
setGain(multiplier) {
|
|
323
|
+
props.gainNode.gain.setValueAtTime(multiplier, props.context.currentTime);
|
|
324
|
+
},
|
|
325
|
+
/** Returns the current gain multiplier */
|
|
326
|
+
getGain() {
|
|
327
|
+
return props.gainNode.gain.value;
|
|
328
|
+
},
|
|
329
|
+
/** Enable the amplification for the first time or if it was disabled before */
|
|
330
|
+
enable() {
|
|
331
|
+
props.source.connect(props.limiterNode);
|
|
332
|
+
props.limiterNode.connect(props.gainNode);
|
|
333
|
+
props.gainNode.connect(props.context.destination);
|
|
334
|
+
},
|
|
335
|
+
/** Disable the amplification */
|
|
336
|
+
disable() {
|
|
337
|
+
props.source.disconnect(props.limiterNode);
|
|
338
|
+
props.limiterNode.disconnect(props.gainNode);
|
|
339
|
+
props.gainNode.disconnect(props.context.destination);
|
|
340
|
+
props.source.connect(props.context.destination);
|
|
341
|
+
},
|
|
342
|
+
/**
|
|
343
|
+
* Set the options of the [limiter / DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options)
|
|
344
|
+
* The default is `{ threshold: -2, knee: 40, ratio: 12, attack: 0.003, release: 0.25 }`
|
|
345
|
+
*/
|
|
346
|
+
setLimiterOptions(options) {
|
|
347
|
+
for (const [key, val] of Object.entries(options))
|
|
348
|
+
props.limiterNode[key].setValueAtTime(val, props.context.currentTime);
|
|
324
349
|
},
|
|
325
|
-
getAmpLevel: () => result.gain.gain.value,
|
|
326
350
|
context,
|
|
327
351
|
source: context.createMediaElementSource(mediaElement),
|
|
328
|
-
|
|
352
|
+
gainNode: context.createGain(),
|
|
353
|
+
limiterNode: context.createDynamicsCompressor()
|
|
329
354
|
};
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
355
|
+
props.setLimiterOptions({
|
|
356
|
+
threshold: -2,
|
|
357
|
+
knee: 40,
|
|
358
|
+
ratio: 12,
|
|
359
|
+
attack: 3e-3,
|
|
360
|
+
release: 0.25
|
|
361
|
+
});
|
|
362
|
+
props.setGain(initialMultiplier);
|
|
363
|
+
return props;
|
|
334
364
|
}
|
|
335
365
|
function isScrollable(element) {
|
|
336
366
|
const { overflowX, overflowY } = getComputedStyle(element);
|
|
@@ -370,6 +400,13 @@ var UserUtils = (function (exports) {
|
|
|
370
400
|
return res;
|
|
371
401
|
});
|
|
372
402
|
}
|
|
403
|
+
function insertValues(str, ...values) {
|
|
404
|
+
return str.replace(/%\d/gm, (match) => {
|
|
405
|
+
var _a, _b;
|
|
406
|
+
const argIndex = Number(match.substring(1)) - 1;
|
|
407
|
+
return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
|
|
408
|
+
});
|
|
409
|
+
}
|
|
373
410
|
|
|
374
411
|
// lib/onSelector.ts
|
|
375
412
|
var selectorMap = /* @__PURE__ */ new Map();
|
|
@@ -421,6 +458,31 @@ var UserUtils = (function (exports) {
|
|
|
421
458
|
return selectorMap;
|
|
422
459
|
}
|
|
423
460
|
|
|
461
|
+
// lib/translation.ts
|
|
462
|
+
var trans = {};
|
|
463
|
+
var curLang;
|
|
464
|
+
function tr(key, ...args) {
|
|
465
|
+
var _a;
|
|
466
|
+
if (!curLang)
|
|
467
|
+
return key;
|
|
468
|
+
const trText = (_a = trans[curLang]) == null ? void 0 : _a[key];
|
|
469
|
+
if (!trText)
|
|
470
|
+
return key;
|
|
471
|
+
if (args.length > 0 && trText.match(/%\d/)) {
|
|
472
|
+
return insertValues(trText, ...args);
|
|
473
|
+
}
|
|
474
|
+
return trText;
|
|
475
|
+
}
|
|
476
|
+
tr.addLanguage = (language, translations) => {
|
|
477
|
+
trans[language] = translations;
|
|
478
|
+
};
|
|
479
|
+
tr.setLanguage = (language) => {
|
|
480
|
+
curLang = language;
|
|
481
|
+
};
|
|
482
|
+
tr.getLanguage = () => {
|
|
483
|
+
return curLang;
|
|
484
|
+
};
|
|
485
|
+
|
|
424
486
|
exports.ConfigManager = ConfigManager;
|
|
425
487
|
exports.addGlobalStyle = addGlobalStyle;
|
|
426
488
|
exports.addParent = addParent;
|
|
@@ -433,6 +495,7 @@ var UserUtils = (function (exports) {
|
|
|
433
495
|
exports.getUnsafeWindow = getUnsafeWindow;
|
|
434
496
|
exports.initOnSelector = initOnSelector;
|
|
435
497
|
exports.insertAfter = insertAfter;
|
|
498
|
+
exports.insertValues = insertValues;
|
|
436
499
|
exports.interceptEvent = interceptEvent;
|
|
437
500
|
exports.interceptWindowEvent = interceptWindowEvent;
|
|
438
501
|
exports.isScrollable = isScrollable;
|
|
@@ -447,6 +510,7 @@ var UserUtils = (function (exports) {
|
|
|
447
510
|
exports.randomizeArray = randomizeArray;
|
|
448
511
|
exports.removeOnSelector = removeOnSelector;
|
|
449
512
|
exports.takeRandomItem = takeRandomItem;
|
|
513
|
+
exports.tr = tr;
|
|
450
514
|
|
|
451
515
|
return exports;
|
|
452
516
|
|
package/dist/index.js
CHANGED
|
@@ -294,22 +294,52 @@ function interceptEvent(eventObject, eventName, predicate) {
|
|
|
294
294
|
function interceptWindowEvent(eventName, predicate) {
|
|
295
295
|
return interceptEvent(getUnsafeWindow(), eventName, predicate);
|
|
296
296
|
}
|
|
297
|
-
function amplifyMedia(mediaElement,
|
|
297
|
+
function amplifyMedia(mediaElement, initialMultiplier = 1) {
|
|
298
298
|
const context = new (window.AudioContext || window.webkitAudioContext)();
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
const props = {
|
|
300
|
+
/** Sets the gain multiplier */
|
|
301
|
+
setGain(multiplier) {
|
|
302
|
+
props.gainNode.gain.setValueAtTime(multiplier, props.context.currentTime);
|
|
303
|
+
},
|
|
304
|
+
/** Returns the current gain multiplier */
|
|
305
|
+
getGain() {
|
|
306
|
+
return props.gainNode.gain.value;
|
|
307
|
+
},
|
|
308
|
+
/** Enable the amplification for the first time or if it was disabled before */
|
|
309
|
+
enable() {
|
|
310
|
+
props.source.connect(props.limiterNode);
|
|
311
|
+
props.limiterNode.connect(props.gainNode);
|
|
312
|
+
props.gainNode.connect(props.context.destination);
|
|
313
|
+
},
|
|
314
|
+
/** Disable the amplification */
|
|
315
|
+
disable() {
|
|
316
|
+
props.source.disconnect(props.limiterNode);
|
|
317
|
+
props.limiterNode.disconnect(props.gainNode);
|
|
318
|
+
props.gainNode.disconnect(props.context.destination);
|
|
319
|
+
props.source.connect(props.context.destination);
|
|
320
|
+
},
|
|
321
|
+
/**
|
|
322
|
+
* Set the options of the [limiter / DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options)
|
|
323
|
+
* The default is `{ threshold: -2, knee: 40, ratio: 12, attack: 0.003, release: 0.25 }`
|
|
324
|
+
*/
|
|
325
|
+
setLimiterOptions(options) {
|
|
326
|
+
for (const [key, val] of Object.entries(options))
|
|
327
|
+
props.limiterNode[key].setValueAtTime(val, props.context.currentTime);
|
|
303
328
|
},
|
|
304
|
-
getAmpLevel: () => result.gain.gain.value,
|
|
305
329
|
context,
|
|
306
330
|
source: context.createMediaElementSource(mediaElement),
|
|
307
|
-
|
|
331
|
+
gainNode: context.createGain(),
|
|
332
|
+
limiterNode: context.createDynamicsCompressor()
|
|
308
333
|
};
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
334
|
+
props.setLimiterOptions({
|
|
335
|
+
threshold: -2,
|
|
336
|
+
knee: 40,
|
|
337
|
+
ratio: 12,
|
|
338
|
+
attack: 3e-3,
|
|
339
|
+
release: 0.25
|
|
340
|
+
});
|
|
341
|
+
props.setGain(initialMultiplier);
|
|
342
|
+
return props;
|
|
313
343
|
}
|
|
314
344
|
function isScrollable(element) {
|
|
315
345
|
const { overflowX, overflowY } = getComputedStyle(element);
|
|
@@ -349,6 +379,13 @@ function fetchAdvanced(_0) {
|
|
|
349
379
|
return res;
|
|
350
380
|
});
|
|
351
381
|
}
|
|
382
|
+
function insertValues(str, ...values) {
|
|
383
|
+
return str.replace(/%\d/gm, (match) => {
|
|
384
|
+
var _a, _b;
|
|
385
|
+
const argIndex = Number(match.substring(1)) - 1;
|
|
386
|
+
return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
|
|
387
|
+
});
|
|
388
|
+
}
|
|
352
389
|
|
|
353
390
|
// lib/onSelector.ts
|
|
354
391
|
var selectorMap = /* @__PURE__ */ new Map();
|
|
@@ -400,6 +437,31 @@ function getSelectorMap() {
|
|
|
400
437
|
return selectorMap;
|
|
401
438
|
}
|
|
402
439
|
|
|
440
|
+
// lib/translation.ts
|
|
441
|
+
var trans = {};
|
|
442
|
+
var curLang;
|
|
443
|
+
function tr(key, ...args) {
|
|
444
|
+
var _a;
|
|
445
|
+
if (!curLang)
|
|
446
|
+
return key;
|
|
447
|
+
const trText = (_a = trans[curLang]) == null ? void 0 : _a[key];
|
|
448
|
+
if (!trText)
|
|
449
|
+
return key;
|
|
450
|
+
if (args.length > 0 && trText.match(/%\d/)) {
|
|
451
|
+
return insertValues(trText, ...args);
|
|
452
|
+
}
|
|
453
|
+
return trText;
|
|
454
|
+
}
|
|
455
|
+
tr.addLanguage = (language, translations) => {
|
|
456
|
+
trans[language] = translations;
|
|
457
|
+
};
|
|
458
|
+
tr.setLanguage = (language) => {
|
|
459
|
+
curLang = language;
|
|
460
|
+
};
|
|
461
|
+
tr.getLanguage = () => {
|
|
462
|
+
return curLang;
|
|
463
|
+
};
|
|
464
|
+
|
|
403
465
|
exports.ConfigManager = ConfigManager;
|
|
404
466
|
exports.addGlobalStyle = addGlobalStyle;
|
|
405
467
|
exports.addParent = addParent;
|
|
@@ -412,6 +474,7 @@ exports.getSelectorMap = getSelectorMap;
|
|
|
412
474
|
exports.getUnsafeWindow = getUnsafeWindow;
|
|
413
475
|
exports.initOnSelector = initOnSelector;
|
|
414
476
|
exports.insertAfter = insertAfter;
|
|
477
|
+
exports.insertValues = insertValues;
|
|
415
478
|
exports.interceptEvent = interceptEvent;
|
|
416
479
|
exports.interceptWindowEvent = interceptWindowEvent;
|
|
417
480
|
exports.isScrollable = isScrollable;
|
|
@@ -426,3 +489,4 @@ exports.randomItemIndex = randomItemIndex;
|
|
|
426
489
|
exports.randomizeArray = randomizeArray;
|
|
427
490
|
exports.removeOnSelector = removeOnSelector;
|
|
428
491
|
exports.takeRandomItem = takeRandomItem;
|
|
492
|
+
exports.tr = tr;
|
package/dist/index.mjs
CHANGED
|
@@ -292,22 +292,52 @@ function interceptEvent(eventObject, eventName, predicate) {
|
|
|
292
292
|
function interceptWindowEvent(eventName, predicate) {
|
|
293
293
|
return interceptEvent(getUnsafeWindow(), eventName, predicate);
|
|
294
294
|
}
|
|
295
|
-
function amplifyMedia(mediaElement,
|
|
295
|
+
function amplifyMedia(mediaElement, initialMultiplier = 1) {
|
|
296
296
|
const context = new (window.AudioContext || window.webkitAudioContext)();
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
297
|
+
const props = {
|
|
298
|
+
/** Sets the gain multiplier */
|
|
299
|
+
setGain(multiplier) {
|
|
300
|
+
props.gainNode.gain.setValueAtTime(multiplier, props.context.currentTime);
|
|
301
|
+
},
|
|
302
|
+
/** Returns the current gain multiplier */
|
|
303
|
+
getGain() {
|
|
304
|
+
return props.gainNode.gain.value;
|
|
305
|
+
},
|
|
306
|
+
/** Enable the amplification for the first time or if it was disabled before */
|
|
307
|
+
enable() {
|
|
308
|
+
props.source.connect(props.limiterNode);
|
|
309
|
+
props.limiterNode.connect(props.gainNode);
|
|
310
|
+
props.gainNode.connect(props.context.destination);
|
|
311
|
+
},
|
|
312
|
+
/** Disable the amplification */
|
|
313
|
+
disable() {
|
|
314
|
+
props.source.disconnect(props.limiterNode);
|
|
315
|
+
props.limiterNode.disconnect(props.gainNode);
|
|
316
|
+
props.gainNode.disconnect(props.context.destination);
|
|
317
|
+
props.source.connect(props.context.destination);
|
|
318
|
+
},
|
|
319
|
+
/**
|
|
320
|
+
* Set the options of the [limiter / DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options)
|
|
321
|
+
* The default is `{ threshold: -2, knee: 40, ratio: 12, attack: 0.003, release: 0.25 }`
|
|
322
|
+
*/
|
|
323
|
+
setLimiterOptions(options) {
|
|
324
|
+
for (const [key, val] of Object.entries(options))
|
|
325
|
+
props.limiterNode[key].setValueAtTime(val, props.context.currentTime);
|
|
301
326
|
},
|
|
302
|
-
getAmpLevel: () => result.gain.gain.value,
|
|
303
327
|
context,
|
|
304
328
|
source: context.createMediaElementSource(mediaElement),
|
|
305
|
-
|
|
329
|
+
gainNode: context.createGain(),
|
|
330
|
+
limiterNode: context.createDynamicsCompressor()
|
|
306
331
|
};
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
332
|
+
props.setLimiterOptions({
|
|
333
|
+
threshold: -2,
|
|
334
|
+
knee: 40,
|
|
335
|
+
ratio: 12,
|
|
336
|
+
attack: 3e-3,
|
|
337
|
+
release: 0.25
|
|
338
|
+
});
|
|
339
|
+
props.setGain(initialMultiplier);
|
|
340
|
+
return props;
|
|
311
341
|
}
|
|
312
342
|
function isScrollable(element) {
|
|
313
343
|
const { overflowX, overflowY } = getComputedStyle(element);
|
|
@@ -347,6 +377,13 @@ function fetchAdvanced(_0) {
|
|
|
347
377
|
return res;
|
|
348
378
|
});
|
|
349
379
|
}
|
|
380
|
+
function insertValues(str, ...values) {
|
|
381
|
+
return str.replace(/%\d/gm, (match) => {
|
|
382
|
+
var _a, _b;
|
|
383
|
+
const argIndex = Number(match.substring(1)) - 1;
|
|
384
|
+
return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
|
|
385
|
+
});
|
|
386
|
+
}
|
|
350
387
|
|
|
351
388
|
// lib/onSelector.ts
|
|
352
389
|
var selectorMap = /* @__PURE__ */ new Map();
|
|
@@ -398,4 +435,29 @@ function getSelectorMap() {
|
|
|
398
435
|
return selectorMap;
|
|
399
436
|
}
|
|
400
437
|
|
|
401
|
-
|
|
438
|
+
// lib/translation.ts
|
|
439
|
+
var trans = {};
|
|
440
|
+
var curLang;
|
|
441
|
+
function tr(key, ...args) {
|
|
442
|
+
var _a;
|
|
443
|
+
if (!curLang)
|
|
444
|
+
return key;
|
|
445
|
+
const trText = (_a = trans[curLang]) == null ? void 0 : _a[key];
|
|
446
|
+
if (!trText)
|
|
447
|
+
return key;
|
|
448
|
+
if (args.length > 0 && trText.match(/%\d/)) {
|
|
449
|
+
return insertValues(trText, ...args);
|
|
450
|
+
}
|
|
451
|
+
return trText;
|
|
452
|
+
}
|
|
453
|
+
tr.addLanguage = (language, translations) => {
|
|
454
|
+
trans[language] = translations;
|
|
455
|
+
};
|
|
456
|
+
tr.setLanguage = (language) => {
|
|
457
|
+
curLang = language;
|
|
458
|
+
};
|
|
459
|
+
tr.getLanguage = () => {
|
|
460
|
+
return curLang;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
export { ConfigManager, addGlobalStyle, addParent, amplifyMedia, autoPlural, clamp, debounce, fetchAdvanced, getSelectorMap, getUnsafeWindow, initOnSelector, insertAfter, insertValues, interceptEvent, interceptWindowEvent, isScrollable, mapRange, onSelector, openInNewTab, pauseFor, preloadImages, randRange, randomItem, randomItemIndex, randomizeArray, removeOnSelector, takeRandomItem, tr };
|
package/dist/lib/dom.d.ts
CHANGED
|
@@ -45,28 +45,51 @@ export declare function interceptEvent<TEvtObj extends EventTarget, TPredicateEv
|
|
|
45
45
|
export declare function interceptWindowEvent<TEvtKey extends keyof WindowEventMap>(eventName: TEvtKey, predicate: (event: WindowEventMap[TEvtKey]) => boolean): void;
|
|
46
46
|
/**
|
|
47
47
|
* Amplifies the gain of the passed media element's audio by the specified multiplier.
|
|
48
|
-
*
|
|
48
|
+
* Also applies a limiter to prevent clipping and distortion.
|
|
49
|
+
* This function supports any MediaElement instance like `<audio>` or `<video>`
|
|
49
50
|
*
|
|
50
|
-
* This
|
|
51
|
+
* This is the audio processing workflow:
|
|
52
|
+
* `MediaElement (source)` => `DynamicsCompressorNode (limiter)` => `GainNode` => `AudioDestinationNode (output)`
|
|
51
53
|
*
|
|
54
|
+
* ⚠️ 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.
|
|
55
|
+
* ⚠️ Make sure to call the returned function `enable()` after calling this function to actually enable the amplification.
|
|
56
|
+
*
|
|
57
|
+
* @param mediaElement The media element to amplify (e.g. `<audio>` or `<video>`)
|
|
58
|
+
* @param initialMultiplier The initial gain multiplier to apply (floating point number, default is `1.0`)
|
|
52
59
|
* @returns Returns an object with the following properties:
|
|
53
60
|
* | Property | Description |
|
|
54
61
|
* | :-- | :-- |
|
|
55
|
-
* | `
|
|
56
|
-
* | `
|
|
57
|
-
* | `
|
|
62
|
+
* | `setGain()` | Used to change the gain multiplier |
|
|
63
|
+
* | `getGain()` | Returns the current gain multiplier |
|
|
64
|
+
* | `enable()` | Call to enable the amplification for the first time or if it was disabled before |
|
|
65
|
+
* | `disable()` | Call to disable amplification |
|
|
66
|
+
* | `setLimiterOptions()` | Used for changing the [options of the DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options) - the default is `{ threshold: -2, knee: 40, ratio: 12, attack: 0.003, release: 0.25 }` |
|
|
58
67
|
* | `context` | The AudioContext instance |
|
|
59
68
|
* | `source` | The MediaElementSourceNode instance |
|
|
60
|
-
* | `
|
|
69
|
+
* | `gainNode` | The GainNode instance |
|
|
70
|
+
* | `limiterNode` | The DynamicsCompressorNode instance used for limiting clipping and distortion |
|
|
61
71
|
*/
|
|
62
|
-
export declare function amplifyMedia<TElem extends HTMLMediaElement>(mediaElement: TElem,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
export declare function amplifyMedia<TElem extends HTMLMediaElement>(mediaElement: TElem, initialMultiplier?: number): {
|
|
73
|
+
/** Sets the gain multiplier */
|
|
74
|
+
setGain(multiplier: number): void;
|
|
75
|
+
/** Returns the current gain multiplier */
|
|
76
|
+
getGain(): number;
|
|
77
|
+
/** Enable the amplification for the first time or if it was disabled before */
|
|
78
|
+
enable(): void;
|
|
79
|
+
/** Disable the amplification */
|
|
80
|
+
disable(): void;
|
|
81
|
+
/**
|
|
82
|
+
* Set the options of the [limiter / DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options)
|
|
83
|
+
* The default is `{ threshold: -2, knee: 40, ratio: 12, attack: 0.003, release: 0.25 }`
|
|
84
|
+
*/
|
|
85
|
+
setLimiterOptions(options: Partial<Record<"threshold" | "knee" | "ratio" | "attack" | "release", number>>): void;
|
|
66
86
|
context: AudioContext;
|
|
67
87
|
source: MediaElementAudioSourceNode;
|
|
68
|
-
|
|
88
|
+
gainNode: GainNode;
|
|
89
|
+
limiterNode: DynamicsCompressorNode;
|
|
69
90
|
};
|
|
91
|
+
/** An object which contains the results of `amplifyMedia()` */
|
|
92
|
+
export type AmplifyMediaResult = ReturnType<typeof amplifyMedia>;
|
|
70
93
|
/** Checks if an element is scrollable in the horizontal and vertical directions */
|
|
71
94
|
export declare function isScrollable(element: Element): {
|
|
72
95
|
vertical: boolean;
|
package/dist/lib/index.d.ts
CHANGED
package/dist/lib/misc.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
/** Represents any value that is either a string itself or can be converted to one (implicitly or explicitly) because it has a toString() method */
|
|
2
|
+
export type Stringifiable = string | {
|
|
3
|
+
toString(): string;
|
|
4
|
+
};
|
|
1
5
|
/**
|
|
2
6
|
* Automatically appends an `s` to the passed `word`, if `num` is not equal to 1
|
|
3
7
|
* @param word A word in singular form, to auto-convert to plural
|
|
4
8
|
* @param num If this is an array or NodeList, the amount of items is used
|
|
5
9
|
*/
|
|
6
|
-
export declare function autoPlural(word:
|
|
10
|
+
export declare function autoPlural(word: Stringifiable, num: number | unknown[] | NodeList): string;
|
|
7
11
|
/** Pauses async execution for the specified time in ms */
|
|
8
12
|
export declare function pauseFor(time: number): Promise<void>;
|
|
9
13
|
/**
|
|
@@ -18,3 +22,10 @@ export type FetchAdvancedOpts = RequestInit & Partial<{
|
|
|
18
22
|
}>;
|
|
19
23
|
/** Calls the fetch API with special options like a timeout */
|
|
20
24
|
export declare function fetchAdvanced(url: string, options?: FetchAdvancedOpts): Promise<Response>;
|
|
25
|
+
/**
|
|
26
|
+
* Inserts the passed values into a string at the respective placeholders.
|
|
27
|
+
* The placeholder format is `%n`, where `n` is the 1-indexed argument number.
|
|
28
|
+
* @param str The string to insert the values into
|
|
29
|
+
* @param values The values to insert, in order, starting at `%1`
|
|
30
|
+
*/
|
|
31
|
+
export declare function insertValues(str: string, ...values: Stringifiable[]): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Stringifiable } from "./misc";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the translated text for the specified key in the current language set by `setLanguage()`
|
|
4
|
+
* If the key is not found in the previously registered translation, the key itself is returned.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ Remember to register a language with `tr.addLanguage()` and set it as active with `tr.setLanguage()` before using this function, otherwise it will always return the key itself.
|
|
7
|
+
* @param key Key of the translation to return
|
|
8
|
+
* @param args Optional arguments to be passed to the translated text. They will replace placeholders in the format `%n`, where `n` is the 1-indexed argument number
|
|
9
|
+
*/
|
|
10
|
+
declare function tr(key: string, ...args: Stringifiable[]): string;
|
|
11
|
+
declare namespace tr {
|
|
12
|
+
var addLanguage: (language: string, translations: Record<string, string>) => void;
|
|
13
|
+
var setLanguage: (language: string) => void;
|
|
14
|
+
var getLanguage: () => string;
|
|
15
|
+
}
|
|
16
|
+
export { tr };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sv443-network/userutils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"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
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"lint": "tsc --noEmit && eslint .",
|
|
10
10
|
"build-types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
11
11
|
"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
|
|
12
|
+
"build-global": "tsup lib/index.ts --format cjs,esm,iife --treeshake --onSuccess \"npm run post-build-global && echo Finished building.\"",
|
|
13
13
|
"build": "npm run build-common -- && npm run build-types",
|
|
14
14
|
"post-build-global": "npm run node-ts -- ./tools/post-build-global.mts",
|
|
15
|
-
"dev": "npm run build-common -- --sourcemap --watch --onSuccess \"npm run build-types
|
|
15
|
+
"dev": "npm run build-common -- --sourcemap --watch --onSuccess \"npm run build-types && echo Finished building.\"",
|
|
16
16
|
"publish-package": "changeset publish",
|
|
17
17
|
"node-ts": "node --no-warnings=ExperimentalWarning --enable-source-maps --loader ts-node/esm"
|
|
18
18
|
},
|