Last active
March 25, 2025 00:35
-
-
Save FOBshippingpoint/94ecfdd661a82548e94aab57e7c3418e to your computer and use it in GitHub Desktop.
Handy userscript toolkit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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