forked from Sv443/Userscript.ts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.ts
More file actions
104 lines (92 loc) · 3.92 KB
/
Copy pathutils.ts
File metadata and controls
104 lines (92 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import { addGlobalStyle, openInNewTab, randomId, type LooseUnion } from "@sv443-network/userutils";
import type resources from "../assets/resources.json";
//#region resources
/** Key of a resource in `assets/resources.json` and extra keys defined by `tools/post-build.ts` */
export type ResourceKey = keyof typeof resources;
/**
* Returns the URL of a resource by its name, as defined in `assets/resources.json`, from GM resource cache - [see GM.getResourceUrl docs](https://wiki.greasespot.net/GM.getResourceUrl)
* Falls back to a `raw.githubusercontent.com` URL or base64-encoded data URI if the resource is not available in the GM resource cache.
* ⚠️ Requires the directive `@grant GM.getResourceUrl`
*/
export async function getResourceUrl(name: LooseUnion<ResourceKey>) {
let url = await GM.getResourceUrl(name);
if(!url || url.length === 0) {
console.warn(`Couldn't get blob URL nor external URL for @resource '${name}', trying to use base64-encoded fallback`);
// @ts-ignore
url = await GM.getResourceUrl(name, false);
}
return url;
}
//#region requests / urls
/**
* Sends a request with the specified parameters and returns the response as a Promise.
* Ignores the CORS policy, contrary to fetch and fetchAdvanced.
* ⚠️ Requires the directive `@grant GM.xmlhttpRequest`
*/
export function sendRequest<T = any>(details: GM.Request<T>) {
return new Promise<GM.Response<T>>((resolve, reject) => {
GM.xmlHttpRequest({
timeout: 10_000,
...details,
onload: resolve,
onerror: reject,
ontimeout: reject,
onabort: reject,
});
});
}
/**
* Opens the given URL in a new tab, using GM.openInTab if available
* ⚠️ Requires the directive `@grant GM.openInTab`
*/
export function openInTab(href: string, background = true) {
try {
openInNewTab(href, background);
}
catch(err) {
window.open(href, "_blank", "noopener noreferrer");
}
}
//#region DOM utils
export let domLoaded = document.readyState === "complete" || document.readyState === "interactive";
document.addEventListener("DOMContentLoaded", () => domLoaded = true);
/**
* Adds generic, accessible interaction listeners to the passed element.
* All listeners have the default behavior prevented and stop immediate propagation.
* @param listenerOptions Provide a {@linkcode listenerOptions} object to configure the listeners
*/
export function onInteraction<TElem extends HTMLElement>(elem: TElem, listener: (evt: MouseEvent | KeyboardEvent) => void, listenerOptions?: AddEventListenerOptions) {
const proxListener = (e: MouseEvent | KeyboardEvent) => {
if(e instanceof KeyboardEvent && !(["Enter", " ", "Space", "Spacebar"].includes(e.key)))
return;
e.preventDefault();
e.stopImmediatePropagation();
listenerOptions?.once && e.type === "keydown" && elem.removeEventListener("click", proxListener, listenerOptions);
listenerOptions?.once && e.type === "click" && elem.removeEventListener("keydown", proxListener, listenerOptions);
listener(e);
};
elem.addEventListener("click", proxListener, listenerOptions);
elem.addEventListener("keydown", proxListener, listenerOptions);
}
/** Removes all child nodes of an element without invoking the slow-ish HTML parser */
export function clearInner(element: Element) {
while(element.hasChildNodes())
clearNode(element!.firstChild as Element);
}
function clearNode(element: Element) {
while(element.hasChildNodes())
clearNode(element!.firstChild as Element);
element.parentNode!.removeChild(element);
}
/**
* Adds a style element to the DOM at runtime.
* @param css The CSS stylesheet to add
* @param ref A reference string to identify the style element - defaults to a random 5-character string
*/
export function addStyle(css: string, ref?: string) {
if(!domLoaded)
throw new Error("DOM has not finished loading yet");
const elem = addGlobalStyle(css);
elem.id = `global-style-${ref ?? randomId(5, 36)}`;
return elem;
}