Skip to content

Instantly share code, notes, and snippets.

@FOBshippingpoint
Last active March 25, 2025 00:35
Show Gist options
  • Save FOBshippingpoint/94ecfdd661a82548e94aab57e7c3418e to your computer and use it in GitHub Desktop.
Save FOBshippingpoint/94ecfdd661a82548e94aab57e7c3418e to your computer and use it in GitHub Desktop.
Handy userscript toolkit
/**
* Add CSS stylesheet to current document.
*
* @param {string} css - CSS stylesheet to adopt.
*/
function addCss(css) {
const extraSheet = new CSSStyleSheet();
extraSheet.replaceSync(css);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, extraSheet];
}
/**
* Write text to clipboard.
* Use Clipboard API if possible, fallback to document.exec when failed.
*
* @param {string} text - string to copy.
*/
async function writeClipboardText(text) {
if ("clipboard" in navigator) {
/* Won't work under HTTP */
navigator.clipboard
.writeText(text)
.then(() => {
console.log("Text copied");
})
.catch((err) => console.error(err.name, err.message));
} else {
const textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.opacity = 0;
document.body.appendChild(textArea);
textArea.focus({ preventScroll: true });
textArea.select();
try {
const success = document.execCommand("copy");
console.log(`Text copy was ${success ? "successful" : "unsuccessful"}.`);
} catch (err) {
console.error(err.name, err.message);
}
document.body.removeChild(textArea);
}
}
/**
* Mock browser's $ alias for document.querySelector()
*/
function $(selector) {
return document.querySelector(selector);
}
/**
* Mock browser's $$ alias for document.querySelectorAll()
*/
function $$(selector) {
return [...document.querySelectorAll(selector)];
}
function $$$(tag) {
return document.createElement(tag);
}
/**
* Mock browser's $x helper function in devtools.
*
* @param {string} xpath - the XPath to be evaluated.
* @see {@link https://devhints.io/xpath}
*/
function $x(xpath) {
const xpathResult = document.evaluate(
xpath,
document,
null,
XPathResult.ANY_TYPE,
null,
);
const elements = [];
let el;
while ((el = xpathResult.iterateNext())) {
elements.push(el);
}
return elements;
}
/**
* Construct element using HTML string.
* @param {string} html - the HTML, also accept multiple elements without wrapper element.
*/
function h(html) {
const template = $$$("template");
template.innerHTML = html;
if (template.content.childElementCount === 1) {
return template.content.firstElementChild;
} else {
/* DocumentFragment */
return template.content;
}
}
/**
* Forked from https://github.com/violentmonkey/vm-dom/blob/master/src/index.ts
*
* Observe an existing `node` until `callback` returns `true`.
* The returned function can be called explicitly to disconnect the observer.
*
* ```js
* observe(document.body, () => {
* const node = document.querySelector('.profile');
* if (node) {
* console.log('It\'s there!');
* return true;
* }
* });
* ```
*/
function observe(node, callback, options) {
const observer = new MutationObserver((mutations, ob) => {
const result = callback(mutations, ob);
if (result) disconnect();
});
observer.observe(node, {
childList: true,
subtree: true,
...options,
});
const disconnect = () => observer.disconnect();
return disconnect;
}
/**
* Wait until querySelector found.
* ```js
* until("#username", (input) => {
* input.value = "admin";
* });
* ```
*/
function until(querySelector, callback) {
const target = $(querySelector);
if (target) {
callback(target);
} else {
observe(document, (mutations) => {
const target = $(querySelector);
if (target) {
callback(target);
return true;
}
});
}
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////INITIALIZATION//////////////////////////////
///////////////////////////////////////////////////////////////////////////
if (EventTarget.prototype.on) {
console.warn(`EventTarget.prototype.on already in use.`);
} else {
Element.prototype.$ = Element.prototype.querySelector;
}
if (EventTarget.prototype.off) {
console.warn(`EventTarget.prototype.off already in use.`);
} else {
EventTarget.prototype.off = EventTarget.prototype.removeEventListener;
}
if (Element.prototype.$) {
console.warn(`Element.prototype.$ already in use.`);
} else {
Element.prototype.$ = Element.prototype.querySelector;
}
if (Element.prototype.$$) {
console.warn(`Element.prototype.$$ already in use.`);
} else {
Element.prototype.$$ = function (...args) {
return [...Element.prototype.querySelectorAll.apply(this, args)];
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment