您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
simple toolkit to help me create userscripts
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/526417/1643812/USToolkit.js
// ==UserScript== // @name USToolkit // @namespace https://greasyfork.org/pt-BR/users/821661 // @version 0.0.6 // @run-at document-start // @match https://*/* // @author hdyzen // @description simple toolkit to help me create userscripts // @license MIT // ==/UserScript== /** * Some functions are strongly inspired by: * github.com/violentmonkey/ * github.com/gorhill/uBlock/ * */ (() => { /** * Sets up a MutationObserver to watch for DOM changes and executes a callback function. * @param {function(MutationRecord[]): (boolean|void)} func The callback function to execute on mutation. * It receives an array of MutationRecord objects. If the function returns `true`, the observer is disconnected. * @param {MutationObserverInit} [options={ childList: true, subtree: true }] The options object for the MutationObserver. * @param {Node} [scope=document] The target node to observe. * @returns {MutationObserver} The created MutationObserver instance. */ function observe(func, options = { childList: true, subtree: true }, scope = document) { const observer = new MutationObserver((mut) => { const shouldDisconnect = func(mut); if (shouldDisconnect === true) { observer.disconnect(); } }); observer.observe(scope, options); return observer; } class OnElements { #rules = new Map(); #observedRoots = new WeakSet(); #combinedSelector = ""; #originalAttachShadow = unsafeWindow.Element.prototype.attachShadow; #isObserving = false; #activeObservers = new Set(); #deep; #root; constructor({ root = document, deep = false }) { this.#root = root; this.#deep = deep; } add(selector, callback) { if (!this.#rules.has(selector)) { this.#rules.set(selector, new Set()); } this.#rules.get(selector).add(callback); this.#updateCombinedSelector(); return this; } once(selector, callback) { const onceFn = (element) => { callback(element); this.remove(selector, callback); }; this.add(selector, onceFn); return this; } per(selector, callback) { const executedElements = new WeakSet(); const perElementFn = (element) => { if (executedElements.has(element)) return; callback(element); executedElements.add(element); }; this.add(selector, perElementFn); return this; } remove(selector, callback) { if (!this.#rules.has(selector)) return; if (!callback) { this.#rules.delete(selector); this.#updateCombinedSelector(); return; } const rule = this.#rules.get(selector); rule.delete(callback); if (rule.size === 0) { this.#rules.delete(selector); } this.#updateCombinedSelector(); return this; } start() { if (this.#isObserving) return; if (this.#deep === true) this.#patchAttachShadow(); if (this.#deep === true) this.#observerExistentShadows(); this.#observe(this.#root); this.#isObserving = true; } stop() { if (!this.#isObserving) return; this.#activeObservers.forEach((observer) => observer.disconnect()); this.#activeObservers.clear(); unsafeWindow.Element.prototype.attachShadow = this.#originalAttachShadow; this.#isObserving = false; } #observe(root) { if (this.#observedRoots.has(root)) { return; } this.#processExistingElements(root); const processedInBatch = new Set(); const processElement = (node) => { if (processedInBatch.has(node)) return; this.#routeElement(node); processedInBatch.add(node); }; const observer = new MutationObserver((mutations) => { if (!this.#combinedSelector) return; for (const mutation of mutations) { if (mutation.type === "attributes" && this.#combinedSelector && mutation.target.matches(this.#combinedSelector)) { processElement(mutation.target); continue; } for (const node of mutation.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; if (this.#combinedSelector && node.matches(this.#combinedSelector)) { processElement(node); } if (this.#combinedSelector) { node.querySelectorAll(this.#combinedSelector).forEach((el) => processElement(el)); } } } processedInBatch.clear(); }); observer.observe(root, { childList: true, subtree: true, attributes: true }); this.#activeObservers.add(observer); this.#observedRoots.add(root); } #processExistingElements(root) { if (!this.#combinedSelector) return; for (const node of queryAll(root, this.#combinedSelector)) { this.#routeElement(node); } } #patchAttachShadow() { const self = this; unsafeWindow.Element.prototype.attachShadow = function (init) { const shadowRoot = self.#originalAttachShadow.call(this, init); self.#observe(shadowRoot); return shadowRoot; }; } #routeElement(element) { for (const [selector, callbacks] of this.#rules.entries()) { if (!element.matches(selector)) { continue; } for (const callback of [...callbacks]) { if (callback(element) !== true) { continue; } this.remove(selector, callback); } } } #observerExistentShadows() { for (const node of queryAll(document, "*")) { if (node.shadowRoot) this.#observe(node.shadowRoot); } } #updateCombinedSelector() { this.#combinedSelector = [...this.#rules.keys()].join(","); } } function onElement(selector, callback) { if (this.observer === undefined) { this.observer = new OnElements({ deep: true }); } this.observer.add(selector, callback).start(); return this.observer; } function* queryAll(scope, selector) { for (const element of scope.querySelectorAll("*")) { if (element.matches(selector)) { yield element; } if (element.shadowRoot) { yield* queryAll(element.shadowRoot, selector); } } } function query(scope, selector) { const iterator = queryAll(scope, selector); const result = iterator.next(); return result.done ? null : result.value; } function closest(element, selector) { let node = element; while (node) { const found = node.closest(selector); if (found) { return found; } const root = node.getRootNode(); if (root instanceof ShadowRoot) { node = root.host; continue; } break; } return null; } function injectScriptInline(code) { const script = document.createElement("script"); script.textContent = code; (document.head || document.documentElement).appendChild(script); script.remove(); return; } /** * Waits for an element that matches a given CSS selector to appear in the DOM. * @param {string} selector The CSS selector for the element to wait for. * @param {number} [timeout=5000] The maximum time to wait in milliseconds. * @returns {Promise<HTMLElement>} A promise that resolves with the found element, or rejects on timeout. */ function waitElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { const onEl = new OnElements({ deep: true }); onEl.once(selector, resolve).start(); setTimeout(() => { onEl.stop(); reject(); }, timeout); }); } /** * Attaches a delegated event listener to a scope. * @param {string} event The name of the event (e.g., 'click'). * @param {string} selector A CSS selector to filter the event target. * @param {function(Event): void} callback The event handler function. * @param {EventListenerOptions} options Options passed to eventListener. * @param {Node} [scope=document] The parent element to attach the listener to. */ function on(event, selector, callback, options, scope = document) { const handler = (event) => { if (closest(event.target, selector)) callback(event); }; scope.addEventListener(event, handler, options); return () => scope.removeEventListener(event, handler, options); } /** * Cria um proxy recursivo que invoca um callback. * O callback pode opcionalmente retornar um valor para substituir o comportamento padrão. * * @param {object} target - O objeto inicial a ser observado. * @param {function(object): any} callback - A função a ser chamada na interceptação. * @returns {Proxy} - O objeto envolvido pelo proxy. */ function createDeepProxy(target, callback) { const _createProxy = (currentTarget, currentPath) => { return new Proxy(currentTarget, { get(obj, prop) { const newPath = [...currentPath, prop]; const value = Reflect.get(obj, prop); const result = callback({ action: "get", path: newPath, prop, value, valueType: valType(value), target: obj, }); if (result !== undefined) { return result; } if (typeof value === "object" && value !== null) { return _createProxy(value, newPath); } return value; }, set(obj, prop, newValue) { const newPath = [...currentPath, prop]; const result = callback({ action: "set", path: newPath, prop, value: newValue, valueType: valType(value), target: obj, }); if (result !== undefined) { return result; } return Reflect.set(obj, prop, newValue); }, }); }; return _createProxy(target, []); } function safeSet(obj, chain, value, { override } = { override: false }) { if (!obj || typeof chain !== "string" || chain === "") { return; } const props = chain.split("."); let current = obj; for (let i = 0; i < props.length; i++) { const prop = props[i]; // console.log("Current:", current, "\nProp:", prop, "\nIndex:", i); if (valType(current) !== "object" || !Object.hasOwn(current, prop) || override) { current = current[prop] = {}; continue; } current = current[prop]; } current[props[props.length - 1]] = value; } /** * Safely retrieves a nested property from an object using a string path. * Supports special wildcards for arrays ('[]') and objects ('{}' or '*'). * @param {object} obj The source object. * @param {string} chain A dot-separated string for the property path (e.g., 'user.address.street'). * @returns {*} The value of the nested property, or undefined if not found. */ function safeGet(obj, chain) { if (!obj || typeof chain !== "string" || chain === "") { return; } const props = chain.split("."); // const props = propChain.match(/'[^']*'|"[^"]*"|\[[^\]]*]|\([^)]*\)|{[^}]*}|[^.()[\]{}\n]+/g); // const props = parsePropChain(propChain); let current = obj; for (let i = 0; i < props.length; i++) { const prop = props[i]; if (current === undefined || current === null) { break; } // console.log(current, prop); if (prop === "[]") { i++; current = handleArray(current, props[i]); continue; } if (prop === "{}" || prop === "*") { i++; current = handleObject(current, props[i]); continue; } if (startsEndsWith(prop, "(", ")")) { current = handleFunction(current, prop); continue; } current = current[prop]; } return current; } /** * Safely handles function calls from the property chain. * It parses arguments as JSON. * @param {function} fn The function to call. * @param {string} prop The string containing arguments, e.g., '({"name": "test"})'. * @returns {*} The result of the function call. */ function handleFunction(fn, prop) { const argString = prop.slice(1, -1).trim().replaceAll("'", '"'); let args; if (argString === "") { return fn(); } try { args = JSON.parse(`[${argString}]`); } catch (err) { console.error(`[UST.safeGet] Failed to execute function in property chain "${prop}":`, err); } return typeof fn === "function" ? fn(...args) : undefined; } function _parseValue(value) { if (value === "true") { return true; } if (value === "false") { return false; } if (value === "null") { return null; } if (value === "undefined") { return undefined; } if (typeof value === "string" && (startsEndsWith(value, "'") || startsEndsWith(value, '"'))) { return value.slice(1, -1); } if (typeof value === "string" && value.trim() !== "") { const num = Number(value); return !Number.isNaN(num) ? num : value; } return value; } /** * Helper for `prop` to handle array wildcards. It maps over an array and extracts a property from each item. * @param {Array<object>} arr The array to process. * @param {string} nextProp The property to extract from each item. * @returns {*} An array of results, or a single result if only one is found. */ function handleArray(arr, nextProp) { const results = []; for (const item of arr) { if (getProp(item, nextProp) !== undefined) { results.push(item); } } return results; } /** * Helper for `prop` to handle object wildcards. It maps over an object's values and extracts a property. * @param {object} obj The object to process. * @param {string} nextProp The property to extract from each value. * @returns {*} An array of results, or a single result if only one is found. */ function handleObject(obj, nextProp) { const keys = Object.keys(obj); const results = []; for (const key of keys) { if (getProp(obj[key], nextProp) !== undefined) { results.push(obj[key]); } } return results; } /** * Safely gets an own property from an object. * @param {object} obj The source object. * @param {string} prop The property name. * @returns {*} The property value or undefined if it doesn't exist. */ function getProp(obj, prop) { if (obj && Object.hasOwn(obj, prop)) { return obj[prop]; } return; } /** * Checks if a value is a plain JavaScript object. * @param {*} val The value to check. * @returns {boolean} True if the value is a plain object, otherwise false. */ function isObject(val) { return Object.prototype.toString.call(val) === "[object Object]"; } // function compareProps(objToCompare, obj) { // return Object.entries(obj).every(([prop, value]) => { // return Object.hasOwn(objToCompare, prop) && objToCompare[prop] === value; // }); // } /** * Checks if all properties and their values in the targetObject exist and are equal in the referenceObject. * @param {Object} referenceObject The object to compare against. * @param {Object} targetObject The object whose properties and values are checked for equality. * @returns {boolean} Returns true if all properties and values in targetObject are present and equal in referenceObject, otherwise false. */ function checkPropertyEquality(referenceObject, targetObject) { const entries = Object.entries(targetObject); for (const [prop, value] of entries) { if (!Object.hasOwn(referenceObject, prop)) { return false; } if (referenceObject[prop] !== value) { return false; } } return true; } function containsValue(valueReference, ...values) { for (const value of values) { if (valueReference === value) return true; } return false; } function startsEndsWith(string, ...searchs) { const [startSearch, endSearch] = searchs; const firstChar = string[0]; const lastChar = string[string.length - 1]; if (endSearch === undefined) { return firstChar === startSearch && lastChar === startSearch; } return firstChar === startSearch && lastChar === endSearch; } /** * Gets a more specific type of a value than `typeof`. * @param {*} val The value whose type is to be determined. * @returns {string} The type of the value (e.g., 'string', 'array', 'object', 'class', 'null'). */ function valType(val) { if (val?.prototype?.constructor === val) { return "class"; } return Object.prototype.toString.call(val).slice(8, -1).toLowerCase(); } /** * Returns the length or size of the given target based on its type. * * Supported types: * - string: Returns the string's length. * - array: Returns the array's length. * - object: Returns the number of own enumerable properties. * - set: Returns the number of elements in the Set. * - map: Returns the number of elements in the Map. * - null: Returns 0. * * @param {*} target - The value whose length or size is to be determined. * @returns {number} The length or size of the target. * @throws {Error} If the type of target is unsupported. */ function len(target) { const type = valType(target); const types = { string: () => target.length, object: () => Object.keys(target).length, array: () => target.length, set: () => target.size, map: () => target.size, null: () => 0, }; if (types[type]) { return types[type](); } else { throw new Error(`Unsupported type: ${type}`); } } /** * Repeatedly calls a function with a delay until it returns `true`. * Uses `requestAnimationFrame` for scheduling. * @param {function(): (boolean|void)} func The function to run. The loop stops if it returns `true`. * @param {number} [time=250] The delay in milliseconds between executions. */ function update(func, time = 250) { const exec = () => { if (func() === true) { return; } setTimeout(() => { requestAnimationFrame(exec); }, time); }; requestAnimationFrame(exec); } /** * Runs a function on every animation frame until the function returns `true`. * @param {function(): (boolean|void)} func The function to execute. The loop stops if it returns `true`. */ function loop(func) { const exec = () => { if (func() === true) { return; } requestAnimationFrame(exec); }; requestAnimationFrame(exec); } /** * Injects a CSS string into the document by adoptedStyleSheets. * @param {string} css The CSS text to apply. * @returns {HTMLStyleElement} A promise that resolves with the created style element. */ function style(css) { const sheet = new CSSStyleSheet(); sheet.replaceSync(css); document.adoptedStyleSheets.push(sheet); for (const node of queryAll(document, "*")) { if (node.shadowRoot) { node.shadowRoot.adoptedStyleSheets.push(sheet); } } } /** * Intercepts calls to an object's method using a Proxy, allowing modification of its behavior. * @param {object} owner The object that owns the method. * @param {string} methodName The name of the method to hook. * @param {ProxyHandler<function>} handler The proxy handler to intercept the method call. * @returns {function(): void} A function that, when called, reverts the method to its original implementation. */ function hook(owner, methodName, handler) { const originalMethod = owner[methodName]; // if (typeof originalMethod !== "function") { // throw new Error(`[UST.patch] The method “${methodName}” was not found in the object "${owner}".`); // } const proxy = new Proxy(originalMethod, handler); owner[methodName] = proxy; return () => { owner[methodName] = originalMethod; }; } /** * An object to execute callbacks based on changes in the page URL, useful for Single Page Applications (SPAs). */ const watchUrl = { _enabled: false, _onUrlRules: [], /** * Adds a URL pattern and a callback to execute when the URL matches. * @param {string|RegExp} pattern The URL pattern to match against. Can be a string or a RegExp. * @param {function(): void} func The callback to execute on match. */ add(pattern, func) { const isRegex = pattern instanceof RegExp; const patternRule = pattern.startsWith("/") ? unsafeWindow.location.origin + pattern : pattern; this._onUrlRules.push({ pattern: patternRule, func, isRegex }); if (this._enabled === false) { this._enabled = true; this.init(); } }, /** * @private * Initializes the URL watching mechanism. */ init() { const exec = (currentUrl) => { const ruleFound = this._onUrlRules.find((rule) => rule.isRegex ? rule.pattern.test(currentUrl) : rule.pattern === currentUrl, ); if (ruleFound) { ruleFound.func(); } }; watchLocation(exec); }, }; /** * Monitors `location.href` for changes and triggers a callback. It handles history API changes (pushState, replaceState) * and popstate events, making it suitable for SPAs. * @param {function(string): void} callback The function to call with the new URL when a change is detected. */ function watchLocation(callback) { let previousUrl = location.href; const observer = new MutationObserver(() => checkForChanges()); observer.observe(unsafeWindow.document, { childList: true, subtree: true }); const checkForChanges = () => { requestAnimationFrame(() => { const currentUrl = location.href; if (currentUrl !== previousUrl) { previousUrl = currentUrl; callback(currentUrl); } }); }; const historyHandler = { apply(target, thisArg, args) { const result = Reflect.apply(target, thisArg, args); checkForChanges(); return result; }, }; hook(history, "pushState", historyHandler); hook(history, "replaceState", historyHandler); unsafeWindow.addEventListener("popstate", checkForChanges); callback(previousUrl); } /** * A promise-based wrapper for the Greasemonkey `GM_xmlhttpRequest` function. * @param {object} options The options for the request, matching the `GM_xmlhttpRequest` specification. * @returns {Promise<object>} A promise that resolves with the response object on success or rejects on error/timeout. */ function request(options) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ onload: resolve, onerror: reject, ontimeout: reject, ...options, }); }); } /** * Extracts data from an element based on an array of property path definitions. * @param {HTMLElement} element The root element to extract properties from. * @param {Array<string>} propsArray Array of property definitions, e.g., ["name:innerText", "link:href"]. * @returns {object} An object containing the extracted data. */ function extractProps(element, propsArray) { const data = {}; for (const propDefinition of propsArray) { const [label, valuePath] = propDefinition.split(":"); if (valuePath) { data[label] = safeGet(element, valuePath); } else { data[label] = safeGet(element, label); } } return data; } /** * @private * Handles a string rule in the scrape schema. * @param {HTMLElement} container The container element. * @param {string} rule The CSS selector for the target element. * @returns {string|null} The text content of the found element, or null. */ function _handleStringRule(container, rule) { const element = container.querySelector(rule); return element ? element.textContent.trim() : null; } /** * @private * Handles an array rule in the scrape schema. * @param {HTMLElement} container The container element. * @param {Array<string>} rule An array where the first item is a sub-selector and the rest are property definitions. * @returns {object} The extracted properties from the sub-element. */ function _handleArrayRule(container, rule) { const [subSelector, ...propsToGet] = rule; if (!subSelector) { throw new Error("[UST.scrape] No subselector provided as the first item in the rule"); } const element = container.querySelector(subSelector); return extractProps(element, propsToGet); } const ruleHandlers = { string: _handleStringRule, array: _handleArrayRule, }; /** * @private * Determines the type of a scrape rule. * @param {*} rule The rule to check. * @returns {string} The type of the rule ('string', 'array', or 'unknown'). */ function _getRuleType(rule) { if (typeof rule === "string") return "string"; if (Array.isArray(rule)) return "array"; return "unknown"; } /** * @private * Processes an object schema for scraping. * @param {HTMLElement} container The container element. * @param {object} schema The schema object. * @returns {object} The scraped data object. */ function _processObjectSchema(container, schema) { const item = {}; for (const key in schema) { const rule = schema[key]; const ruleType = _getRuleType(rule); const handler = ruleHandlers[ruleType]; if (handler) { item[key] = handler(container, rule); continue; } console.warn(`[UST.scrape] Rule for key “${key}” has an unsupported type.`); } return item; } /** * @private * Processes a single container element based on the provided schema. * @param {HTMLElement} container The container element to process. * @param {object|Array<string>} schema The schema to apply. * @returns {object} The scraped data. */ function _processContainer(container, schema) { if (Array.isArray(schema)) { return extractProps(container, schema); } if (isObject(schema)) { return _processObjectSchema(container, schema); } console.warn("[UST.scrape] Invalid schema format."); return {}; } /** * Scrapes structured data from the DOM based on a selector and a schema. * @param {string} selector CSS selector for the container elements to scrape. * @param {object|Array<string>} schema Defines the data to extract from each container. * @param {function(HTMLElement, object): void} func A callback for each scraped item, receiving the container element and the extracted data object. * @param {Node} [scope=document] The scope within which to search for containers. * @returns {Array<object>} An array of the scraped data objects. */ function scrape(selector, schema, func, scope = document) { const containers = scope.querySelectorAll(selector); const results = []; for (const container of containers) { const item = _processContainer(container, schema); func(container, item); results.push(item); } return results; } /** * Iterates over all elements matching a selector and applies a function to each. * @param {string} selector A CSS selector. * @param {function(Node): void} func The function to execute for each matching element. * @returns {NodeListOf<Element>} The list of nodes found. */ function each(selector, func) { const nodes = document.querySelectorAll(selector); for (const node of nodes) { func(node); } return nodes; } /** * Chains multiple iterables together into a single sequence. * @param {...Iterable} iterables One or more iterable objects (e.g., arrays, sets). * @returns {Generator} A generator that yields values from each iterable in order. */ function* chain(...iterables) { for (const it of iterables) { yield* it; } } /** * Creates a debounced version of a function that delays its execution until after a certain time has passed * without it being called. * @param {function} func The function to debounce. * @param {number} wait The debounce delay in milliseconds. * @returns {function} The new debounced function. */ function debounce(func, wait) { let timeout; return function (...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } /** * Pauses execution for a specified number of milliseconds. * @param {number} ms The number of milliseconds to sleep. * @returns {Promise<void>} A promise that resolves after the specified time. */ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * A simple template engine that extends Map. It replaces `{{placeholder}}` syntax in strings. * @extends Map */ class Templates extends Map { /** * Fills a template with the provided data. * @param {*} key The key of the template stored in the map. * @param {object} [data={}] An object with key-value pairs to replace placeholders. * @returns {string|null} The template string with placeholders filled, or null if the template is not found. */ fill(key, data = {}) { const template = super.get(key); if (!template) { console.warn(`[UST.Templates] Template with key “${key}” not found.`); return null; } return template.replace(/\{\{(\s*\w+\s*)\}\}/g, (match, placeholder) => Object.hasOwn(data, placeholder) ? data[placeholder] : match, ); } /** * Renders a template into a DocumentFragment. * @param {*} key The key of the template stored in the map. * @param {object} [data={}] An object with data to fill the placeholders. * @returns {DocumentFragment|null} A document fragment containing the rendered HTML, or null if the template is not found. */ render(key, data = {}) { const filledHtml = this.fill(key, data); if (filledHtml === null) { return null; } const templateElement = document.createElement("template"); templateElement.innerHTML = filledHtml; return templateElement.content.cloneNode(true); } } /** * Factory function to create a new Templates instance. * @returns {Templates} A new instance of the Templates class. */ function templates() { return new Templates(); } /** * A class for creating lazy, chainable operations (map, filter, take) on iterables. * Operations are only executed when the sequence is consumed. */ class LazySequence extends Array { /** * @param {Iterable<any>} iterable The initial iterable. */ constructor(iterable) { super(); this.iterable = iterable; } /** * Creates a new lazy sequence with a mapping function. * @param {function(*): *} func The mapping function. * @returns {LazySequence} A new LazySequence instance. */ map(func) { const self = this; return new LazySequence({ *[Symbol.iterator]() { for (const value of self.iterable) { yield func(value); } }, }); } /** * Creates a new lazy sequence with a filtering function. * @param {function(*): boolean} func The filtering function. * @returns {LazySequence} A new LazySequence instance. */ filter(func) { const self = this; return new LazySequence({ *[Symbol.iterator]() { for (const value of self.iterable) { if (func(value)) { yield value; } } }, }); } /** * Creates a new lazy sequence that takes only the first n items. * @param {number} n The number of items to take. * @returns {LazySequence} A new LazySequence instance. */ take(n) { const self = this; return new LazySequence({ *[Symbol.iterator]() { let count = 0; for (const value of self.iterable) { if (count >= n) break; yield value; count++; } }, }); } /** * Makes the LazySequence itself iterable. */ *[Symbol.iterator]() { yield* this.iterable; } /** * Executes all lazy operations and returns the results as an array. * @returns {Array<*>} An array containing all values from the processed iterable. */ collect() { return [...this.iterable]; } } /** * Factory function to create a new LazySequence. * @param {Iterable<any>} iterable An iterable to wrap. * @returns {LazySequence} A new LazySequence instance. */ function lazy(iterable) { return new LazySequence(iterable); } /** * Creates a DocumentFragment and populates it using a callback. * This is useful for building a piece of DOM in memory before attaching it to the live DOM. * @param {function(DocumentFragment): void} builderCallback A function that receives a document fragment and can append nodes to it. * @returns {DocumentFragment} The populated document fragment. */ function createFromFragment(builderCallback) { const fragment = document.createDocumentFragment(); builderCallback(fragment); return fragment; } /** * Detaches an element from the DOM, runs a callback to perform modifications, and then re-attaches it. * This can improve performance by preventing multiple browser reflows and repaints during manipulation. * @param {HTMLElement|string} elementOrSelector The element or its CSS selector. * @param {function(HTMLElement): void} callback The function to execute with the detached element. */ function withDetached(elementOrSelector, callback) { const element = typeof elementOrSelector === "string" ? document.querySelector(elementOrSelector) : elementOrSelector; if (!element || !element.parentElement) return; const parent = element.parentElement; const nextSibling = element.nextElementSibling; parent.removeChild(element); try { callback(element); } finally { parent.insertBefore(element, nextSibling); } } window.UST = window.UST || {}; Object.assign(window.UST, { observe, OnElements, onElement, queryAll, query, closest, injectScriptInline, waitElement, on, createDeepProxy, safeSet, safeGet, handleArray, handleObject, checkPropertyEquality, getProp, isObject, containsValue, valType, len, update, loop, style, hook, watchUrl, watchLocation, request, extractProps, scrape, each, chain, debounce, sleep, templates, lazy, createFromFragment, withDetached, }); })();