@sv443-network/userutils 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # @sv443-network/userutils
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - bb60db0: minor fixes
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 9206f6e: Initial release - Features:
14
+ - `onSelector()` to call a listener once a selector is found in the DOM
15
+ - `autoPlural()` to automatically pluralize a string
16
+ - `clamp()` to clamp a number between a min and max value
17
+ - `pauseFor()` to pause the execution of a function for a given amount of time
18
+ - `debounce()` to call a function only once, after a given amount of time
19
+ - `getUnsafeWindow()` to get the unsafeWindow object or fall back to the regular window object
20
+ - `insertAfter()` to insert an element as a sibling after another element
21
+ - `addParent()` to add a parent element around another element
22
+ - `addGlobalStyle()` to add a global style to the page
23
+ - `preloadImages()` to preload images into the browser cache for faster loading later on
24
+ - `fetchAdvanced()` as a wrapper around the fetch API with a timeout option
25
+ - `openInNewTab()` to open a link in a new tab
26
+ - `interceptEvent()` to conditionally intercept events registered by `addEventListener()` on any given EventTarget object
27
+ - `interceptWindowEvent()` to conditionally intercept events registered by `addEventListener()` on the window object
package/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Sven Fehler (Sv443)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,296 @@
1
+ ## UserUtils
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 definitions.
4
+
5
+ <br>
6
+
7
+ ## Table of Contents:
8
+ - [Installation](#installation)
9
+ - [Features](#features)
10
+ - [onSelector()](#onselector) - call a listener once a selector is found in the DOM
11
+ - [autoPlural()](#autoplural) - automatically pluralize a string
12
+ - [clamp()](#clamp) - clamp a number between a min and max value
13
+ - [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
14
+ - [debounce()](#debounce) - call a function only once, after a given amount of time
15
+ - [getUnsafeWindow()](#getunsafewindow) - get the unsafeWindow object or fall back to the regular window object
16
+ - [insertAfter()](#insertafter) - insert an element as a sibling after another element
17
+ - [addParent()](#addparent) - add a parent element around another element
18
+ - [addGlobalStyle()](#addglobalstyle) - add a global style to the page
19
+ - [preloadImages()](#preloadimages) - preload images into the browser cache for faster loading later on
20
+ - [fetchAdvanced()](#fetchadvanced) - wrapper around the fetch API with a timeout option
21
+ - [openInNewTab()](#openinnewtab) - open a link in a new tab
22
+ - [interceptEvent()](#interceptevent) - conditionally intercepts events registered by `addEventListener()` on any given EventTarget object
23
+ - [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
24
+ - [License](#license)
25
+
26
+ <br><br>
27
+
28
+ ## Installation:
29
+ - If you are using a bundler like webpack, you can install this package using npm:
30
+ ```
31
+ npm i @sv443-network/userutils
32
+ ```
33
+ Then, import it in your script as usual:
34
+ ```ts
35
+ import { addGlobalStyle } from "@sv443-network/userutils";
36
+ // or
37
+ import * as userUtils from "@sv443-network/userutils";
38
+ ```
39
+ Shameless plug: I also have a [webpack-based template for userscripts in TypeScript](https://github.com/Sv443/Userscript.ts) that you can use to get started quickly.
40
+
41
+ <br>
42
+
43
+ - If you are not using a bundler, you can include the latest release from GreasyFork by adding this directive to the userscript header:
44
+ ```
45
+ // @require https://greasyfork.org/scripts/TODO
46
+ ```
47
+
48
+ <br>
49
+
50
+ If you like using this library, please consider [supporting development](https://github.com/sponsors/Sv443)
51
+
52
+ <br><br>
53
+
54
+ ## Features:
55
+
56
+ ### onSelector()
57
+ \- UNFINISHED -
58
+
59
+ Registers a listener to be called whenever the element(s) behind a selector is/are found in the DOM.
60
+ In order to use this function, the MutationObservers have to be initialized with `initOnSelector()` first.
61
+
62
+ Example:
63
+ ```ts
64
+ // TODO
65
+ ```
66
+
67
+ <br>
68
+
69
+ ### autoPlural()
70
+ Usage: `autoPlural(str: string, num: number | Array | NodeList): string`
71
+
72
+ Automatically pluralizes a string if the given number is not 1.
73
+ If an array or NodeList is passed, the length of it will be used.
74
+
75
+ Example:
76
+ ```ts
77
+ autoPlural("apple", 0); // "apples"
78
+ autoPlural("apple", 1); // "apple"
79
+ autoPlural("apple", 2); // "apples"
80
+
81
+ autoPlural("apple", [1]); // "apple"
82
+ autoPlural("apple", [1, 2]); // "apples"
83
+ ```
84
+
85
+ <br>
86
+
87
+ ### clamp()
88
+ Usage: `clamp(num: number, min: number, max: number): number`
89
+
90
+ Clamps a number between a min and max value.
91
+
92
+ Example:
93
+ ```ts
94
+ clamp(5, 0, 10); // 5
95
+ clamp(-1, 0, 10); // 0
96
+ clamp(Infinity, 0, 10); // 10
97
+ ```
98
+
99
+ <br>
100
+
101
+ ### pauseFor()
102
+ Usage: `pauseFor(ms: number): Promise<void>`
103
+
104
+ Pauses async execution for a given amount of time.
105
+
106
+ Example:
107
+ ```ts
108
+ async function run() {
109
+ console.log("Hello");
110
+ await pauseFor(3000); // waits for 3 seconds
111
+ console.log("World");
112
+ }
113
+ ```
114
+
115
+ <br>
116
+
117
+ ### debounce()
118
+ Usage: `debounce(func: Function, timeout?: number): Function`
119
+
120
+ Debounces a function, meaning that it will only be called once after a given amount of time.
121
+ This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.
122
+ The timeout will default to 300ms if left undefined.
123
+
124
+ Example:
125
+ ```ts
126
+ window.addEventListener("resize", debounce((event) => {
127
+ console.log("Window was resized:", event);
128
+ }, 500)); // 500ms timeout
129
+ ```
130
+
131
+ <br>
132
+
133
+ ### getUnsafeWindow()
134
+ Usage: `getUnsafeWindow(): Window`
135
+
136
+ Returns the unsafeWindow object or falls back to the regular window object if the `@grant unsafeWindow` is not given.
137
+ 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
+
139
+ Example:
140
+ ```ts
141
+ const mouseEvent = new MouseEvent("click", {
142
+ view: getUnsafeWindow(),
143
+ });
144
+ document.body.dispatchEvent(mouseEvent);
145
+ ```
146
+
147
+ <br>
148
+
149
+ ### insertAfter()
150
+ Usage: `insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement`
151
+
152
+ Inserts the element passed as `afterElement` as a sibling after the passed `beforeElement`.
153
+ The `afterElement` will be returned.
154
+
155
+ Example:
156
+ ```ts
157
+ const beforeElement = document.querySelector("#before");
158
+ const afterElement = document.createElement("div");
159
+ afterElement.innerText = "After";
160
+ insertAfter(beforeElement, afterElement);
161
+ ```
162
+
163
+ <br>
164
+
165
+ ### addParent()
166
+ Usage: `addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement`
167
+
168
+ Adds a parent element around the passed `element` and returns the new parent.
169
+ Previously registered event listeners should be kept intact.
170
+
171
+ Example:
172
+ ```ts
173
+ const element = document.querySelector("#element");
174
+ const newParent = document.createElement("a");
175
+ newParent.href = "https://example.org/";
176
+ addParent(element, newParent);
177
+ ```
178
+
179
+ <br>
180
+
181
+ ### addGlobalStyle()
182
+ Usage: `addGlobalStyle(css: string): void`
183
+
184
+ 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).
186
+
187
+ Example:
188
+ ```ts
189
+ document.addEventListener("DOMContentLoaded", () => {
190
+ addGlobalStyle(`
191
+ body {
192
+ background-color: red;
193
+ }
194
+ `);
195
+ });
196
+ ```
197
+
198
+ <br>
199
+
200
+ ### preloadImages()
201
+ Usage: `preloadImages(...urls: string[]): Promise<void>`
202
+
203
+ Preloads images into browser cache by creating an invisible `<img>` element for each URL passed.
204
+ 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.
206
+
207
+ Example:
208
+ ```ts
209
+ preloadImages(
210
+ "https://example.org/image1.png",
211
+ "https://example.org/image2.png",
212
+ "https://example.org/image3.png",
213
+ ).then((results) => {
214
+ console.log("Images preloaded. Results:", results);
215
+ });
216
+ ```
217
+
218
+ <br>
219
+
220
+ ### fetchAdvanced()
221
+ Usage: `fetchAdvanced(url: string, options?: FetchAdvancedOptions): Promise<Response>`
222
+
223
+ A wrapper around the native `fetch()` function that adds options like a timeout property.
224
+ The timeout will default to 10 seconds if left undefined.
225
+
226
+ Example:
227
+ ```ts
228
+ fetchAdvanced("https://example.org/", {
229
+ timeout: 5000,
230
+ }).then((response) => {
231
+ console.log("Response:", response);
232
+ });
233
+ ```
234
+
235
+ <br>
236
+
237
+ ### openInNewTab()
238
+ Usage: `openInNewTab(url: string): void`
239
+
240
+ Creates an invisible anchor with a `_blank` target and clicks it.
241
+ 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
+ This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.
243
+
244
+ Example:
245
+ ```ts
246
+ document.querySelector("#button").addEventListener("click", () => {
247
+ openInNewTab("https://example.org/");
248
+ });
249
+ ```
250
+
251
+ <br>
252
+
253
+ ### interceptEvent()
254
+ Usage: `interceptEvent(eventObject: EventTarget, eventName: string, predicate: () => boolean): void`
255
+
256
+ 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
+ Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
259
+
260
+ Example:
261
+ ```ts
262
+ interceptEvent(document.body, "click", () => {
263
+ return true; // prevent all click events on the body
264
+ });
265
+ ```
266
+
267
+ <br>
268
+
269
+ ### interceptWindowEvent()
270
+ Usage: `interceptWindowEvent(eventName: string, predicate: () => boolean): void`
271
+
272
+ 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
+ This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
274
+
275
+ Example:
276
+ ```ts
277
+ interceptWindowEvent("beforeunload", () => {
278
+ return true; // prevent the pesky "Are you sure you want to leave this page?" popup
279
+ });
280
+ ```
281
+
282
+
283
+ <br><br>
284
+
285
+ ## License:
286
+ This library is licensed under the MIT License.
287
+ See the [license file](./LICENSE.txt) for details.
288
+
289
+ <br><br>
290
+
291
+ <div style="text-align: center;" align="center">
292
+
293
+ Made with ❤️ by [Sv443](https://github.com/Sv443)
294
+ If you like this library, please consider [supporting development](https://github.com/sponsors/Sv443)
295
+
296
+ </div>
@@ -0,0 +1,84 @@
1
+ type SelectorExistsOpts = {
2
+ /** The selector to check for */
3
+ selector: string;
4
+ /** Whether to use `querySelectorAll()` instead */
5
+ all?: boolean;
6
+ /** Whether to call the listener continuously instead of once */
7
+ continuous?: boolean;
8
+ };
9
+ type FetchAdvancedOpts = RequestInit & Partial<{
10
+ timeout: number;
11
+ }>;
12
+
13
+ /**
14
+ * Automatically appends an `s` to the passed `word`, if `num` is not equal to 1
15
+ * @param word A word in singular form, to auto-convert to plural
16
+ * @param num If this is an array or NodeList, the amount of items is used
17
+ */
18
+ declare function autoPlural(word: string, num: number | unknown[] | NodeList): string;
19
+ /** Ensures the passed `value` always stays between `min` and `max` */
20
+ declare function clamp(value: number, min: number, max: number): number;
21
+ /** Pauses async execution for the specified time in ms */
22
+ declare function pauseFor(time: number): Promise<unknown>;
23
+ /**
24
+ * Calls the passed `func` after the specified `timeout` in ms.
25
+ * Any subsequent calls to this function will reset the timer and discard previous calls.
26
+ */
27
+ declare function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout?: number): (...args: TArgs[]) => void;
28
+ /**
29
+ * Returns `unsafeWindow` if the `@grant unsafeWindow` is given, otherwise falls back to the regular `window`
30
+ */
31
+ declare function getUnsafeWindow(): Window;
32
+ /**
33
+ * Inserts `afterElement` as a sibling just after the provided `beforeElement`
34
+ * @returns Returns the `afterElement`
35
+ */
36
+ declare function insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement;
37
+ /**
38
+ * Adds a parent container around the provided element
39
+ * @returns Returns the new parent element
40
+ */
41
+ declare function addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement;
42
+ /**
43
+ * Adds global CSS style in the form of a `<style>` element in the document's `<head>`
44
+ * This needs to be run after the `DOMContentLoaded` event has fired on the document object (or instantly if `@run-at document-end` is used).
45
+ * @param style CSS string
46
+ */
47
+ declare function addGlobalStyle(style: string): void;
48
+ /**
49
+ * Preloads an array of image URLs so they can be loaded instantly from the browser cache later on
50
+ * @param rejects If set to `true`, the returned PromiseSettledResults will contain rejections for any of the images that failed to load
51
+ * @returns Returns an array of `PromiseSettledResult` - each resolved result will contain the loaded image element, while each rejected result will contain an `ErrorEvent`
52
+ */
53
+ declare function preloadImages(srcUrls: string[], rejects?: boolean): Promise<PromiseSettledResult<unknown>[]>;
54
+ /** Calls the fetch API with special options like a timeout */
55
+ declare function fetchAdvanced(url: string, options?: FetchAdvancedOpts): Promise<Response>;
56
+ /**
57
+ * Creates an invisible anchor with a `_blank` target and clicks it.
58
+ * 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.
59
+ *
60
+ * This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.
61
+ */
62
+ declare function openInNewTab(href: string): void;
63
+ /**
64
+ * Intercepts the specified event on the passed object and prevents it from being called if the called `predicate` function returns a truthy value.
65
+ * Calling this function will set the `stackTraceLimit` to 1000 to ensure the stack trace is preserved.
66
+ */
67
+ declare function interceptEvent<TEvtObj extends EventTarget>(eventObject: TEvtObj, eventName: Parameters<TEvtObj["addEventListener"]>[0], predicate: () => boolean): void;
68
+ /**
69
+ * Intercepts the specified event on the window object and prevents it from being called if the called `predicate` function returns a truthy value.
70
+ * Calling this function will set the `stackTraceLimit` to 1000 to ensure the stack trace is preserved.
71
+ */
72
+ declare function interceptWindowEvent(eventName: keyof WindowEventMap, predicate: () => boolean): void;
73
+
74
+ /**
75
+ * Calls the `listener` as soon as the `selector` exists in the DOM.
76
+ * Listeners are deleted when they are called once, unless `options.continuous` is set.
77
+ * Multiple listeners with the same selector may be registered.
78
+ * @template TElem The type of element that this selector will return - FIXME: listener inferring doesn't work when this generic is given
79
+ */
80
+ declare function onSelector<TElem = HTMLElement, TOpts extends SelectorExistsOpts = SelectorExistsOpts>(options: TOpts, listener: (element: TOpts["all"] extends true ? (TElem extends HTMLElement ? NodeListOf<TElem> : TElem) : TElem) => void): void;
81
+ /** Removes all listeners registered in `onSelector()` with a matching selector property */
82
+ declare function removeOnSelector(selector: string): void;
83
+
84
+ export { FetchAdvancedOpts, SelectorExistsOpts, addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getUnsafeWindow, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
@@ -0,0 +1,84 @@
1
+ type SelectorExistsOpts = {
2
+ /** The selector to check for */
3
+ selector: string;
4
+ /** Whether to use `querySelectorAll()` instead */
5
+ all?: boolean;
6
+ /** Whether to call the listener continuously instead of once */
7
+ continuous?: boolean;
8
+ };
9
+ type FetchAdvancedOpts = RequestInit & Partial<{
10
+ timeout: number;
11
+ }>;
12
+
13
+ /**
14
+ * Automatically appends an `s` to the passed `word`, if `num` is not equal to 1
15
+ * @param word A word in singular form, to auto-convert to plural
16
+ * @param num If this is an array or NodeList, the amount of items is used
17
+ */
18
+ declare function autoPlural(word: string, num: number | unknown[] | NodeList): string;
19
+ /** Ensures the passed `value` always stays between `min` and `max` */
20
+ declare function clamp(value: number, min: number, max: number): number;
21
+ /** Pauses async execution for the specified time in ms */
22
+ declare function pauseFor(time: number): Promise<unknown>;
23
+ /**
24
+ * Calls the passed `func` after the specified `timeout` in ms.
25
+ * Any subsequent calls to this function will reset the timer and discard previous calls.
26
+ */
27
+ declare function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout?: number): (...args: TArgs[]) => void;
28
+ /**
29
+ * Returns `unsafeWindow` if the `@grant unsafeWindow` is given, otherwise falls back to the regular `window`
30
+ */
31
+ declare function getUnsafeWindow(): Window;
32
+ /**
33
+ * Inserts `afterElement` as a sibling just after the provided `beforeElement`
34
+ * @returns Returns the `afterElement`
35
+ */
36
+ declare function insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement;
37
+ /**
38
+ * Adds a parent container around the provided element
39
+ * @returns Returns the new parent element
40
+ */
41
+ declare function addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement;
42
+ /**
43
+ * Adds global CSS style in the form of a `<style>` element in the document's `<head>`
44
+ * This needs to be run after the `DOMContentLoaded` event has fired on the document object (or instantly if `@run-at document-end` is used).
45
+ * @param style CSS string
46
+ */
47
+ declare function addGlobalStyle(style: string): void;
48
+ /**
49
+ * Preloads an array of image URLs so they can be loaded instantly from the browser cache later on
50
+ * @param rejects If set to `true`, the returned PromiseSettledResults will contain rejections for any of the images that failed to load
51
+ * @returns Returns an array of `PromiseSettledResult` - each resolved result will contain the loaded image element, while each rejected result will contain an `ErrorEvent`
52
+ */
53
+ declare function preloadImages(srcUrls: string[], rejects?: boolean): Promise<PromiseSettledResult<unknown>[]>;
54
+ /** Calls the fetch API with special options like a timeout */
55
+ declare function fetchAdvanced(url: string, options?: FetchAdvancedOpts): Promise<Response>;
56
+ /**
57
+ * Creates an invisible anchor with a `_blank` target and clicks it.
58
+ * 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.
59
+ *
60
+ * This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.
61
+ */
62
+ declare function openInNewTab(href: string): void;
63
+ /**
64
+ * Intercepts the specified event on the passed object and prevents it from being called if the called `predicate` function returns a truthy value.
65
+ * Calling this function will set the `stackTraceLimit` to 1000 to ensure the stack trace is preserved.
66
+ */
67
+ declare function interceptEvent<TEvtObj extends EventTarget>(eventObject: TEvtObj, eventName: Parameters<TEvtObj["addEventListener"]>[0], predicate: () => boolean): void;
68
+ /**
69
+ * Intercepts the specified event on the window object and prevents it from being called if the called `predicate` function returns a truthy value.
70
+ * Calling this function will set the `stackTraceLimit` to 1000 to ensure the stack trace is preserved.
71
+ */
72
+ declare function interceptWindowEvent(eventName: keyof WindowEventMap, predicate: () => boolean): void;
73
+
74
+ /**
75
+ * Calls the `listener` as soon as the `selector` exists in the DOM.
76
+ * Listeners are deleted when they are called once, unless `options.continuous` is set.
77
+ * Multiple listeners with the same selector may be registered.
78
+ * @template TElem The type of element that this selector will return - FIXME: listener inferring doesn't work when this generic is given
79
+ */
80
+ declare function onSelector<TElem = HTMLElement, TOpts extends SelectorExistsOpts = SelectorExistsOpts>(options: TOpts, listener: (element: TOpts["all"] extends true ? (TElem extends HTMLElement ? NodeListOf<TElem> : TElem) : TElem) => void): void;
81
+ /** Removes all listeners registered in `onSelector()` with a matching selector property */
82
+ declare function removeOnSelector(selector: string): void;
83
+
84
+ export { FetchAdvancedOpts, SelectorExistsOpts, addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getUnsafeWindow, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
package/dist/index.js ADDED
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
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
+ };
42
+
43
+ // lib/utils.ts
44
+ function autoPlural(word, num) {
45
+ if (Array.isArray(num) || num instanceof NodeList)
46
+ num = num.length;
47
+ return `${word}${num === 1 ? "" : "s"}`;
48
+ }
49
+ function clamp(value, min, max) {
50
+ return Math.max(Math.min(value, max), min);
51
+ }
52
+ function pauseFor(time) {
53
+ return new Promise((res) => {
54
+ setTimeout(res, time);
55
+ });
56
+ }
57
+ function debounce(func, timeout = 300) {
58
+ let timer;
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
@@ -0,0 +1 @@
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 ADDED
@@ -0,0 +1,146 @@
1
+ var __defProp = Object.defineProperty;
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
+ };
40
+
41
+ // lib/utils.ts
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
@@ -0,0 +1 @@
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/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@sv443-network/userutils",
3
+ "version": "0.1.1",
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
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "lint": "tsc && eslint .",
10
+ "prepare-build": "tsup lib/index.ts --format cjs,esm --dts --clean --treeshake",
11
+ "build": "npm run prepare-build -- --minify",
12
+ "build-dev": "npm run prepare-build -- --sourcemap --watch"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/Sv443-Network/UserUtils.git"
17
+ },
18
+ "keywords": [
19
+ "userscript",
20
+ "utilities"
21
+ ],
22
+ "author": {
23
+ "name": "Sv443",
24
+ "url": "https://github.com/Sv443"
25
+ },
26
+ "license": "MIT",
27
+ "bugs": {
28
+ "url": "https://github.com/Sv443-Network/UserUtils/issues"
29
+ },
30
+ "homepage": "https://github.com/Sv443-Network/UserUtils#readme",
31
+ "devDependencies": {
32
+ "@changesets/cli": "^2.26.2",
33
+ "@types/greasemonkey": "^4.0.4",
34
+ "@typescript-eslint/eslint-plugin": "^6.2.1",
35
+ "@typescript-eslint/parser": "^6.2.1",
36
+ "eslint": "^8.46.0",
37
+ "tslib": "^2.6.1",
38
+ "tsup": "^7.2.0",
39
+ "typescript": "^5.1.6"
40
+ },
41
+ "browserslist": [
42
+ "last 1 version",
43
+ "> 1%",
44
+ "not dead"
45
+ ]
46
+ }