Sven пре 1 година
родитељ
комит
72181703dd
6 измењених фајлова са 377 додато и 16 уклоњено
  1. 1 0
      .gitignore
  2. 1 0
      .npmignore
  3. 202 3
      README.md
  4. 168 0
      package-lock.json
  5. 2 3
      package.json
  6. 3 10
      tsconfig.json

+ 1 - 0
.gitignore

@@ -1,2 +1,3 @@
 node_modules/
 dist/
+test.ts

+ 1 - 0
.npmignore

@@ -5,3 +5,4 @@ lib/
 .gitignore
 .npmignore
 tsconfig.json
+test.ts

+ 202 - 3
README.md

@@ -1,5 +1,6 @@
 ## UserUtils
-Various utilities for userscripts
+Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more.  
+Contains builtin TypeScript definitions.
 
 <br>
 
@@ -31,9 +32,9 @@ Various utilities for userscripts
   ```
   Then, import it in your script as usual:
   ```ts
-  import * as userUtils from "@sv443-network/userutils";
-  // or
   import { addGlobalStyle } from "@sv443-network/userutils";
+  // or
+  import * as userUtils from "@sv443-network/userutils";
   ```
   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.
 
@@ -53,32 +54,230 @@ If you like using this library, please consider [supporting development](https:/
 ## Features:
 
 ### onSelector()
+\- UNFINISHED -  
+  
+Registers a listener to be called whenever the element(s) behind a selector is/are found in the DOM.  
+In order to use this function, the MutationObservers have to be initialized with `initOnSelector()` first.  
+  
+Example:
+```ts
+// TODO
+```
+
+<br>
 
 ### autoPlural()
+Usage: `autoPlural(str: string, num: number | Array | NodeList): string`  
+  
+Automatically pluralizes a string if the given number is not 1.  
+If an array or NodeList is passed, the length of it will be used.  
+  
+Example:
+```ts
+autoPlural("apple", 0); // "apples"
+autoPlural("apple", 1); // "apple"
+autoPlural("apple", 2); // "apples"
+
+autoPlural("apple", [1]);    // "apple"
+autoPlural("apple", [1, 2]); // "apples"
+```
+
+<br>
 
 ### clamp()
+Usage: `clamp(num: number, min: number, max: number): number`  
+  
+Clamps a number between a min and max value.  
+  
+Example:
+```ts
+clamp(5, 0, 10);        // 5
+clamp(-1, 0, 10);       // 0
+clamp(Infinity, 0, 10); // 10
+```
+
+<br>
 
 ### pauseFor()
+Usage: `pauseFor(ms: number): Promise<void>`  
+  
+Pauses async execution for a given amount of time.  
+  
+Example:
+```ts
+async function run() {
+  console.log("Hello");
+  await pauseFor(3000); // waits for 3 seconds
+  console.log("World");
+}
+```
+
+<br>
 
 ### debounce()
+Usage: `debounce(func: Function, timeout?: number): Function`  
+  
+Debounces a function, meaning that it will only be called once after a given amount of time.  
+This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.  
+The timeout will default to 300ms if left undefined.  
+  
+Example:
+```ts
+window.addEventListener("resize", debounce((event) => {
+  console.log("Window was resized:", event);
+}, 500)); // 500ms timeout
+```
+
+<br>
 
 ### getUnsafeWindow()
+Usage: `getUnsafeWindow(): Window`  
+  
+Returns the unsafeWindow object or falls back to the regular window object if the `@grant unsafeWindow` is not given.  
+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.  
+  
+Example:
+```ts
+const mouseEvent = new MouseEvent("click", {
+  view: getUnsafeWindow(),
+});
+document.body.dispatchEvent(mouseEvent);
+```
+
+<br>
 
 ### insertAfter()
+Usage: `insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement`  
+  
+Inserts the element passed as `afterElement` as a sibling after the passed `beforeElement`.  
+The `afterElement` will be returned.  
+  
+Example:
+```ts
+const beforeElement = document.querySelector("#before");
+const afterElement = document.createElement("div");
+afterElement.innerText = "After";
+insertAfter(beforeElement, afterElement);
+```
+
+<br>
 
 ### addParent()
+Usage: `addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement`  
+  
+Adds a parent element around the passed `element` and returns the new parent.  
+Previously registered event listeners should be kept intact.  
+  
+Example:
+```ts
+const element = document.querySelector("#element");
+const newParent = document.createElement("a");
+newParent.href = "https://example.org/";
+addParent(element, newParent);
+```
+
+<br>
 
 ### addGlobalStyle()
+Usage: `addGlobalStyle(css: string): void`  
+  
+Adds a global style to the page in form of a `<style>` element that's inserted into the `<head>`.  
+This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).  
+  
+Example:
+```ts
+document.addEventListener("DOMContentLoaded", () => {
+  addGlobalStyle(`
+    body {
+      background-color: red;
+    }
+  `);
+});
+```
+
+<br>
 
 ### preloadImages()
+Usage: `preloadImages(...urls: string[]): Promise<void>`  
+  
+Preloads images into browser cache by creating an invisible `<img>` element for each URL passed.  
+The images will be loaded in parallel and the returned Promise will only resolve once all images have been loaded.  
+The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected.  
+  
+Example:
+```ts
+preloadImages(
+  "https://example.org/image1.png",
+  "https://example.org/image2.png",
+  "https://example.org/image3.png",
+).then((results) => {
+  console.log("Images preloaded. Results:", results);
+});
+```
+
+<br>
 
 ### fetchAdvanced()
+Usage: `fetchAdvanced(url: string, options?: FetchAdvancedOptions): Promise<Response>`  
+  
+A wrapper around the native `fetch()` function that adds options like a timeout property.  
+The timeout will default to 10 seconds if left undefined.  
+  
+Example:
+```ts
+fetchAdvanced("https://example.org/", {
+  timeout: 5000,
+}).then((response) => {
+  console.log("Response:", response);
+});
+```
+
+<br>
 
 ### openInNewTab()
+Usage: `openInNewTab(url: string): void`  
+  
+Creates an invisible anchor with a `_blank` target and clicks it.  
+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.  
+This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.  
+  
+Example:
+```ts
+document.querySelector("#button").addEventListener("click", () => {
+  openInNewTab("https://example.org/");
+});
+```
+
+<br>
 
 ### interceptEvent()
+Usage: `interceptEvent(eventObject: EventTarget, eventName: string, predicate: () => boolean): void`  
+  
+Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.  
+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.  
+Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.  
+  
+Example:
+```ts
+interceptEvent(document.body, "click", () => {
+  return true; // prevent all click events on the body
+});
+```
+
+<br>
 
 ### interceptWindowEvent()
+Usage: `interceptWindowEvent(eventName: string, predicate: () => boolean): void`  
+  
+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.  
+This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).  
+  
+Example:
+```ts
+interceptWindowEvent("beforeunload", () => {
+  return true; // prevent the pesky "Are you sure you want to leave this page?" popup
+});
+```
 
 
 <br><br>

+ 168 - 0
package-lock.json

@@ -713,6 +713,32 @@
         "prettier": "^2.7.1"
       }
     },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
     "node_modules/@esbuild/android-arm": {
       "version": "0.18.17",
       "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz",
@@ -1355,6 +1381,38 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+      "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+      "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "node_modules/@types/greasemonkey": {
       "version": "4.0.4",
       "resolved": "https://registry.npmjs.org/@types/greasemonkey/-/greasemonkey-4.0.4.tgz",
@@ -1611,6 +1669,17 @@
         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
       }
     },
+    "node_modules/acorn-walk": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+      "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/ajv": {
       "version": "6.12.6",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -1679,6 +1748,14 @@
         "node": ">= 8"
       }
     },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2028,6 +2105,14 @@
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
       "dev": true
     },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2169,6 +2254,17 @@
         "node": ">=8"
       }
     },
+    "node_modules/diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
     "node_modules/dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -3645,6 +3741,14 @@
         "node": ">=10"
       }
     },
+    "node_modules/make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "node_modules/map-obj": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
@@ -5227,6 +5331,51 @@
       "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
       "dev": true
     },
+    "node_modules/ts-node": {
+      "version": "10.9.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+      "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-esm": "dist/bin-esm.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/tslib": {
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
@@ -5443,6 +5592,14 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
     "node_modules/validate-npm-package-license": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -5635,6 +5792,17 @@
         "node": ">=12"
       }
     },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/yocto-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

+ 2 - 3
package.json

@@ -9,8 +9,7 @@
     "lint": "tsc && eslint .",
     "prepare-build": "tsup lib/index.ts --format cjs,esm --dts --clean --treeshake",
     "build": "npm run prepare-build -- --minify",
-    "build-dev": "npm run prepare-build -- --sourcemap --watch",
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "build-dev": "npm run prepare-build -- --sourcemap --watch"
   },
   "repository": {
     "type": "git",
@@ -44,4 +43,4 @@
     "> 1%",
     "not dead"
   ]
-}
+}

+ 3 - 10
tsconfig.json

@@ -4,11 +4,7 @@
     "moduleResolution": "node",
     "target": "ES2016",
     "outDir": "dist/out",
-    "lib": [
-      "ES2020",
-      "DOM",
-      "DOM.Iterable"
-    ],
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
     "allowSyntheticDefaultImports": true,
     "baseUrl": ".",
     "forceConsistentCasingInFileNames": true,
@@ -20,10 +16,7 @@
     "useDefineForClassFields": true,
     "noImplicitThis": false,
     "noUncheckedIndexedAccess": true,
-    "noEmit": true,
+    "noEmit": true
   },
-  "exclude": [
-    "**/*.js",
-    "dist/**",
-  ],
+  "exclude": ["**/*.js", "dist/**"]
 }