@sv443-network/userutils 0.1.1 → 0.2.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 +6 -0
- package/README.md +164 -32
- package/dist/index.d.mts +38 -13
- package/dist/index.d.ts +38 -13
- package/dist/index.js +17 -159
- package/dist/index.mjs +2 -145
- package/package.json +6 -5
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## UserUtils
|
|
2
2
|
Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more.
|
|
3
|
-
Contains builtin TypeScript
|
|
3
|
+
Contains builtin TypeScript declarations.
|
|
4
4
|
|
|
5
5
|
<br>
|
|
6
6
|
|
|
@@ -8,6 +8,7 @@ Contains builtin TypeScript definitions.
|
|
|
8
8
|
- [Installation](#installation)
|
|
9
9
|
- [Features](#features)
|
|
10
10
|
- [onSelector()](#onselector) - call a listener once a selector is found in the DOM
|
|
11
|
+
- [initOnSelector()](#initonselector) - needs to be called once to be able to use `onSelector()`
|
|
11
12
|
- [autoPlural()](#autoplural) - automatically pluralize a string
|
|
12
13
|
- [clamp()](#clamp) - clamp a number between a min and max value
|
|
13
14
|
- [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
|
|
@@ -54,16 +55,89 @@ If you like using this library, please consider [supporting development](https:/
|
|
|
54
55
|
## Features:
|
|
55
56
|
|
|
56
57
|
### onSelector()
|
|
57
|
-
|
|
58
|
+
Usage:
|
|
59
|
+
```ts
|
|
60
|
+
onSelector<TElement = HTMLElement>(selector: string, options: {
|
|
61
|
+
listener: (elements: TElement | NodeListOf<TElement>) => void,
|
|
62
|
+
all?: boolean,
|
|
63
|
+
continuous?: boolean,
|
|
64
|
+
}): void
|
|
65
|
+
```
|
|
58
66
|
|
|
59
67
|
Registers a listener to be called whenever the element(s) behind a selector is/are found in the DOM.
|
|
60
|
-
|
|
68
|
+
If the selector already exists, the listener will be called immediately.
|
|
69
|
+
|
|
70
|
+
If `all` is set to `true`, querySelectorAll() will be used instead and the listener will return a NodeList of matching elements.
|
|
71
|
+
This will also include elements that were already found in a previous listener call.
|
|
72
|
+
If set to `false` (default), querySelector() will be used and only the first element will be returned.
|
|
73
|
+
|
|
74
|
+
If `continuous` is set to `true`, the listener will not be deregistered after it was called once (defaults to false).
|
|
61
75
|
|
|
62
|
-
|
|
76
|
+
When using TypeScript, the generic `TElement` can be used to specify the type of the element(s) that the listener will return.
|
|
77
|
+
|
|
78
|
+
⚠️ In order to use this function, [`initOnSelector()`](#initonselector) has to be called as soon as possible.
|
|
79
|
+
This initialization function has to be called after `DOMContentLoaded` is fired (or immediately if `@run-at document-end` is set).
|
|
80
|
+
|
|
81
|
+
Calling onSelector() before `DOMContentLoaded` is fired will not throw an error, but it also won't trigger listeners until the DOM is accessible.
|
|
82
|
+
|
|
83
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
84
|
+
|
|
63
85
|
```ts
|
|
64
|
-
|
|
86
|
+
document.addEventListener("DOMContentLoaded", initOnSelector);
|
|
87
|
+
|
|
88
|
+
// Continuously checks if `div` elements are added to the DOM, then returns all of them (even previously detected ones) in a NodeList
|
|
89
|
+
onSelector("div", {
|
|
90
|
+
listener: (element) => {
|
|
91
|
+
console.log("Elements found:", element);
|
|
92
|
+
},
|
|
93
|
+
all: true,
|
|
94
|
+
continuous: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Checks if an input element with a value attribute of "5" is added to the DOM, then returns it and deregisters the listener
|
|
98
|
+
onSelector("input[value=\"5\"]", {
|
|
99
|
+
listener: (element) => {
|
|
100
|
+
console.log("Element found:", element);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
65
103
|
```
|
|
66
104
|
|
|
105
|
+
</details>
|
|
106
|
+
|
|
107
|
+
<br>
|
|
108
|
+
|
|
109
|
+
### initOnSelector()
|
|
110
|
+
Usage:
|
|
111
|
+
```ts
|
|
112
|
+
initOnSelector(options: {
|
|
113
|
+
attributes?: boolean,
|
|
114
|
+
characterData?: boolean,
|
|
115
|
+
}): void
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Initializes the MutationObserver that is used by [`onSelector()`](#onselector) to check for the registered selectors whenever a DOM change occurs on the `<body>`
|
|
119
|
+
By default, this only checks if elements are added or removed (at any depth).
|
|
120
|
+
|
|
121
|
+
Set `attributes` to `true` to also check for attribute changes on every single descendant of the `<body>` (defaults to false).
|
|
122
|
+
Set `characterData` to `true` to also check for character data changes on every single descendant of the `<body>` (defaults to false).
|
|
123
|
+
|
|
124
|
+
⚠️ Using these extra options can have a performance impact on larger sites or sites with a constantly changing DOM.
|
|
125
|
+
|
|
126
|
+
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
127
|
+
|
|
128
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
132
|
+
initOnSelector({
|
|
133
|
+
attributes: true,
|
|
134
|
+
characterData: true,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
</details>
|
|
140
|
+
|
|
67
141
|
<br>
|
|
68
142
|
|
|
69
143
|
### autoPlural()
|
|
@@ -72,7 +146,8 @@ Usage: `autoPlural(str: string, num: number | Array | NodeList): string`
|
|
|
72
146
|
Automatically pluralizes a string if the given number is not 1.
|
|
73
147
|
If an array or NodeList is passed, the length of it will be used.
|
|
74
148
|
|
|
75
|
-
Example
|
|
149
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
150
|
+
|
|
76
151
|
```ts
|
|
77
152
|
autoPlural("apple", 0); // "apples"
|
|
78
153
|
autoPlural("apple", 1); // "apple"
|
|
@@ -82,6 +157,8 @@ autoPlural("apple", [1]); // "apple"
|
|
|
82
157
|
autoPlural("apple", [1, 2]); // "apples"
|
|
83
158
|
```
|
|
84
159
|
|
|
160
|
+
</details>
|
|
161
|
+
|
|
85
162
|
<br>
|
|
86
163
|
|
|
87
164
|
### clamp()
|
|
@@ -89,13 +166,16 @@ Usage: `clamp(num: number, min: number, max: number): number`
|
|
|
89
166
|
|
|
90
167
|
Clamps a number between a min and max value.
|
|
91
168
|
|
|
92
|
-
Example
|
|
169
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
170
|
+
|
|
93
171
|
```ts
|
|
94
172
|
clamp(5, 0, 10); // 5
|
|
95
173
|
clamp(-1, 0, 10); // 0
|
|
96
174
|
clamp(Infinity, 0, 10); // 10
|
|
97
175
|
```
|
|
98
176
|
|
|
177
|
+
</details>
|
|
178
|
+
|
|
99
179
|
<br>
|
|
100
180
|
|
|
101
181
|
### pauseFor()
|
|
@@ -103,7 +183,8 @@ Usage: `pauseFor(ms: number): Promise<void>`
|
|
|
103
183
|
|
|
104
184
|
Pauses async execution for a given amount of time.
|
|
105
185
|
|
|
106
|
-
Example
|
|
186
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
187
|
+
|
|
107
188
|
```ts
|
|
108
189
|
async function run() {
|
|
109
190
|
console.log("Hello");
|
|
@@ -112,6 +193,8 @@ async function run() {
|
|
|
112
193
|
}
|
|
113
194
|
```
|
|
114
195
|
|
|
196
|
+
</details>
|
|
197
|
+
|
|
115
198
|
<br>
|
|
116
199
|
|
|
117
200
|
### debounce()
|
|
@@ -121,13 +204,16 @@ Debounces a function, meaning that it will only be called once after a given amo
|
|
|
121
204
|
This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.
|
|
122
205
|
The timeout will default to 300ms if left undefined.
|
|
123
206
|
|
|
124
|
-
Example
|
|
207
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
208
|
+
|
|
125
209
|
```ts
|
|
126
210
|
window.addEventListener("resize", debounce((event) => {
|
|
127
211
|
console.log("Window was resized:", event);
|
|
128
212
|
}, 500)); // 500ms timeout
|
|
129
213
|
```
|
|
130
214
|
|
|
215
|
+
</details>
|
|
216
|
+
|
|
131
217
|
<br>
|
|
132
218
|
|
|
133
219
|
### getUnsafeWindow()
|
|
@@ -136,7 +222,8 @@ Usage: `getUnsafeWindow(): Window`
|
|
|
136
222
|
Returns the unsafeWindow object or falls back to the regular window object if the `@grant unsafeWindow` is not given.
|
|
137
223
|
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.
|
|
138
224
|
|
|
139
|
-
Example
|
|
225
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
226
|
+
|
|
140
227
|
```ts
|
|
141
228
|
const mouseEvent = new MouseEvent("click", {
|
|
142
229
|
view: getUnsafeWindow(),
|
|
@@ -144,15 +231,20 @@ const mouseEvent = new MouseEvent("click", {
|
|
|
144
231
|
document.body.dispatchEvent(mouseEvent);
|
|
145
232
|
```
|
|
146
233
|
|
|
234
|
+
</details>
|
|
235
|
+
|
|
147
236
|
<br>
|
|
148
237
|
|
|
149
238
|
### insertAfter()
|
|
150
239
|
Usage: `insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement`
|
|
151
240
|
|
|
152
241
|
Inserts the element passed as `afterElement` as a sibling after the passed `beforeElement`.
|
|
153
|
-
The `afterElement` will be returned.
|
|
242
|
+
The passed `afterElement` will be returned.
|
|
243
|
+
|
|
244
|
+
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
154
245
|
|
|
155
|
-
Example
|
|
246
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
247
|
+
|
|
156
248
|
```ts
|
|
157
249
|
const beforeElement = document.querySelector("#before");
|
|
158
250
|
const afterElement = document.createElement("div");
|
|
@@ -160,15 +252,20 @@ afterElement.innerText = "After";
|
|
|
160
252
|
insertAfter(beforeElement, afterElement);
|
|
161
253
|
```
|
|
162
254
|
|
|
255
|
+
</details>
|
|
256
|
+
|
|
163
257
|
<br>
|
|
164
258
|
|
|
165
259
|
### addParent()
|
|
166
260
|
Usage: `addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement`
|
|
167
261
|
|
|
168
262
|
Adds a parent element around the passed `element` and returns the new parent.
|
|
169
|
-
Previously registered event listeners
|
|
263
|
+
Previously registered event listeners are kept intact.
|
|
170
264
|
|
|
171
|
-
|
|
265
|
+
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
266
|
+
|
|
267
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
268
|
+
|
|
172
269
|
```ts
|
|
173
270
|
const element = document.querySelector("#element");
|
|
174
271
|
const newParent = document.createElement("a");
|
|
@@ -176,15 +273,18 @@ newParent.href = "https://example.org/";
|
|
|
176
273
|
addParent(element, newParent);
|
|
177
274
|
```
|
|
178
275
|
|
|
276
|
+
</details>
|
|
277
|
+
|
|
179
278
|
<br>
|
|
180
279
|
|
|
181
280
|
### addGlobalStyle()
|
|
182
281
|
Usage: `addGlobalStyle(css: string): void`
|
|
183
282
|
|
|
184
283
|
Adds a global style to the page in form of a `<style>` element that's inserted into the `<head>`.
|
|
185
|
-
This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
284
|
+
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
186
285
|
|
|
187
|
-
Example
|
|
286
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
287
|
+
|
|
188
288
|
```ts
|
|
189
289
|
document.addEventListener("DOMContentLoaded", () => {
|
|
190
290
|
addGlobalStyle(`
|
|
@@ -195,43 +295,61 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
195
295
|
});
|
|
196
296
|
```
|
|
197
297
|
|
|
298
|
+
</details>
|
|
299
|
+
|
|
198
300
|
<br>
|
|
199
301
|
|
|
200
302
|
### preloadImages()
|
|
201
|
-
Usage: `preloadImages(
|
|
303
|
+
Usage: `preloadImages(urls: string[], rejects?: boolean): Promise<void>`
|
|
202
304
|
|
|
203
305
|
Preloads images into browser cache by creating an invisible `<img>` element for each URL passed.
|
|
204
306
|
The images will be loaded in parallel and the returned Promise will only resolve once all images have been loaded.
|
|
205
|
-
The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected.
|
|
307
|
+
The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected, but only if `rejects` is set to true.
|
|
206
308
|
|
|
207
|
-
Example
|
|
309
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
310
|
+
|
|
208
311
|
```ts
|
|
209
|
-
preloadImages(
|
|
312
|
+
preloadImages([
|
|
210
313
|
"https://example.org/image1.png",
|
|
211
314
|
"https://example.org/image2.png",
|
|
212
315
|
"https://example.org/image3.png",
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
|
|
316
|
+
], true)
|
|
317
|
+
.then((results) => {
|
|
318
|
+
console.log("Images preloaded. Results:", results);
|
|
319
|
+
});
|
|
216
320
|
```
|
|
217
321
|
|
|
322
|
+
</details>
|
|
323
|
+
|
|
218
324
|
<br>
|
|
219
325
|
|
|
220
326
|
### fetchAdvanced()
|
|
221
|
-
Usage:
|
|
327
|
+
Usage:
|
|
328
|
+
```ts
|
|
329
|
+
fetchAdvanced(url: string, options?: {
|
|
330
|
+
timeout?: number,
|
|
331
|
+
// any other options from fetch() except for signal
|
|
332
|
+
}): Promise<Response>
|
|
333
|
+
```
|
|
222
334
|
|
|
223
335
|
A wrapper around the native `fetch()` function that adds options like a timeout property.
|
|
224
336
|
The timeout will default to 10 seconds if left undefined.
|
|
225
337
|
|
|
226
|
-
Example
|
|
338
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
339
|
+
|
|
227
340
|
```ts
|
|
228
|
-
fetchAdvanced("https://example.org/", {
|
|
341
|
+
fetchAdvanced("https://api.example.org/data", {
|
|
229
342
|
timeout: 5000,
|
|
230
|
-
|
|
231
|
-
|
|
343
|
+
headers: {
|
|
344
|
+
"Accept": "application/json",
|
|
345
|
+
},
|
|
346
|
+
}).then(async (response) => {
|
|
347
|
+
console.log("Data:", await response.json());
|
|
232
348
|
});
|
|
233
349
|
```
|
|
234
350
|
|
|
351
|
+
</details>
|
|
352
|
+
|
|
235
353
|
<br>
|
|
236
354
|
|
|
237
355
|
### openInNewTab()
|
|
@@ -241,29 +359,38 @@ Creates an invisible anchor with a `_blank` target and clicks it.
|
|
|
241
359
|
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.
|
|
242
360
|
This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.
|
|
243
361
|
|
|
244
|
-
|
|
362
|
+
⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
363
|
+
|
|
364
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
365
|
+
|
|
245
366
|
```ts
|
|
246
367
|
document.querySelector("#button").addEventListener("click", () => {
|
|
247
368
|
openInNewTab("https://example.org/");
|
|
248
369
|
});
|
|
249
370
|
```
|
|
250
371
|
|
|
372
|
+
</details>
|
|
373
|
+
|
|
251
374
|
<br>
|
|
252
375
|
|
|
253
376
|
### interceptEvent()
|
|
254
377
|
Usage: `interceptEvent(eventObject: EventTarget, eventName: string, predicate: () => boolean): void`
|
|
255
378
|
|
|
256
379
|
Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
257
|
-
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.
|
|
258
380
|
Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
|
|
259
381
|
|
|
260
|
-
|
|
382
|
+
⚠️ 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.
|
|
383
|
+
|
|
384
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
385
|
+
|
|
261
386
|
```ts
|
|
262
387
|
interceptEvent(document.body, "click", () => {
|
|
263
388
|
return true; // prevent all click events on the body
|
|
264
389
|
});
|
|
265
390
|
```
|
|
266
391
|
|
|
392
|
+
</details>
|
|
393
|
+
|
|
267
394
|
<br>
|
|
268
395
|
|
|
269
396
|
### interceptWindowEvent()
|
|
@@ -272,13 +399,18 @@ Usage: `interceptWindowEvent(eventName: string, predicate: () => boolean): void`
|
|
|
272
399
|
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.
|
|
273
400
|
This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
|
|
274
401
|
|
|
275
|
-
|
|
402
|
+
⚠️ 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.
|
|
403
|
+
|
|
404
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
405
|
+
|
|
276
406
|
```ts
|
|
277
407
|
interceptWindowEvent("beforeunload", () => {
|
|
278
408
|
return true; // prevent the pesky "Are you sure you want to leave this page?" popup
|
|
279
409
|
});
|
|
280
410
|
```
|
|
281
411
|
|
|
412
|
+
</details>
|
|
413
|
+
|
|
282
414
|
|
|
283
415
|
<br><br>
|
|
284
416
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
type
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
type InitOnSelectorOpts = {
|
|
2
|
+
/** Set to true if mutations to any element's attributes are to also trigger the onSelector check (warning: this might draw a lot of performance on larger sites) */
|
|
3
|
+
attributes?: boolean;
|
|
4
|
+
/** Set to true if mutations to any element's character data are to also trigger the onSelector check (warning: this might draw a lot of performance on larger sites) */
|
|
5
|
+
characterData?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type OnSelectorOpts<TElem extends Element = HTMLElement> = SelectorOptsOne<TElem> | SelectorOptsAll<TElem>;
|
|
8
|
+
type SelectorOptsOne<TElem extends Element> = SelectorOptsBase & {
|
|
9
|
+
/** Whether to use `querySelectorAll()` instead - default is false */
|
|
10
|
+
all?: false;
|
|
11
|
+
/** Gets called whenever the selector was found in the DOM */
|
|
12
|
+
listener: (element: TElem) => void;
|
|
13
|
+
};
|
|
14
|
+
type SelectorOptsAll<TElem extends Element> = SelectorOptsBase & {
|
|
15
|
+
/** Whether to use `querySelectorAll()` instead - default is false */
|
|
16
|
+
all: true;
|
|
17
|
+
/** Gets called whenever the selector was found in the DOM */
|
|
18
|
+
listener: (elements: NodeListOf<TElem>) => void;
|
|
19
|
+
};
|
|
20
|
+
type SelectorOptsBase = {
|
|
21
|
+
/** Whether to call the listener continuously instead of once - default is false */
|
|
7
22
|
continuous?: boolean;
|
|
8
23
|
};
|
|
9
24
|
type FetchAdvancedOpts = RequestInit & Partial<{
|
|
25
|
+
/** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
|
|
10
26
|
timeout: number;
|
|
11
27
|
}>;
|
|
12
28
|
|
|
@@ -62,12 +78,14 @@ declare function fetchAdvanced(url: string, options?: FetchAdvancedOpts): Promis
|
|
|
62
78
|
declare function openInNewTab(href: string): void;
|
|
63
79
|
/**
|
|
64
80
|
* Intercepts the specified event on the passed object and prevents it from being called if the called `predicate` function returns a truthy value.
|
|
65
|
-
*
|
|
81
|
+
* 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.
|
|
82
|
+
* Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
|
|
66
83
|
*/
|
|
67
84
|
declare function interceptEvent<TEvtObj extends EventTarget>(eventObject: TEvtObj, eventName: Parameters<TEvtObj["addEventListener"]>[0], predicate: () => boolean): void;
|
|
68
85
|
/**
|
|
69
86
|
* Intercepts the specified event on the window object and prevents it from being called if the called `predicate` function returns a truthy value.
|
|
70
|
-
*
|
|
87
|
+
* 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.
|
|
88
|
+
* Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
|
|
71
89
|
*/
|
|
72
90
|
declare function interceptWindowEvent(eventName: keyof WindowEventMap, predicate: () => boolean): void;
|
|
73
91
|
|
|
@@ -75,10 +93,17 @@ declare function interceptWindowEvent(eventName: keyof WindowEventMap, predicate
|
|
|
75
93
|
* Calls the `listener` as soon as the `selector` exists in the DOM.
|
|
76
94
|
* Listeners are deleted when they are called once, unless `options.continuous` is set.
|
|
77
95
|
* Multiple listeners with the same selector may be registered.
|
|
78
|
-
* @
|
|
96
|
+
* @param selector The selector to listen for
|
|
97
|
+
* @param options Used for switching to `querySelectorAll()` and for calling the listener continuously
|
|
98
|
+
* @template TElem The type of element that the listener will return as its argument (defaults to the generic HTMLElement)
|
|
99
|
+
*/
|
|
100
|
+
declare function onSelector<TElem extends Element = HTMLElement>(selector: string, options: OnSelectorOpts<TElem>): void;
|
|
101
|
+
/** Removes all listeners registered in `onSelector()` that have the given selector */
|
|
102
|
+
declare function removeOnSelector(selector: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Initializes a MutationObserver that checks for all registered selectors whenever an element is added to or removed from the `<body>`
|
|
105
|
+
* @param opts For fine-tuning when the MutationObserver checks for the selectors
|
|
79
106
|
*/
|
|
80
|
-
declare function
|
|
81
|
-
/** Removes all listeners registered in `onSelector()` with a matching selector property */
|
|
82
|
-
declare function removeOnSelector(selector: string): void;
|
|
107
|
+
declare function initOnSelector(opts?: InitOnSelectorOpts): void;
|
|
83
108
|
|
|
84
|
-
export { FetchAdvancedOpts,
|
|
109
|
+
export { FetchAdvancedOpts, InitOnSelectorOpts, OnSelectorOpts, addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getUnsafeWindow, initOnSelector, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
type
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
type InitOnSelectorOpts = {
|
|
2
|
+
/** Set to true if mutations to any element's attributes are to also trigger the onSelector check (warning: this might draw a lot of performance on larger sites) */
|
|
3
|
+
attributes?: boolean;
|
|
4
|
+
/** Set to true if mutations to any element's character data are to also trigger the onSelector check (warning: this might draw a lot of performance on larger sites) */
|
|
5
|
+
characterData?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type OnSelectorOpts<TElem extends Element = HTMLElement> = SelectorOptsOne<TElem> | SelectorOptsAll<TElem>;
|
|
8
|
+
type SelectorOptsOne<TElem extends Element> = SelectorOptsBase & {
|
|
9
|
+
/** Whether to use `querySelectorAll()` instead - default is false */
|
|
10
|
+
all?: false;
|
|
11
|
+
/** Gets called whenever the selector was found in the DOM */
|
|
12
|
+
listener: (element: TElem) => void;
|
|
13
|
+
};
|
|
14
|
+
type SelectorOptsAll<TElem extends Element> = SelectorOptsBase & {
|
|
15
|
+
/** Whether to use `querySelectorAll()` instead - default is false */
|
|
16
|
+
all: true;
|
|
17
|
+
/** Gets called whenever the selector was found in the DOM */
|
|
18
|
+
listener: (elements: NodeListOf<TElem>) => void;
|
|
19
|
+
};
|
|
20
|
+
type SelectorOptsBase = {
|
|
21
|
+
/** Whether to call the listener continuously instead of once - default is false */
|
|
7
22
|
continuous?: boolean;
|
|
8
23
|
};
|
|
9
24
|
type FetchAdvancedOpts = RequestInit & Partial<{
|
|
25
|
+
/** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
|
|
10
26
|
timeout: number;
|
|
11
27
|
}>;
|
|
12
28
|
|
|
@@ -62,12 +78,14 @@ declare function fetchAdvanced(url: string, options?: FetchAdvancedOpts): Promis
|
|
|
62
78
|
declare function openInNewTab(href: string): void;
|
|
63
79
|
/**
|
|
64
80
|
* Intercepts the specified event on the passed object and prevents it from being called if the called `predicate` function returns a truthy value.
|
|
65
|
-
*
|
|
81
|
+
* 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.
|
|
82
|
+
* Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
|
|
66
83
|
*/
|
|
67
84
|
declare function interceptEvent<TEvtObj extends EventTarget>(eventObject: TEvtObj, eventName: Parameters<TEvtObj["addEventListener"]>[0], predicate: () => boolean): void;
|
|
68
85
|
/**
|
|
69
86
|
* Intercepts the specified event on the window object and prevents it from being called if the called `predicate` function returns a truthy value.
|
|
70
|
-
*
|
|
87
|
+
* 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.
|
|
88
|
+
* Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
|
|
71
89
|
*/
|
|
72
90
|
declare function interceptWindowEvent(eventName: keyof WindowEventMap, predicate: () => boolean): void;
|
|
73
91
|
|
|
@@ -75,10 +93,17 @@ declare function interceptWindowEvent(eventName: keyof WindowEventMap, predicate
|
|
|
75
93
|
* Calls the `listener` as soon as the `selector` exists in the DOM.
|
|
76
94
|
* Listeners are deleted when they are called once, unless `options.continuous` is set.
|
|
77
95
|
* Multiple listeners with the same selector may be registered.
|
|
78
|
-
* @
|
|
96
|
+
* @param selector The selector to listen for
|
|
97
|
+
* @param options Used for switching to `querySelectorAll()` and for calling the listener continuously
|
|
98
|
+
* @template TElem The type of element that the listener will return as its argument (defaults to the generic HTMLElement)
|
|
99
|
+
*/
|
|
100
|
+
declare function onSelector<TElem extends Element = HTMLElement>(selector: string, options: OnSelectorOpts<TElem>): void;
|
|
101
|
+
/** Removes all listeners registered in `onSelector()` that have the given selector */
|
|
102
|
+
declare function removeOnSelector(selector: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Initializes a MutationObserver that checks for all registered selectors whenever an element is added to or removed from the `<body>`
|
|
105
|
+
* @param opts For fine-tuning when the MutationObserver checks for the selectors
|
|
79
106
|
*/
|
|
80
|
-
declare function
|
|
81
|
-
/** Removes all listeners registered in `onSelector()` with a matching selector property */
|
|
82
|
-
declare function removeOnSelector(selector: string): void;
|
|
107
|
+
declare function initOnSelector(opts?: InitOnSelectorOpts): void;
|
|
83
108
|
|
|
84
|
-
export { FetchAdvancedOpts,
|
|
109
|
+
export { FetchAdvancedOpts, InitOnSelectorOpts, OnSelectorOpts, addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getUnsafeWindow, initOnSelector, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
|
package/dist/index.js
CHANGED
|
@@ -1,162 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var __defProps = Object.defineProperties;
|
|
5
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
9
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
10
|
-
var __spreadValues = (a, b) => {
|
|
11
|
-
for (var prop in b || (b = {}))
|
|
12
|
-
if (__hasOwnProp.call(b, prop))
|
|
13
|
-
__defNormalProp(a, prop, b[prop]);
|
|
14
|
-
if (__getOwnPropSymbols)
|
|
15
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
16
|
-
if (__propIsEnum.call(b, prop))
|
|
17
|
-
__defNormalProp(a, prop, b[prop]);
|
|
18
|
-
}
|
|
19
|
-
return a;
|
|
20
|
-
};
|
|
21
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
22
|
-
var __async = (__this, __arguments, generator) => {
|
|
23
|
-
return new Promise((resolve, reject) => {
|
|
24
|
-
var fulfilled = (value) => {
|
|
25
|
-
try {
|
|
26
|
-
step(generator.next(value));
|
|
27
|
-
} catch (e) {
|
|
28
|
-
reject(e);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
var rejected = (value) => {
|
|
32
|
-
try {
|
|
33
|
-
step(generator.throw(value));
|
|
34
|
-
} catch (e) {
|
|
35
|
-
reject(e);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
39
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
40
|
-
});
|
|
41
|
-
};
|
|
3
|
+
var b=Object.defineProperty,v=Object.defineProperties;var y=Object.getOwnPropertyDescriptors;var m=Object.getOwnPropertySymbols;var x=Object.prototype.hasOwnProperty,g=Object.prototype.propertyIsEnumerable;var f=(t,e,n)=>e in t?b(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,u=(t,e)=>{for(var n in e||(e={}))x.call(e,n)&&f(t,n,e[n]);if(m)for(var n of m(e))g.call(e,n)&&f(t,n,e[n]);return t},d=(t,e)=>v(t,y(e));var E=(t,e,n)=>new Promise((r,o)=>{var s=c=>{try{l(n.next(c));}catch(p){o(p);}},i=c=>{try{l(n.throw(c));}catch(p){o(p);}},l=c=>c.done?r(c.value):Promise.resolve(c.value).then(s,i);l((n=n.apply(t,e)).next());});function w(t,e){return (Array.isArray(e)||e instanceof NodeList)&&(e=e.length),`${t}${e===1?"":"s"}`}function M(t,e,n){return Math.max(Math.min(t,n),e)}function S(t){return new Promise(e=>{setTimeout(e,t);})}function A(t,e=300){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>t.apply(this,r),e);}}function h(){try{return unsafeWindow}catch(t){return window}}function _(t,e){var n;return (n=t.parentNode)==null||n.insertBefore(e,t.nextSibling),e}function k(t,e){let n=t.parentNode;if(!n)throw new Error("Element doesn't have a parent node");return n.replaceChild(e,t),e.appendChild(t),e}function H(t){let e=document.createElement("style");e.innerHTML=t,document.head.appendChild(e);}function I(t,e=!1){let n=t.map(r=>new Promise((o,s)=>{let i=new Image;i.src=r,i.addEventListener("load",()=>o(i)),i.addEventListener("error",l=>e&&s(l));}));return Promise.allSettled(n)}function j(n){return E(this,arguments,function*(t,e={}){let{timeout:r=1e4}=e,o=new AbortController,s=setTimeout(()=>o.abort(),r),i=yield fetch(t,d(u({},e),{signal:o.signal}));return clearTimeout(s),i})}function N(t){let e=document.createElement("a");Object.assign(e,{className:"userutils-open-in-new-tab",target:"_blank",rel:"noopener noreferrer",href:t}),e.style.display="none",document.body.appendChild(e),e.click(),setTimeout(e.remove,50);}function O(t,e,n){typeof Error.stackTraceLimit=="number"&&Error.stackTraceLimit<1e3&&(Error.stackTraceLimit=1e3),function(r){element.__proto__.addEventListener=function(...o){if(!(o[0]===e&&n()))return r.apply(this,o)};}(t.__proto__.addEventListener);}function P(t,e){return O(h(),t,e)}var a=new Map;function W(t,e){let n=[];a.has(t)&&(n=a.get(t)),n.push(e),a.set(t,n),T(t,n);}function q(t){return a.delete(t)}function T(t,e){let n=[];e.forEach((r,o)=>{try{let s=r.all?document.querySelectorAll(t):document.querySelector(t);s&&(r.listener(s),r.continuous||n.push(o));}catch(s){}}),n.length>0&&a.set(t,e.filter((r,o)=>!n.includes(o)));}function $(t={}){new MutationObserver(()=>{for(let[n,r]of a.entries())T(n,r);}).observe(document.body,d(u({},t),{childList:!0}));}
|
|
42
4
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return function(...args) {
|
|
60
|
-
clearTimeout(timer);
|
|
61
|
-
timer = setTimeout(() => func.apply(this, args), timeout);
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function getUnsafeWindow() {
|
|
65
|
-
try {
|
|
66
|
-
return unsafeWindow;
|
|
67
|
-
} catch (e) {
|
|
68
|
-
return window;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function insertAfter(beforeElement, afterElement) {
|
|
72
|
-
var _a;
|
|
73
|
-
(_a = beforeElement.parentNode) == null ? void 0 : _a.insertBefore(afterElement, beforeElement.nextSibling);
|
|
74
|
-
return afterElement;
|
|
75
|
-
}
|
|
76
|
-
function addParent(element2, newParent) {
|
|
77
|
-
const oldParent = element2.parentNode;
|
|
78
|
-
if (!oldParent)
|
|
79
|
-
throw new Error("Element doesn't have a parent node");
|
|
80
|
-
oldParent.replaceChild(newParent, element2);
|
|
81
|
-
newParent.appendChild(element2);
|
|
82
|
-
return newParent;
|
|
83
|
-
}
|
|
84
|
-
function addGlobalStyle(style) {
|
|
85
|
-
const styleElem = document.createElement("style");
|
|
86
|
-
styleElem.innerHTML = style;
|
|
87
|
-
document.head.appendChild(styleElem);
|
|
88
|
-
}
|
|
89
|
-
function preloadImages(srcUrls, rejects = false) {
|
|
90
|
-
const promises = srcUrls.map((src) => new Promise((res, rej) => {
|
|
91
|
-
const image = new Image();
|
|
92
|
-
image.src = src;
|
|
93
|
-
image.addEventListener("load", () => res(image));
|
|
94
|
-
image.addEventListener("error", (evt) => rejects && rej(evt));
|
|
95
|
-
}));
|
|
96
|
-
return Promise.allSettled(promises);
|
|
97
|
-
}
|
|
98
|
-
function fetchAdvanced(_0) {
|
|
99
|
-
return __async(this, arguments, function* (url, options = {}) {
|
|
100
|
-
const { timeout = 1e4 } = options;
|
|
101
|
-
const controller = new AbortController();
|
|
102
|
-
const id = setTimeout(() => controller.abort(), timeout);
|
|
103
|
-
const res = yield fetch(url, __spreadProps(__spreadValues({}, options), {
|
|
104
|
-
signal: controller.signal
|
|
105
|
-
}));
|
|
106
|
-
clearTimeout(id);
|
|
107
|
-
return res;
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
function openInNewTab(href) {
|
|
111
|
-
const openElem = document.createElement("a");
|
|
112
|
-
Object.assign(openElem, {
|
|
113
|
-
className: "userutils-open-in-new-tab",
|
|
114
|
-
target: "_blank",
|
|
115
|
-
rel: "noopener noreferrer",
|
|
116
|
-
href
|
|
117
|
-
});
|
|
118
|
-
openElem.style.display = "none";
|
|
119
|
-
document.body.appendChild(openElem);
|
|
120
|
-
openElem.click();
|
|
121
|
-
setTimeout(openElem.remove, 50);
|
|
122
|
-
}
|
|
123
|
-
function interceptEvent(eventObject, eventName, predicate) {
|
|
124
|
-
if (Error.stackTraceLimit < 1e3) {
|
|
125
|
-
Error.stackTraceLimit = 1e3;
|
|
126
|
-
}
|
|
127
|
-
(function(original) {
|
|
128
|
-
element.__proto__.addEventListener = function(...args) {
|
|
129
|
-
if (args[0] === eventName && predicate())
|
|
130
|
-
return;
|
|
131
|
-
else
|
|
132
|
-
return original.apply(this, args);
|
|
133
|
-
};
|
|
134
|
-
})(eventObject.__proto__.addEventListener);
|
|
135
|
-
}
|
|
136
|
-
function interceptWindowEvent(eventName, predicate) {
|
|
137
|
-
return interceptEvent(getUnsafeWindow(), eventName, predicate);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// lib/onSelector.ts
|
|
141
|
-
function onSelector(options, listener) {
|
|
142
|
-
}
|
|
143
|
-
function removeOnSelector(selector) {
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
exports.addGlobalStyle = addGlobalStyle;
|
|
147
|
-
exports.addParent = addParent;
|
|
148
|
-
exports.autoPlural = autoPlural;
|
|
149
|
-
exports.clamp = clamp;
|
|
150
|
-
exports.debounce = debounce;
|
|
151
|
-
exports.fetchAdvanced = fetchAdvanced;
|
|
152
|
-
exports.getUnsafeWindow = getUnsafeWindow;
|
|
153
|
-
exports.insertAfter = insertAfter;
|
|
154
|
-
exports.interceptEvent = interceptEvent;
|
|
155
|
-
exports.interceptWindowEvent = interceptWindowEvent;
|
|
156
|
-
exports.onSelector = onSelector;
|
|
157
|
-
exports.openInNewTab = openInNewTab;
|
|
158
|
-
exports.pauseFor = pauseFor;
|
|
159
|
-
exports.preloadImages = preloadImages;
|
|
160
|
-
exports.removeOnSelector = removeOnSelector;
|
|
161
|
-
//# sourceMappingURL=out.js.map
|
|
162
|
-
//# sourceMappingURL=index.js.map
|
|
5
|
+
exports.addGlobalStyle = H;
|
|
6
|
+
exports.addParent = k;
|
|
7
|
+
exports.autoPlural = w;
|
|
8
|
+
exports.clamp = M;
|
|
9
|
+
exports.debounce = A;
|
|
10
|
+
exports.fetchAdvanced = j;
|
|
11
|
+
exports.getUnsafeWindow = h;
|
|
12
|
+
exports.initOnSelector = $;
|
|
13
|
+
exports.insertAfter = _;
|
|
14
|
+
exports.interceptEvent = O;
|
|
15
|
+
exports.interceptWindowEvent = P;
|
|
16
|
+
exports.onSelector = W;
|
|
17
|
+
exports.openInNewTab = N;
|
|
18
|
+
exports.pauseFor = S;
|
|
19
|
+
exports.preloadImages = I;
|
|
20
|
+
exports.removeOnSelector = q;
|
package/dist/index.mjs
CHANGED
|
@@ -1,146 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
var __defProps = Object.defineProperties;
|
|
3
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
-
var __spreadValues = (a, b) => {
|
|
9
|
-
for (var prop in b || (b = {}))
|
|
10
|
-
if (__hasOwnProp.call(b, prop))
|
|
11
|
-
__defNormalProp(a, prop, b[prop]);
|
|
12
|
-
if (__getOwnPropSymbols)
|
|
13
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
-
if (__propIsEnum.call(b, prop))
|
|
15
|
-
__defNormalProp(a, prop, b[prop]);
|
|
16
|
-
}
|
|
17
|
-
return a;
|
|
18
|
-
};
|
|
19
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
-
var __async = (__this, __arguments, generator) => {
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
var fulfilled = (value) => {
|
|
23
|
-
try {
|
|
24
|
-
step(generator.next(value));
|
|
25
|
-
} catch (e) {
|
|
26
|
-
reject(e);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
var rejected = (value) => {
|
|
30
|
-
try {
|
|
31
|
-
step(generator.throw(value));
|
|
32
|
-
} catch (e) {
|
|
33
|
-
reject(e);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
37
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
38
|
-
});
|
|
39
|
-
};
|
|
1
|
+
var b=Object.defineProperty,v=Object.defineProperties;var y=Object.getOwnPropertyDescriptors;var m=Object.getOwnPropertySymbols;var x=Object.prototype.hasOwnProperty,g=Object.prototype.propertyIsEnumerable;var f=(t,e,n)=>e in t?b(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,u=(t,e)=>{for(var n in e||(e={}))x.call(e,n)&&f(t,n,e[n]);if(m)for(var n of m(e))g.call(e,n)&&f(t,n,e[n]);return t},d=(t,e)=>v(t,y(e));var E=(t,e,n)=>new Promise((r,o)=>{var s=c=>{try{l(n.next(c));}catch(p){o(p);}},i=c=>{try{l(n.throw(c));}catch(p){o(p);}},l=c=>c.done?r(c.value):Promise.resolve(c.value).then(s,i);l((n=n.apply(t,e)).next());});function w(t,e){return (Array.isArray(e)||e instanceof NodeList)&&(e=e.length),`${t}${e===1?"":"s"}`}function M(t,e,n){return Math.max(Math.min(t,n),e)}function S(t){return new Promise(e=>{setTimeout(e,t);})}function A(t,e=300){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>t.apply(this,r),e);}}function h(){try{return unsafeWindow}catch(t){return window}}function _(t,e){var n;return (n=t.parentNode)==null||n.insertBefore(e,t.nextSibling),e}function k(t,e){let n=t.parentNode;if(!n)throw new Error("Element doesn't have a parent node");return n.replaceChild(e,t),e.appendChild(t),e}function H(t){let e=document.createElement("style");e.innerHTML=t,document.head.appendChild(e);}function I(t,e=!1){let n=t.map(r=>new Promise((o,s)=>{let i=new Image;i.src=r,i.addEventListener("load",()=>o(i)),i.addEventListener("error",l=>e&&s(l));}));return Promise.allSettled(n)}function j(n){return E(this,arguments,function*(t,e={}){let{timeout:r=1e4}=e,o=new AbortController,s=setTimeout(()=>o.abort(),r),i=yield fetch(t,d(u({},e),{signal:o.signal}));return clearTimeout(s),i})}function N(t){let e=document.createElement("a");Object.assign(e,{className:"userutils-open-in-new-tab",target:"_blank",rel:"noopener noreferrer",href:t}),e.style.display="none",document.body.appendChild(e),e.click(),setTimeout(e.remove,50);}function O(t,e,n){typeof Error.stackTraceLimit=="number"&&Error.stackTraceLimit<1e3&&(Error.stackTraceLimit=1e3),function(r){element.__proto__.addEventListener=function(...o){if(!(o[0]===e&&n()))return r.apply(this,o)};}(t.__proto__.addEventListener);}function P(t,e){return O(h(),t,e)}var a=new Map;function W(t,e){let n=[];a.has(t)&&(n=a.get(t)),n.push(e),a.set(t,n),T(t,n);}function q(t){return a.delete(t)}function T(t,e){let n=[];e.forEach((r,o)=>{try{let s=r.all?document.querySelectorAll(t):document.querySelector(t);s&&(r.listener(s),r.continuous||n.push(o));}catch(s){}}),n.length>0&&a.set(t,e.filter((r,o)=>!n.includes(o)));}function $(t={}){new MutationObserver(()=>{for(let[n,r]of a.entries())T(n,r);}).observe(document.body,d(u({},t),{childList:!0}));}
|
|
40
2
|
|
|
41
|
-
|
|
42
|
-
function autoPlural(word, num) {
|
|
43
|
-
if (Array.isArray(num) || num instanceof NodeList)
|
|
44
|
-
num = num.length;
|
|
45
|
-
return `${word}${num === 1 ? "" : "s"}`;
|
|
46
|
-
}
|
|
47
|
-
function clamp(value, min, max) {
|
|
48
|
-
return Math.max(Math.min(value, max), min);
|
|
49
|
-
}
|
|
50
|
-
function pauseFor(time) {
|
|
51
|
-
return new Promise((res) => {
|
|
52
|
-
setTimeout(res, time);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function debounce(func, timeout = 300) {
|
|
56
|
-
let timer;
|
|
57
|
-
return function(...args) {
|
|
58
|
-
clearTimeout(timer);
|
|
59
|
-
timer = setTimeout(() => func.apply(this, args), timeout);
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function getUnsafeWindow() {
|
|
63
|
-
try {
|
|
64
|
-
return unsafeWindow;
|
|
65
|
-
} catch (e) {
|
|
66
|
-
return window;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function insertAfter(beforeElement, afterElement) {
|
|
70
|
-
var _a;
|
|
71
|
-
(_a = beforeElement.parentNode) == null ? void 0 : _a.insertBefore(afterElement, beforeElement.nextSibling);
|
|
72
|
-
return afterElement;
|
|
73
|
-
}
|
|
74
|
-
function addParent(element2, newParent) {
|
|
75
|
-
const oldParent = element2.parentNode;
|
|
76
|
-
if (!oldParent)
|
|
77
|
-
throw new Error("Element doesn't have a parent node");
|
|
78
|
-
oldParent.replaceChild(newParent, element2);
|
|
79
|
-
newParent.appendChild(element2);
|
|
80
|
-
return newParent;
|
|
81
|
-
}
|
|
82
|
-
function addGlobalStyle(style) {
|
|
83
|
-
const styleElem = document.createElement("style");
|
|
84
|
-
styleElem.innerHTML = style;
|
|
85
|
-
document.head.appendChild(styleElem);
|
|
86
|
-
}
|
|
87
|
-
function preloadImages(srcUrls, rejects = false) {
|
|
88
|
-
const promises = srcUrls.map((src) => new Promise((res, rej) => {
|
|
89
|
-
const image = new Image();
|
|
90
|
-
image.src = src;
|
|
91
|
-
image.addEventListener("load", () => res(image));
|
|
92
|
-
image.addEventListener("error", (evt) => rejects && rej(evt));
|
|
93
|
-
}));
|
|
94
|
-
return Promise.allSettled(promises);
|
|
95
|
-
}
|
|
96
|
-
function fetchAdvanced(_0) {
|
|
97
|
-
return __async(this, arguments, function* (url, options = {}) {
|
|
98
|
-
const { timeout = 1e4 } = options;
|
|
99
|
-
const controller = new AbortController();
|
|
100
|
-
const id = setTimeout(() => controller.abort(), timeout);
|
|
101
|
-
const res = yield fetch(url, __spreadProps(__spreadValues({}, options), {
|
|
102
|
-
signal: controller.signal
|
|
103
|
-
}));
|
|
104
|
-
clearTimeout(id);
|
|
105
|
-
return res;
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
function openInNewTab(href) {
|
|
109
|
-
const openElem = document.createElement("a");
|
|
110
|
-
Object.assign(openElem, {
|
|
111
|
-
className: "userutils-open-in-new-tab",
|
|
112
|
-
target: "_blank",
|
|
113
|
-
rel: "noopener noreferrer",
|
|
114
|
-
href
|
|
115
|
-
});
|
|
116
|
-
openElem.style.display = "none";
|
|
117
|
-
document.body.appendChild(openElem);
|
|
118
|
-
openElem.click();
|
|
119
|
-
setTimeout(openElem.remove, 50);
|
|
120
|
-
}
|
|
121
|
-
function interceptEvent(eventObject, eventName, predicate) {
|
|
122
|
-
if (Error.stackTraceLimit < 1e3) {
|
|
123
|
-
Error.stackTraceLimit = 1e3;
|
|
124
|
-
}
|
|
125
|
-
(function(original) {
|
|
126
|
-
element.__proto__.addEventListener = function(...args) {
|
|
127
|
-
if (args[0] === eventName && predicate())
|
|
128
|
-
return;
|
|
129
|
-
else
|
|
130
|
-
return original.apply(this, args);
|
|
131
|
-
};
|
|
132
|
-
})(eventObject.__proto__.addEventListener);
|
|
133
|
-
}
|
|
134
|
-
function interceptWindowEvent(eventName, predicate) {
|
|
135
|
-
return interceptEvent(getUnsafeWindow(), eventName, predicate);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// lib/onSelector.ts
|
|
139
|
-
function onSelector(options, listener) {
|
|
140
|
-
}
|
|
141
|
-
function removeOnSelector(selector) {
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export { addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getUnsafeWindow, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
|
|
145
|
-
//# sourceMappingURL=out.js.map
|
|
146
|
-
//# sourceMappingURL=index.mjs.map
|
|
3
|
+
export { H as addGlobalStyle, k as addParent, w as autoPlural, M as clamp, A as debounce, j as fetchAdvanced, h as getUnsafeWindow, $ as initOnSelector, _ as insertAfter, O as interceptEvent, P as interceptWindowEvent, W as onSelector, N as openInNewTab, S as pauseFor, I as preloadImages, q as removeOnSelector };
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sv443-network/userutils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more ",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"lint": "tsc && eslint .",
|
|
10
|
-
"
|
|
11
|
-
"build": "npm run
|
|
12
|
-
"
|
|
10
|
+
"build-common": "tsup lib/index.ts --format cjs,esm --dts --clean --treeshake",
|
|
11
|
+
"build": "npm run build-common -- --minify",
|
|
12
|
+
"dev": "npm run build-common -- --sourcemap --watch",
|
|
13
|
+
"publish-package": "npm run build && changeset publish"
|
|
13
14
|
},
|
|
14
15
|
"repository": {
|
|
15
16
|
"type": "git",
|
|
@@ -43,4 +44,4 @@
|
|
|
43
44
|
"> 1%",
|
|
44
45
|
"not dead"
|
|
45
46
|
]
|
|
46
|
-
}
|
|
47
|
+
}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/utils.ts","../lib/onSelector.ts"],"names":["element"],"mappings":"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","sourcesContent":["import type { FetchAdvancedOpts } from \"./types\";\n\n/**\n * Automatically appends an `s` to the passed `word`, if `num` is not equal to 1\n * @param word A word in singular form, to auto-convert to plural\n * @param num If this is an array or NodeList, the amount of items is used\n */\nexport function autoPlural(word: string, num: number | unknown[] | NodeList) {\n if(Array.isArray(num) || num instanceof NodeList)\n num = num.length;\n return `${word}${num === 1 ? \"\" : \"s\"}`;\n}\n\n/** Ensures the passed `value` always stays between `min` and `max` */\nexport function clamp(value: number, min: number, max: number) {\n return Math.max(Math.min(value, max), min);\n}\n\n/** Pauses async execution for the specified time in ms */\nexport function pauseFor(time: number) {\n return new Promise((res) => {\n setTimeout(res, time);\n });\n}\n\n/**\n * Calls the passed `func` after the specified `timeout` in ms. \n * Any subsequent calls to this function will reset the timer and discard previous calls.\n */\nexport function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout = 300) { // eslint-disable-line @typescript-eslint/no-explicit-any\n let timer: number | undefined;\n return function(...args: TArgs[]) {\n clearTimeout(timer);\n timer = setTimeout(() => func.apply(this, args), timeout) as unknown as number;\n };\n}\n\n/**\n * Returns `unsafeWindow` if the `@grant unsafeWindow` is given, otherwise falls back to the regular `window`\n */\nexport function getUnsafeWindow() {\n try {\n // throws ReferenceError if the \"@grant unsafeWindow\" isn't present\n return unsafeWindow;\n }\n catch(e) {\n return window;\n }\n}\n\n/**\n * Inserts `afterElement` as a sibling just after the provided `beforeElement`\n * @returns Returns the `afterElement`\n */\nexport function insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement) {\n beforeElement.parentNode?.insertBefore(afterElement, beforeElement.nextSibling);\n return afterElement;\n}\n\n/**\n * Adds a parent container around the provided element\n * @returns Returns the new parent element\n */\nexport function addParent(element: HTMLElement, newParent: HTMLElement) {\n const oldParent = element.parentNode;\n\n if(!oldParent)\n throw new Error(\"Element doesn't have a parent node\");\n\n oldParent.replaceChild(newParent, element);\n newParent.appendChild(element);\n\n return newParent;\n}\n\n/**\n * Adds global CSS style in the form of a `<style>` element in the document's `<head>` \n * This needs to be run after the `DOMContentLoaded` event has fired on the document object (or instantly if `@run-at document-end` is used).\n * @param style CSS string\n */\nexport function addGlobalStyle(style: string) {\n const styleElem = document.createElement(\"style\");\n styleElem.innerHTML = style;\n document.head.appendChild(styleElem);\n}\n\n/**\n * Preloads an array of image URLs so they can be loaded instantly from the browser cache later on\n * @param rejects If set to `true`, the returned PromiseSettledResults will contain rejections for any of the images that failed to load\n * @returns Returns an array of `PromiseSettledResult` - each resolved result will contain the loaded image element, while each rejected result will contain an `ErrorEvent`\n */\nexport function preloadImages(srcUrls: string[], rejects = false) {\n const promises = srcUrls.map(src => new Promise((res, rej) => {\n const image = new Image();\n image.src = src;\n image.addEventListener(\"load\", () => res(image));\n image.addEventListener(\"error\", (evt) => rejects && rej(evt));\n }));\n\n return Promise.allSettled(promises);\n}\n\n/** Calls the fetch API with special options like a timeout */\nexport async function fetchAdvanced(url: string, options: FetchAdvancedOpts = {}) {\n const { timeout = 10000 } = options;\n\n const controller = new AbortController();\n const id = setTimeout(() => controller.abort(), timeout);\n\n const res = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n\n clearTimeout(id);\n return res;\n}\n\n/**\n * Creates an invisible anchor with a `_blank` target and clicks it. \n * 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. \n * \n * This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.\n */\nexport function openInNewTab(href: string) {\n const openElem = document.createElement(\"a\");\n Object.assign(openElem, {\n className: \"userutils-open-in-new-tab\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n href,\n });\n openElem.style.display = \"none\";\n\n document.body.appendChild(openElem);\n openElem.click();\n // timeout just to be safe\n setTimeout(openElem.remove, 50);\n}\n\n/**\n * Intercepts the specified event on the passed object and prevents it from being called if the called `predicate` function returns a truthy value. \n * Calling this function will set the `stackTraceLimit` to 1000 to ensure the stack trace is preserved.\n */\nexport function interceptEvent<TEvtObj extends EventTarget>(eventObject: TEvtObj, eventName: Parameters<TEvtObj[\"addEventListener\"]>[0], predicate: () => boolean) { \n // default is between 10 and 100 on conventional browsers so this should hopefully be more than enough\n // @ts-ignore\n if(Error.stackTraceLimit < 1000) {\n // @ts-ignore\n Error.stackTraceLimit = 1000;\n }\n\n (function(original: typeof eventObject.addEventListener) {\n // @ts-ignore\n element.__proto__.addEventListener = function(...args: Parameters<typeof eventObject.addEventListener>) {\n if(args[0] === eventName && predicate())\n return;\n else\n return original.apply(this, args);\n };\n // @ts-ignore\n })(eventObject.__proto__.addEventListener);\n}\n\n/**\n * Intercepts the specified event on the window object and prevents it from being called if the called `predicate` function returns a truthy value. \n * Calling this function will set the `stackTraceLimit` to 1000 to ensure the stack trace is preserved.\n */\nexport function interceptWindowEvent(eventName: keyof WindowEventMap, predicate: () => boolean) { \n return interceptEvent(getUnsafeWindow(), eventName, predicate);\n}\n","import { SelectorExistsOpts } from \"./types\";\n\n/**\n * Calls the `listener` as soon as the `selector` exists in the DOM. \n * Listeners are deleted when they are called once, unless `options.continuous` is set. \n * Multiple listeners with the same selector may be registered.\n * @template TElem The type of element that this selector will return - FIXME: listener inferring doesn't work when this generic is given\n */\nexport function onSelector<TElem = HTMLElement, TOpts extends SelectorExistsOpts = SelectorExistsOpts>(\n options: TOpts,\n listener: (element: TOpts[\"all\"] extends true ? (TElem extends HTMLElement ? NodeListOf<TElem> : TElem) : TElem) => void,\n) {\n // TODO:\n void [options, listener];\n}\n\n/** Removes all listeners registered in `onSelector()` with a matching selector property */\nexport function removeOnSelector(selector: string) {\n // TODO:\n void [selector];\n}\n"]}
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/utils.ts","../lib/onSelector.ts"],"names":["element"],"mappings":"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","sourcesContent":["import type { FetchAdvancedOpts } from \"./types\";\n\n/**\n * Automatically appends an `s` to the passed `word`, if `num` is not equal to 1\n * @param word A word in singular form, to auto-convert to plural\n * @param num If this is an array or NodeList, the amount of items is used\n */\nexport function autoPlural(word: string, num: number | unknown[] | NodeList) {\n if(Array.isArray(num) || num instanceof NodeList)\n num = num.length;\n return `${word}${num === 1 ? \"\" : \"s\"}`;\n}\n\n/** Ensures the passed `value` always stays between `min` and `max` */\nexport function clamp(value: number, min: number, max: number) {\n return Math.max(Math.min(value, max), min);\n}\n\n/** Pauses async execution for the specified time in ms */\nexport function pauseFor(time: number) {\n return new Promise((res) => {\n setTimeout(res, time);\n });\n}\n\n/**\n * Calls the passed `func` after the specified `timeout` in ms. \n * Any subsequent calls to this function will reset the timer and discard previous calls.\n */\nexport function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout = 300) { // eslint-disable-line @typescript-eslint/no-explicit-any\n let timer: number | undefined;\n return function(...args: TArgs[]) {\n clearTimeout(timer);\n timer = setTimeout(() => func.apply(this, args), timeout) as unknown as number;\n };\n}\n\n/**\n * Returns `unsafeWindow` if the `@grant unsafeWindow` is given, otherwise falls back to the regular `window`\n */\nexport function getUnsafeWindow() {\n try {\n // throws ReferenceError if the \"@grant unsafeWindow\" isn't present\n return unsafeWindow;\n }\n catch(e) {\n return window;\n }\n}\n\n/**\n * Inserts `afterElement` as a sibling just after the provided `beforeElement`\n * @returns Returns the `afterElement`\n */\nexport function insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement) {\n beforeElement.parentNode?.insertBefore(afterElement, beforeElement.nextSibling);\n return afterElement;\n}\n\n/**\n * Adds a parent container around the provided element\n * @returns Returns the new parent element\n */\nexport function addParent(element: HTMLElement, newParent: HTMLElement) {\n const oldParent = element.parentNode;\n\n if(!oldParent)\n throw new Error(\"Element doesn't have a parent node\");\n\n oldParent.replaceChild(newParent, element);\n newParent.appendChild(element);\n\n return newParent;\n}\n\n/**\n * Adds global CSS style in the form of a `<style>` element in the document's `<head>` \n * This needs to be run after the `DOMContentLoaded` event has fired on the document object (or instantly if `@run-at document-end` is used).\n * @param style CSS string\n */\nexport function addGlobalStyle(style: string) {\n const styleElem = document.createElement(\"style\");\n styleElem.innerHTML = style;\n document.head.appendChild(styleElem);\n}\n\n/**\n * Preloads an array of image URLs so they can be loaded instantly from the browser cache later on\n * @param rejects If set to `true`, the returned PromiseSettledResults will contain rejections for any of the images that failed to load\n * @returns Returns an array of `PromiseSettledResult` - each resolved result will contain the loaded image element, while each rejected result will contain an `ErrorEvent`\n */\nexport function preloadImages(srcUrls: string[], rejects = false) {\n const promises = srcUrls.map(src => new Promise((res, rej) => {\n const image = new Image();\n image.src = src;\n image.addEventListener(\"load\", () => res(image));\n image.addEventListener(\"error\", (evt) => rejects && rej(evt));\n }));\n\n return Promise.allSettled(promises);\n}\n\n/** Calls the fetch API with special options like a timeout */\nexport async function fetchAdvanced(url: string, options: FetchAdvancedOpts = {}) {\n const { timeout = 10000 } = options;\n\n const controller = new AbortController();\n const id = setTimeout(() => controller.abort(), timeout);\n\n const res = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n\n clearTimeout(id);\n return res;\n}\n\n/**\n * Creates an invisible anchor with a `_blank` target and clicks it. \n * 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. \n * \n * This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.\n */\nexport function openInNewTab(href: string) {\n const openElem = document.createElement(\"a\");\n Object.assign(openElem, {\n className: \"userutils-open-in-new-tab\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n href,\n });\n openElem.style.display = \"none\";\n\n document.body.appendChild(openElem);\n openElem.click();\n // timeout just to be safe\n setTimeout(openElem.remove, 50);\n}\n\n/**\n * Intercepts the specified event on the passed object and prevents it from being called if the called `predicate` function returns a truthy value. \n * Calling this function will set the `stackTraceLimit` to 1000 to ensure the stack trace is preserved.\n */\nexport function interceptEvent<TEvtObj extends EventTarget>(eventObject: TEvtObj, eventName: Parameters<TEvtObj[\"addEventListener\"]>[0], predicate: () => boolean) { \n // default is between 10 and 100 on conventional browsers so this should hopefully be more than enough\n // @ts-ignore\n if(Error.stackTraceLimit < 1000) {\n // @ts-ignore\n Error.stackTraceLimit = 1000;\n }\n\n (function(original: typeof eventObject.addEventListener) {\n // @ts-ignore\n element.__proto__.addEventListener = function(...args: Parameters<typeof eventObject.addEventListener>) {\n if(args[0] === eventName && predicate())\n return;\n else\n return original.apply(this, args);\n };\n // @ts-ignore\n })(eventObject.__proto__.addEventListener);\n}\n\n/**\n * Intercepts the specified event on the window object and prevents it from being called if the called `predicate` function returns a truthy value. \n * Calling this function will set the `stackTraceLimit` to 1000 to ensure the stack trace is preserved.\n */\nexport function interceptWindowEvent(eventName: keyof WindowEventMap, predicate: () => boolean) { \n return interceptEvent(getUnsafeWindow(), eventName, predicate);\n}\n","import { SelectorExistsOpts } from \"./types\";\n\n/**\n * Calls the `listener` as soon as the `selector` exists in the DOM. \n * Listeners are deleted when they are called once, unless `options.continuous` is set. \n * Multiple listeners with the same selector may be registered.\n * @template TElem The type of element that this selector will return - FIXME: listener inferring doesn't work when this generic is given\n */\nexport function onSelector<TElem = HTMLElement, TOpts extends SelectorExistsOpts = SelectorExistsOpts>(\n options: TOpts,\n listener: (element: TOpts[\"all\"] extends true ? (TElem extends HTMLElement ? NodeListOf<TElem> : TElem) : TElem) => void,\n) {\n // TODO:\n void [options, listener];\n}\n\n/** Removes all listeners registered in `onSelector()` with a matching selector property */\nexport function removeOnSelector(selector: string) {\n // TODO:\n void [selector];\n}\n"]}
|