This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/456034/1526010/Basic%20Functions%20%28For%20userscripts%29.js
/* eslint-disable no-multi-spaces */
/* eslint-disable no-return-assign */
// ==UserScript==
// @name Basic Functions (For userscripts)
// @name:zh-CN 常用函数(用户脚本)
// @name:en Basic Functions (For userscripts)
// @namespace PY-DNG Userscripts
// @version 0.10.1
// @description Useful functions for myself
// @description:zh-CN 自用函数
// @description:en Useful functions for myself
// @author PY-DNG
// @license GPL-3.0-or-later
// ==/UserScript==
// Note: version 0.8.2.1 is modified just the license and it's not uploaded to GF yet 23-11-26 15:03
// Note: version 0.8.3.1 is added just the description of parseArgs and has not uploaded to GF yet 24-02-03 18:55
let [
// Console & Debug
LogLevel, DoLog, Err, Assert,
// DOM
$, $All, $CrE, $AEL, $$CrE, addStyle, detectDom, destroyEvent,
// Data
copyProp, copyProps, parseArgs, escJsStr, replaceText,
// Environment & Browser
getUrlArgv, dl_browser, dl_GM,
// Logic & Task
AsyncManager, queueTask, testChecker, registerChecker, loadFuncs
] = (function() {
/**
* level defination for DoLog function, bigger ones has higher possibility to be printed in console
* @property {Number} None - 0
* @property {Number} Error - 1
* @property {Number} Success - 2
* @property {Number} Warning - 3
* @property {Number} Info - 4
*/
/**
* Logger with level and logger function specification
* @param {Number} [level=LogLevel.Info] - level specified in LogLevel object
* @param {String} content - log content
* @param {String} [logger=log] - which log function to use (in window.console[logger])
*/
const [LogLevel, DoLog] = (function() {
const LogLevel = {
None: 0,
Error: 1,
Success: 2,
Warning: 3,
Info: 4,
};
return [LogLevel, DoLog];
function DoLog() {
// Get window
const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window;
const LogLevelMap = {};
LogLevelMap[LogLevel.None] = {
prefix: '',
color: 'color:#ffffff'
}
LogLevelMap[LogLevel.Error] = {
prefix: '[Error]',
color: 'color:#ff0000'
}
LogLevelMap[LogLevel.Success] = {
prefix: '[Success]',
color: 'color:#00aa00'
}
LogLevelMap[LogLevel.Warning] = {
prefix: '[Warning]',
color: 'color:#ffa500'
}
LogLevelMap[LogLevel.Info] = {
prefix: '[Info]',
color: 'color:#888888'
}
LogLevelMap[LogLevel.Elements] = {
prefix: '[Elements]',
color: 'color:#000000'
}
// Current log level
DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
// Log counter
DoLog.logCount === undefined && (DoLog.logCount = 0);
// Get args
let [level, logContent, logger] = parseArgs([...arguments], [
[2],
[1,2],
[1,2,3]
], [LogLevel.Info, 'DoLog initialized.', 'log']);
let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : '');
let subst = LogLevelMap[level].color;
switch (typeof(logContent)) {
case 'string':
msg += '%s';
break;
case 'number':
msg += '%d';
break;
default:
msg += '%o';
break;
}
// Log when log level permits
if (level <= DoLog.logLevel) {
// Log to console when log level permits
if (level <= DoLog.logLevel) {
if (++DoLog.logCount > 512) {
console.clear();
DoLog.logCount = 0;
}
console[logger](msg, subst, logContent);
}
}
}
}) ();
// type: [Error, TypeError]
/**
* @typedef {Number} ErrorType
*
*/
/**
* Throw an error
* @param {String} msg - the error message
* @param {ErrorType} [type=0] - error type, which also means the Error constructor
*/
function Err(msg, type=0) {
throw new [Error, TypeError][type]((typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + msg);
}
function Assert(val, errmsg, errtype) {
val || Err(errmsg, errtype);
}
// Basic functions
// querySelector
function $() {
switch(arguments.length) {
case 2:
return arguments[0].querySelector(arguments[1]);
break;
default:
return document.querySelector(arguments[0]);
}
}
// querySelectorAll
function $All() {
switch(arguments.length) {
case 2:
return arguments[0].querySelectorAll(arguments[1]);
break;
default:
return document.querySelectorAll(arguments[0]);
}
}
// createElement
function $CrE() {
switch(arguments.length) {
case 2:
return arguments[0].createElement(arguments[1]);
break;
default:
return document.createElement(arguments[0]);
}
}
// addEventListener
function $AEL(...args) {
const target = args.shift();
return target.addEventListener.apply(target, args);
}
function $$CrE() {
const [tagName, props, attrs, classes, styles, listeners] = parseArgs([...arguments], [
function(args, defaultValues) {
const arg = args[0];
return {
'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
'object': () => ['tagName', 'props', 'attrs', 'classes', 'styles', 'listeners'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
}[typeof arg]();
},
[1,2],
[1,2,3],
[1,2,3,4],
[1,2,3,4,5]
], ['div', {}, {}, [], {}, []]);
const elm = $CrE(tagName);
for (const [name, val] of Object.entries(props)) {
elm[name] = val;
}
for (const [name, val] of Object.entries(attrs)) {
elm.setAttribute(name, val);
}
for (const cls of Array.isArray(classes) ? classes : [classes]) {
elm.classList.add(cls);
}
for (const [name, val] of Object.entries(styles)) {
elm.style[name] = val;
}
for (const listener of listeners) {
$AEL(...[elm, ...listener]);
}
return elm;
}
// Append a style text to document(<head>) with a <style> element
// arguments: css | css, id | parentElement, css, id
// remove old one when id duplicates with another element in document
function addStyle() {
// Get arguments
const [parentElement, css, id] = parseArgs([...arguments], [
[2],
[2,3],
[1,2,3]
], [document.head, '', null]);
// Make <style>
const style = $CrE("style");
style.textContent = css;
id !== null && (style.id = id);
id !== null && $(`#${id}`) && $(`#${id}`).remove();
// Append to parentElement
parentElement.appendChild(style);
return style;
}
// Get callback when specific dom/element loaded
// detectDom({[root], selector, callback}) | detectDom(selector, callback) | detectDom(root, selector, callback) | detectDom(root, selector, callback, attributes)
// Supports both callback for multiple detection, and promise for one-time detection.
// By default promise mode is preferred, meaning `callback` argument should be provided explicitly when using callback
// mode (by adding `callback` property in details object, or provide all 4 arguments where callback should be the last)
// This behavior is different from versions that equals to or older than 0.8.4.2, so be careful when using it.
function detectDom() {
let [selectors, root, attributes, callback] = parseArgs([...arguments], [
function(args, defaultValues) {
const arg = args[0];
return {
'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
'object': () => ['selector', 'root', 'attributes', 'callback'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
}[typeof arg]();
},
[2,1],
[2,1,3],
[2,1,3,4],
], [[''], document, false, null]);
!Array.isArray(selectors) && (selectors = [selectors]);
if (select(root, selectors)) {
for (const elm of selectAll(root, selectors)) {
if (callback) {
setTimeout(callback.bind(null, elm));
} else {
return Promise.resolve(elm);
}
}
}
const observer = new MutationObserver(mCallback);
observer.observe(root, {
childList: true,
subtree: true,
attributes,
});
let isPromise = !callback;
return callback ? observer : new Promise((resolve, reject) => callback = resolve);
function mCallback(mutationList, observer) {
const addedNodes = mutationList.reduce((an, mutation) => {
switch (mutation.type) {
case 'childList':
an.push(...mutation.addedNodes);
break;
case 'attributes':
an.push(mutation.target);
break;
}
return an;
}, []);
const addedSelectorNodes = addedNodes.reduce((nodes, anode) => {
if (anode.matches && match(anode, selectors)) {
nodes.add(anode);
}
const childMatches = anode.querySelectorAll ? selectAll(anode, selectors) : [];
for (const cm of childMatches) {
nodes.add(cm);
}
return nodes;
}, new Set());
for (const node of addedSelectorNodes) {
callback(node);
isPromise && observer.disconnect();
}
}
function selectAll(elm, selectors) {
!Array.isArray(selectors) && (selectors = [selectors]);
return selectors.map(selector => [...$All(elm, selector)]).reduce((all, arr) => {
all.push(...arr);
return all;
}, []);
}
function select(elm, selectors) {
const all = selectAll(elm, selectors);
return all.length ? all[0] : null;
}
function match(elm, selectors) {
return !!elm.matches && selectors.some(selector => elm.matches(selector));
}
}
// Just stopPropagation and preventDefault
function destroyEvent(e) {
if (!e) {return false;};
if (!e instanceof Event) {return false;};
e.stopPropagation();
e.preventDefault();
}
// Object1[prop] ==> Object2[prop]
function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);}
function copyProps(obj1, obj2, props) {(props || Object.keys(obj1)).forEach((prop) => (copyProp(obj1, obj2, prop)));}
// Argument parser with sorting and defaultValue support
function parseArgs(args, rules, defaultValues=[]) {
// args and rules should be array, but not just iterable (string is also iterable)
if (!Array.isArray(args) || !Array.isArray(rules)) {
throw new TypeError('parseArgs: args and rules should be array')
}
// fill rules[0]
(!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
// max arguments length
const count = rules.length - 1;
// args.length must <= count
if (args.length > count) {
throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
}
// rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
for (let i = 1; i <= count; i++) {
const rule = rules[i];
if (Array.isArray(rule)) {
if (rule.length !== i) {
throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
}
if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
}
} else if (typeof rule !== 'function') {
throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
}
}
// Parse
const rule = rules[args.length];
let parsed;
if (Array.isArray(rule)) {
parsed = [...defaultValues];
for (let i = 0; i < rule.length; i++) {
parsed[rule[i]-1] = args[i];
}
} else {
parsed = rule(args, defaultValues);
}
return parsed;
}
// escape str into javascript written format
function escJsStr(str, quote='"') {
str = str.replaceAll('\\', '\\\\').replaceAll(quote, '\\' + quote).replaceAll('\t', '\\t');
str = quote === '`' ? str.replaceAll(/(\$\{[^\}]*\})/g, '\\$1') : str.replaceAll('\r', '\\r').replaceAll('\n', '\\n');
return quote + str + quote;
}
// Replace model text with no mismatching of replacing replaced text
// e.g. replaceText('aaaabbbbccccdddd', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'}) === 'bbbbccccddddeeee'
// replaceText('abcdAABBAA', {'BB': 'AA', 'AAAAAA': 'This is a trap!'}) === 'abcdAAAAAA'
// replaceText('abcd{AAAA}BB}', {'{AAAA}': '{BB', '{BBBB}': 'This is a trap!'}) === 'abcd{BBBB}'
// replaceText('abcd', {}) === 'abcd'
/* Note:
replaceText will replace in sort of replacer's iterating sort
e.g. currently replaceText('abcdAABBAA', {'BBAA': 'TEXT', 'AABB': 'TEXT'}) === 'abcdAATEXT'
but remember: (As MDN Web Doc said,) Although the keys of an ordinary Object are ordered now, this was
not always the case, and the order is complex. As a result, it's best not to rely on property order.
So, don't expect replaceText will treat replacer key-values in any specific sort. Use replaceText to
replace irrelevance replacer keys only.
*/
function replaceText(text, replacer) {
if (Object.entries(replacer).length === 0) {return text;}
const [models, targets] = Object.entries(replacer);
const len = models.length;
let text_arr = [{text: text, replacable: true}];
for (const [model, target] of Object.entries(replacer)) {
text_arr = replace(text_arr, model, target);
}
return text_arr.map((text_obj) => (text_obj.text)).join('');
function replace(text_arr, model, target) {
const result_arr = [];
for (const text_obj of text_arr) {
if (text_obj.replacable) {
const splited = text_obj.text.split(model);
for (const part of splited) {
result_arr.push({text: part, replacable: true});
result_arr.push({text: target, replacable: false});
}
result_arr.pop();
} else {
result_arr.push(text_obj);
}
}
return result_arr;
}
}
// Get a url argument from location.href
// also recieve a function to deal the matched string
// returns defaultValue if name not found
// Args: {name, url=location.href, defaultValue=null, dealFunc=((a)=>{return a;})} or (name) or (url, name) or (url, name, defaultValue) or (url, name, defaultValue, dealFunc)
function getUrlArgv(details) {
const [name, url, defaultValue, dealFunc] = parseArgs([...arguments], [
function(args, defaultValues) {
const arg = args[0];
return {
'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
'object': () => ['name', 'url', 'defaultValue', 'dealFunc'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
}[typeof arg]();
},
[2,1],
[2,1,3],
[2,1,3,4]
], [null, location.href, null, a => a]);
if (name === null) { return null; }
const search = new URL(url).search;
const objSearch = new URLSearchParams(search);
const raw = objSearch.has(name) ? objSearch.get(name) : defaultValue;
const argv = dealFunc(raw);
return argv;
}
// Save dataURL to file
function dl_browser(dataURL, filename) {
const a = document.createElement('a');
a.href = dataURL;
a.download = filename;
a.click();
}
// File download function
// details looks like the detail of GM_xmlhttpRequest
// onload function will be called after file saved to disk
function dl_GM(details) {
if (!details.url || !details.name) {return false;};
// Configure request object
const requestObj = {
url: details.url,
responseType: 'blob',
onload: function(e) {
// Save file
dl_browser(URL.createObjectURL(e.response), details.name);
// onload callback
details.onload ? details.onload(e) : function() {};
}
}
if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
if (details.onerror ) {requestObj.onerror = details.onerror;};
if (details.onabort ) {requestObj.onabort = details.onabort;};
if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
// Send request
GM_xmlhttpRequest(requestObj);
}
function AsyncManager() {
const AM = this;
// Ongoing tasks count
this.taskCount = 0;
// Whether generate finish events
let finishEvent = false;
Object.defineProperty(this, 'finishEvent', {
configurable: true,
enumerable: true,
get: () => (finishEvent),
set: (b) => {
finishEvent = b;
b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
}
});
// Add one task
this.add = () => (++AM.taskCount);
// Finish one task
this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
}
function queueTask(task, queueId='default') {
init();
return new Promise((resolve, reject) => {
queueTask.hasOwnProperty(queueId) || (queueTask[queueId] = { tasks: [], ongoing: 0 });
queueTask[queueId].tasks.push({task, resolve, reject});
checkTask(queueId);
});
function init() {
if (!queueTask[queueId]?.initialized) {
queueTask[queueId] = {
// defaults
tasks: [],
ongoing: 0,
max: 3,
sleep: 500,
// user's pre-sets
...(queueTask[queueId] || {}),
// initialized flag
initialized: true
}
};
}
function checkTask() {
const queue = queueTask[queueId];
setTimeout(() => {
if (queue.ongoing < queue.max && queue.tasks.length) {
const task = queue.tasks.shift();
queue.ongoing++;
setTimeout(
() => task.task().then(v => {
queue.ongoing--;
task.resolve(v);
checkTask(queueId);
}).catch(e => {
queue.ongoing--;
task.reject(e);
checkTask(queueId);
}),
queue.sleep
);
}
});
}
}
const [FunctionLoader, loadFuncs, require, isLoaded] = (function() {
/**
* 一般用作函数对象oFunc的加载条件,检测当前环境是否适合/需要该oFunc加载
* @typedef {Object} checker_func
* @property {string} type - checker's identifier
* @property {function} func - actual internal judgement implementation
*/
/**
* 一般用作函数对象oFunc的加载条件,检测当前环境是否适合/需要该oFunc加载
* @typedef {Object} checker
* @property {string} type - checker's identifier
* @property {*} value - param that goes into checker function
*/
/**
* 被加载函数对象的func函数
* @callback oFuncBody
* @param {oFunc} oFunc
* @returns {*|Promise<*>}
*/
/**
* 被加载执行的函数对象
* @typedef {Object} oFunc
* @property {string} id - 每次load(每个FuncPool实例)内唯一的标识符
* @property {checker[]|checker} [checkers] - oFunc执行的条件
* @property {string[]|string} [detectDom] - 如果提供,开始checker检查前会首先等待其中所有css选择器对应的元素在document中出现
* @property {string[]|string} [dependencies] - 如果提供,应为其他函数对象的id或者id列表;开始checker检查前会首先等待其中所有指定的函数对象加载完毕
* @property {boolean} [readonly] - 指定该函数的返回值是否应该被Proxy保护为不可修改对象
* @property {oFuncBody} func - 实际实现了功能的函数
* @property {boolean} [STOP] - [调试用] 指定不执行此函数对象
*/
const registered_checkers = {
switch: value => value,
url: value => location.href === value,
path: value => location.pathname === value,
regurl: value => !!location.href.match(value),
regpath: value => !!location.pathname.match(value),
starturl: value => location.href.startsWith(value),
startpath: value => location.pathname.startsWith(value),
func: value => value()
};
class FuncPool extends EventTarget {
static #STILL_LOADING = Symbol('oFunc still loading');
static FunctionNotFound = Symbol('Function not found');
static FunctionNotLoaded = Symbol('Function not loaded');
/** @typedef {symbol|*} return_value */
/** @type {Map<oFunc, return_value>} */
#oFuncs = new Map();
/**
* 创建新函数池,并加载提供的函数对象
* @param {oFunc[]|oFunc} [oFuncs] - 可选,需要加载的函数对象或其数组,不提供时默认为空数组
* @returns {FuncPool}
*/
constructor(oFuncs=[]) {
super();
this.load(oFuncs);
}
/**
* 加载提供的一个或多个函数对象,并将其加入到函数池中
* @param {oFunc[]|oFunc} [oFuncs] - 可选,需要加载的函数对象或其数组,不提供时默认为空数组
* @returns {undefined}
*/
load(oFuncs=[]) {
oFuncs = Array.isArray(oFuncs) ? oFuncs : [oFuncs];
for (const oFunc of oFuncs) {
this.#load(oFunc);
}
}
/**
* 加载一个函数对象,并将其加入到函数池中
* 当id重复时,直接报错RedeclarationError
* 异步函数,当彻底load完毕/checkers确定不加载时resolve
* 当加载完毕时,广播load事件;如果全部加载完毕,还广播all_load事件
* @param {oFunc} oFunc
* @returns {Promise<undefined>}
*/
async #load(oFunc) {
const that = this;
// 已经在函数池中的函数对象,不重复load
if (this.#oFuncs.has(oFunc)) {
return;
}
// 检查有无重复id
for (const o of this.#oFuncs.keys()) {
if (o.id === oFunc.id) {
throw new RedeclarationError(`Attempts to load oFunc with id already in use: ${oFunc.id}`);
}
}
// 设置当前返回值为STILL_LOADING
this.#oFuncs.set(oFunc, FuncPool.#STILL_LOADING);
// 加载依赖
const dependencies = Array.isArray(oFunc.dependencies) ? oFunc.dependencies : ( oFunc.dependencies ? [oFunc.dependencies] : [] );
const promise_deps = Promise.all(dependencies.map(id => new Promise((resolve, reject) => {
$AEL(that, 'load', e => e.detail.oFunc.id === id && resolve());
})));
// 检测detectDOM中css选择器指定的元素出现
const selectors = Array.isArray(oFunc.detectDom) ? oFunc.detectDom : ( oFunc.detectDom ? [oFunc.detectDom] : [] );
const promise_css = Promise.all(selectors.map(selector => detectDom(selector)));
// 等待上述两项完成
await Promise.all([promise_deps, promise_css]);
// 检测checkers加载条件
const checkers = Array.isArray(oFunc.checkers) ? oFunc.checkers : ( oFunc.checkers ? [oFunc.checkers] : [] );
if (!testCheckers(checkers)) {
return;
}
// 执行函数对象
const raw_return_value = oFunc.func(oFunc);
const return_value = await Promise.resolve(raw_return_value);
// 设置返回值
this.#oFuncs.set(oFunc, return_value);
// 广播事件
this.dispatchEvent(new CustomEvent('load', {
detail: {
oFunc, id: oFunc.id, return_value
}
}));
Array.from(this.#oFuncs.values()).every(v => v !== FuncPool.#STILL_LOADING) &&
this.dispatchEvent(new CustomEvent('all_load', {}));
}
/**
* 获取指定函数对象的返回值
* 如果指定的函数对象不存在,返回FunctionNotFound
* 如果指定的函数对象存在但尚未加载,返回FunctionNotLoaded
* 如果函数对象指定了readonly为真值,则返回前用Proxy包装返回值,使其不可修改
* @param {string} id - 函数对象的id
* @returns {*}
*/
require(id) {
for (const [oFunc, return_value] of this.#oFuncs.entries()) {
if (oFunc.id === id) {
if (return_value === FuncPool.#STILL_LOADING) {
return FuncPool.FunctionNotLoaded;
} else {
return oFunc.readonly ? FuncPool.#MakeReadonlyObj(return_value) : return_value;
}
}
}
return FuncPool.FunctionNotFound;
}
isLoaded(id) {
for (const [oFunc, return_value] of this.#oFuncs.entries()) {
if (oFunc.id === id) {
if (return_value === FuncPool.#STILL_LOADING) {
return false;
} else {
return true;
}
}
return false;
}
}
/**
* 以Proxy包装value,使其属性只读
* 如果传入的不是obj,则直接返回value
* @param {Object} val
* @returns {Proxy}
*/
static #MakeReadonlyObj(val) {
return isObject(val) ? new Proxy(val, {
get: function(target, property, receiver) {
return FuncPool.#MakeReadonlyObj(target[property]);
},
set: function(target, property, value, receiver) {},
has: function(target, prop) {},
setPrototypeOf(target, newProto) {
return false;
},
defineProperty(target, property, descriptor) {
return true;
},
deleteProperty(target, property) {
return false;
},
preventExtensions(target) {
return false;
}
}) : val;
function isObject(value) {
return ['object', 'function'].includes(typeof value) && value !== null;
}
}
}
class RedeclarationError extends TypeError {}
class CircularDependencyError extends ReferenceError {}
// 预置的函数池
const default_pool = new FuncPool();
/**
* 在预置的函数池中加载函数对象或其数组
* @param {oFunc[]|oFunc} oFuncs - 需要执行的函数对象
* @returns {FuncPool}
*/
function loadFuncs(oFuncs) {
default_pool.load(oFuncs);
return default_pool;
}
/**
* 在预置的函数池中获取函数对象的返回值
* @param {string} id - 函数对象的字符串id
* @returns {*}
*/
function require(id) {
return default_pool.require(id);
}
/**
* 在预置的函数池中检查指定函数对象是否已经加载完毕(有返回值可用)
* @param {string} id - 函数对象的字符串id
* @returns {boolean}
*/
function isLoaded(id) {
return default_pool.isLoaded(id);
}
/**
* 测试给定checker是否检测通过
* 给定多个checker时,checkers之间是 或 关系,有一个checker通过即算作整体通过
* 注意此函数设计和旧版testChecker的设计不同,旧版中一个checker可以有多个值,还可通过checker.all指定多值之间的关系为 与 还是 或
* @param {checker[]|checker} [checkers] - 需要检测的checkers
* @returns {boolean}
*/
function testCheckers(checkers=[]) {
checkers = Array.isArray(checkers) ? checkers : [checkers];
return checkers.length === 0 || checkers.some(checker => !!registered_checkers[checker.type]?.(checker.value));
}
/**
* 注册新checker
* 如果给定type已经被其他checker占用,则会报错RedeclarationError
* @param {string} type - checker类名
* @param {function} func - checker implementation
* @returns {undefined}
*/
function registerChecker(type, func) {
if (registered_checkers.hasOwnProperty(type)) {
throw RedeclarationError(`Attempts to register checker with type already in use: ${type}`);
}
registered_checkers[type] = func;
}
return [{
FuncPool,
testCheckers,
registerChecker,
get checkers() {
return Object.assign({}, registered_checkers);
},
Error: {
RedeclarationError,
CircularDependencyError
}
}, loadFuncs, require, isLoaded];
}) ();
return [
// Console & Debug
LogLevel, DoLog, Err, Assert,
// DOM
$, $All, $CrE, $AEL, $$CrE, addStyle, detectDom, destroyEvent,
// Data
copyProp, copyProps, parseArgs, escJsStr, replaceText,
// Environment & Browser
getUrlArgv, dl_browser, dl_GM,
// Logic & Task
AsyncManager, queueTask, FunctionLoader, loadFuncs, require
];
})();