Basic Functions (For userscripts)

Useful functions for myself

目前為 2024-09-08 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/456034/1443751/Basic%20Functions%20%28For%20userscripts%29.js

  1. /* eslint-disable no-multi-spaces */
  2. /* eslint-disable no-return-assign */
  3.  
  4. // ==UserScript==
  5. // @name Basic Functions (For userscripts)
  6. // @name:zh-CN 常用函数(用户脚本)
  7. // @name:en Basic Functions (For userscripts)
  8. // @namespace PY-DNG Userscripts
  9. // @version 0.8.12
  10. // @description Useful functions for myself
  11. // @description:zh-CN 自用函数
  12. // @description:en Useful functions for myself
  13. // @author PY-DNG
  14. // @license GPL-3.0-or-later
  15. // ==/UserScript==
  16.  
  17. // Note: version 0.8.2.1 is modified just the license and it's not uploaded to GF yet 23-11-26 15:03
  18. // 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
  19.  
  20. let [
  21. // Console & Debug
  22. LogLevel, DoLog, Err, Assert,
  23.  
  24. // DOM
  25. $, $All, $CrE, $AEL, $$CrE, addStyle, detectDom, destroyEvent,
  26.  
  27. // Data
  28. copyProp, copyProps, parseArgs, escJsStr, replaceText,
  29.  
  30. // Environment & Browser
  31. getUrlArgv, dl_browser, dl_GM,
  32.  
  33. // Logic & Task
  34. AsyncManager, queueTask, testChecker, registerChecker, loadFuncs
  35. ] = (function() {
  36. /**
  37. * level defination for DoLog function, bigger ones has higher possibility to be printed in console
  38. * @property {Number} None - 0
  39. * @property {Number} Error - 1
  40. * @property {Number} Success - 2
  41. * @property {Number} Warning - 3
  42. * @property {Number} Info - 4
  43. */
  44. /**
  45. * Logger with level and logger function specification
  46. * @param {Number} [level=LogLevel.Info] - level specified in LogLevel object
  47. * @param {String} content - log content
  48. * @param {String} [logger=log] - which log function to use (in window.console[logger])
  49. */
  50. const [LogLevel, DoLog] = (function() {
  51. const LogLevel = {
  52. None: 0,
  53. Error: 1,
  54. Success: 2,
  55. Warning: 3,
  56. Info: 4,
  57. };
  58.  
  59. return [LogLevel, DoLog];
  60. function DoLog() {
  61. // Get window
  62. const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window;
  63.  
  64. const LogLevelMap = {};
  65. LogLevelMap[LogLevel.None] = {
  66. prefix: '',
  67. color: 'color:#ffffff'
  68. }
  69. LogLevelMap[LogLevel.Error] = {
  70. prefix: '[Error]',
  71. color: 'color:#ff0000'
  72. }
  73. LogLevelMap[LogLevel.Success] = {
  74. prefix: '[Success]',
  75. color: 'color:#00aa00'
  76. }
  77. LogLevelMap[LogLevel.Warning] = {
  78. prefix: '[Warning]',
  79. color: 'color:#ffa500'
  80. }
  81. LogLevelMap[LogLevel.Info] = {
  82. prefix: '[Info]',
  83. color: 'color:#888888'
  84. }
  85. LogLevelMap[LogLevel.Elements] = {
  86. prefix: '[Elements]',
  87. color: 'color:#000000'
  88. }
  89.  
  90. // Current log level
  91. DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  92.  
  93. // Log counter
  94. DoLog.logCount === undefined && (DoLog.logCount = 0);
  95.  
  96. // Get args
  97. let [level, logContent, logger] = parseArgs([...arguments], [
  98. [2],
  99. [1,2],
  100. [1,2,3]
  101. ], [LogLevel.Info, 'DoLog initialized.', 'log']);
  102.  
  103. let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : '');
  104. let subst = LogLevelMap[level].color;
  105.  
  106. switch (typeof(logContent)) {
  107. case 'string':
  108. msg += '%s';
  109. break;
  110. case 'number':
  111. msg += '%d';
  112. break;
  113. default:
  114. msg += '%o';
  115. break;
  116. }
  117.  
  118. // Log when log level permits
  119. if (level <= DoLog.logLevel) {
  120. // Log to console when log level permits
  121. if (level <= DoLog.logLevel) {
  122. if (++DoLog.logCount > 512) {
  123. console.clear();
  124. DoLog.logCount = 0;
  125. }
  126. console[logger](msg, subst, logContent);
  127. }
  128. }
  129. }
  130. }) ();
  131.  
  132. // type: [Error, TypeError]
  133. /**
  134. * @typedef {Number} ErrorType
  135. *
  136. */
  137. /**
  138. * Throw an error
  139. * @param {String} msg - the error message
  140. * @param {ErrorType} [type=0] - error type, which also means the Error constructor
  141. */
  142. function Err(msg, type=0) {
  143. throw new [Error, TypeError][type]((typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + msg);
  144. }
  145.  
  146. function Assert(val, errmsg, errtype) {
  147. val || Err(errmsg, errtype);
  148. }
  149.  
  150. // Basic functions
  151. // querySelector
  152. function $() {
  153. switch(arguments.length) {
  154. case 2:
  155. return arguments[0].querySelector(arguments[1]);
  156. break;
  157. default:
  158. return document.querySelector(arguments[0]);
  159. }
  160. }
  161. // querySelectorAll
  162. function $All() {
  163. switch(arguments.length) {
  164. case 2:
  165. return arguments[0].querySelectorAll(arguments[1]);
  166. break;
  167. default:
  168. return document.querySelectorAll(arguments[0]);
  169. }
  170. }
  171. // createElement
  172. function $CrE() {
  173. switch(arguments.length) {
  174. case 2:
  175. return arguments[0].createElement(arguments[1]);
  176. break;
  177. default:
  178. return document.createElement(arguments[0]);
  179. }
  180. }
  181. // addEventListener
  182. function $AEL(...args) {
  183. const target = args.shift();
  184. return target.addEventListener.apply(target, args);
  185. }
  186. function $$CrE() {
  187. const [tagName, props, attrs, classes, styles, listeners] = parseArgs([...arguments], [
  188. function(args, defaultValues) {
  189. const arg = args[0];
  190. return {
  191. 'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
  192. 'object': () => ['tagName', 'props', 'attrs', 'classes', 'styles', 'listeners'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
  193. }[typeof arg]();
  194. },
  195. [1,2],
  196. [1,2,3],
  197. [1,2,3,4],
  198. [1,2,3,4,5]
  199. ], ['div', {}, {}, [], {}, []]);
  200. const elm = $CrE(tagName);
  201. for (const [name, val] of Object.entries(props)) {
  202. elm[name] = val;
  203. }
  204. for (const [name, val] of Object.entries(attrs)) {
  205. elm.setAttribute(name, val);
  206. }
  207. for (const cls of Array.isArray(classes) ? classes : [classes]) {
  208. elm.classList.add(cls);
  209. }
  210. for (const [name, val] of Object.entries(styles)) {
  211. elm.style[name] = val;
  212. }
  213. for (const listener of listeners) {
  214. $AEL(...[elm, ...listener]);
  215. }
  216. return elm;
  217. }
  218.  
  219. // Append a style text to document(<head>) with a <style> element
  220. // arguments: css | css, id | parentElement, css, id
  221. // remove old one when id duplicates with another element in document
  222. function addStyle() {
  223. // Get arguments
  224. const [parentElement, css, id] = parseArgs([...arguments], [
  225. [2],
  226. [2,3],
  227. [1,2,3]
  228. ], [document.head, '', null]);
  229.  
  230. // Make <style>
  231. const style = $CrE("style");
  232. style.textContent = css;
  233. id !== null && (style.id = id);
  234. id !== null && $(`#${id}`) && $(`#${id}`).remove();
  235.  
  236. // Append to parentElement
  237. parentElement.appendChild(style);
  238. return style;
  239. }
  240.  
  241. // Get callback when specific dom/element loaded
  242. // detectDom({[root], selector, callback[, once]}) | detectDom(selector, callback) | detectDom(root, selector, callback) | detectDom(root, selector, callback, attributes) | detectDom(root, selector, callback, attributes, once)
  243. // Supports both callback for multiple detection, and promise for one-time detection.
  244. // By default promise mode is preferred, meaning `callback` argument should be provided explicitly when using callback
  245. // mode (by adding `callback` property in details object, or provide all 4 arguments where callback should be the last)
  246. // This behavior is different from versions that equals to or older than 0.8.4.2, so be careful when using it.
  247. function detectDom() {
  248. let [selectors, root, attributes, callback] = parseArgs([...arguments], [
  249. function(args, defaultValues) {
  250. const arg = args[0];
  251. return {
  252. 'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
  253. 'object': () => ['selector', 'root', 'attributes', 'callback'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
  254. }[typeof arg]();
  255. },
  256. [2,1],
  257. [2,1,3],
  258. [2,1,3,4],
  259. ], [[''], document, false, null]);
  260. !Array.isArray(selectors) && (selectors = [selectors]);
  261.  
  262. if (select(root, selectors)) {
  263. for (const elm of selectAll(root, selectors)) {
  264. if (callback) {
  265. callback(elm);
  266. } else {
  267. return Promise.resolve(elm);
  268. }
  269. }
  270. }
  271.  
  272. const observer = new MutationObserver(mCallback);
  273. observer.observe(root, {
  274. childList: true,
  275. subtree: true,
  276. attributes,
  277. });
  278.  
  279. let isPromise = !callback;
  280. return callback ? observer : new Promise((resolve, reject) => callback = resolve);
  281.  
  282. function mCallback(mutationList, observer) {
  283. const addedNodes = mutationList.reduce((an, mutation) => {
  284. switch (mutation.type) {
  285. case 'childList':
  286. an.push(...mutation.addedNodes);
  287. break;
  288. case 'attributes':
  289. an.push(mutation.target);
  290. break;
  291. }
  292. return an;
  293. }, []);
  294. const addedSelectorNodes = addedNodes.reduce((nodes, anode) => {
  295. if (anode.matches && match(anode, selectors)) {
  296. nodes.add(anode);
  297. }
  298. const childMatches = anode.querySelectorAll ? selectAll(anode, selectors) : [];
  299. for (const cm of childMatches) {
  300. nodes.add(cm);
  301. }
  302. return nodes;
  303. }, new Set());
  304. for (const node of addedSelectorNodes) {
  305. callback(node);
  306. isPromise && observer.disconnect();
  307. }
  308. }
  309.  
  310. function selectAll(elm, selectors) {
  311. !Array.isArray(selectors) && (selectors = [selectors]);
  312. return selectors.map(selector => [...$All(elm, selector)]).reduce((all, arr) => {
  313. all.push(...arr);
  314. return all;
  315. }, []);
  316. }
  317.  
  318. function select(elm, selectors) {
  319. const all = selectAll(elm, selectors);
  320. return all.length ? all[0] : null;
  321. }
  322.  
  323. function match(elm, selectors) {
  324. return !!elm.matches && selectors.some(selector => elm.matches(selector));
  325. }
  326. }
  327.  
  328. // Just stopPropagation and preventDefault
  329. function destroyEvent(e) {
  330. if (!e) {return false;};
  331. if (!e instanceof Event) {return false;};
  332. e.stopPropagation();
  333. e.preventDefault();
  334. }
  335.  
  336. // Object1[prop] ==> Object2[prop]
  337. function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);}
  338. function copyProps(obj1, obj2, props) {(props || Object.keys(obj1)).forEach((prop) => (copyProp(obj1, obj2, prop)));}
  339.  
  340. // Argument parser with sorting and defaultValue support
  341. function parseArgs(args, rules, defaultValues=[]) {
  342. // args and rules should be array, but not just iterable (string is also iterable)
  343. if (!Array.isArray(args) || !Array.isArray(rules)) {
  344. throw new TypeError('parseArgs: args and rules should be array')
  345. }
  346.  
  347. // fill rules[0]
  348. (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
  349.  
  350. // max arguments length
  351. const count = rules.length - 1;
  352.  
  353. // args.length must <= count
  354. if (args.length > count) {
  355. throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
  356. }
  357.  
  358. // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
  359. for (let i = 1; i <= count; i++) {
  360. const rule = rules[i];
  361. if (Array.isArray(rule)) {
  362. if (rule.length !== i) {
  363. throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
  364. }
  365. if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
  366. throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
  367. }
  368. } else if (typeof rule !== 'function') {
  369. throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
  370. }
  371. }
  372.  
  373. // Parse
  374. const rule = rules[args.length];
  375. let parsed;
  376. if (Array.isArray(rule)) {
  377. parsed = [...defaultValues];
  378. for (let i = 0; i < rule.length; i++) {
  379. parsed[rule[i]-1] = args[i];
  380. }
  381. } else {
  382. parsed = rule(args, defaultValues);
  383. }
  384. return parsed;
  385. }
  386.  
  387. // escape str into javascript written format
  388. function escJsStr(str, quote='"') {
  389. str = str.replaceAll('\\', '\\\\').replaceAll(quote, '\\' + quote).replaceAll('\t', '\\t');
  390. str = quote === '`' ? str.replaceAll(/(\$\{[^\}]*\})/g, '\\$1') : str.replaceAll('\r', '\\r').replaceAll('\n', '\\n');
  391. return quote + str + quote;
  392. }
  393.  
  394. // Replace model text with no mismatching of replacing replaced text
  395. // e.g. replaceText('aaaabbbbccccdddd', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'}) === 'bbbbccccddddeeee'
  396. // replaceText('abcdAABBAA', {'BB': 'AA', 'AAAAAA': 'This is a trap!'}) === 'abcdAAAAAA'
  397. // replaceText('abcd{AAAA}BB}', {'{AAAA}': '{BB', '{BBBB}': 'This is a trap!'}) === 'abcd{BBBB}'
  398. // replaceText('abcd', {}) === 'abcd'
  399. /* Note:
  400. replaceText will replace in sort of replacer's iterating sort
  401. e.g. currently replaceText('abcdAABBAA', {'BBAA': 'TEXT', 'AABB': 'TEXT'}) === 'abcdAATEXT'
  402. but remember: (As MDN Web Doc said,) Although the keys of an ordinary Object are ordered now, this was
  403. not always the case, and the order is complex. As a result, it's best not to rely on property order.
  404. So, don't expect replaceText will treat replacer key-values in any specific sort. Use replaceText to
  405. replace irrelevance replacer keys only.
  406. */
  407. function replaceText(text, replacer) {
  408. if (Object.entries(replacer).length === 0) {return text;}
  409. const [models, targets] = Object.entries(replacer);
  410. const len = models.length;
  411. let text_arr = [{text: text, replacable: true}];
  412. for (const [model, target] of Object.entries(replacer)) {
  413. text_arr = replace(text_arr, model, target);
  414. }
  415. return text_arr.map((text_obj) => (text_obj.text)).join('');
  416.  
  417. function replace(text_arr, model, target) {
  418. const result_arr = [];
  419. for (const text_obj of text_arr) {
  420. if (text_obj.replacable) {
  421. const splited = text_obj.text.split(model);
  422. for (const part of splited) {
  423. result_arr.push({text: part, replacable: true});
  424. result_arr.push({text: target, replacable: false});
  425. }
  426. result_arr.pop();
  427. } else {
  428. result_arr.push(text_obj);
  429. }
  430. }
  431. return result_arr;
  432. }
  433. }
  434.  
  435. // Get a url argument from location.href
  436. // also recieve a function to deal the matched string
  437. // returns defaultValue if name not found
  438. // 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)
  439. function getUrlArgv(details) {
  440. const [name, url, defaultValue, dealFunc] = parseArgs([...arguments], [
  441. function(args, defaultValues) {
  442. const arg = args[0];
  443. return {
  444. 'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
  445. 'object': () => ['name', 'url', 'defaultValue', 'dealFunc'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
  446. }[typeof arg]();
  447. },
  448. [2,1],
  449. [2,1,3],
  450. [2,1,3,4]
  451. ], [null, location.href, null, a => a]);
  452.  
  453. if (name === null) { return null; }
  454.  
  455. const search = new URL(url).search;
  456. const objSearch = new URLSearchParams(search);
  457. const raw = objSearch.has(name) ? objSearch.get(name) : defaultValue;
  458. const argv = dealFunc(raw);
  459.  
  460. return argv;
  461. }
  462.  
  463. // Save dataURL to file
  464. function dl_browser(dataURL, filename) {
  465. const a = document.createElement('a');
  466. a.href = dataURL;
  467. a.download = filename;
  468. a.click();
  469. }
  470.  
  471. // File download function
  472. // details looks like the detail of GM_xmlhttpRequest
  473. // onload function will be called after file saved to disk
  474. function dl_GM(details) {
  475. if (!details.url || !details.name) {return false;};
  476.  
  477. // Configure request object
  478. const requestObj = {
  479. url: details.url,
  480. responseType: 'blob',
  481. onload: function(e) {
  482. // Save file
  483. dl_browser(URL.createObjectURL(e.response), details.name);
  484.  
  485. // onload callback
  486. details.onload ? details.onload(e) : function() {};
  487. }
  488. }
  489. if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
  490. if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
  491. if (details.onerror ) {requestObj.onerror = details.onerror;};
  492. if (details.onabort ) {requestObj.onabort = details.onabort;};
  493. if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
  494. if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
  495.  
  496. // Send request
  497. GM_xmlhttpRequest(requestObj);
  498. }
  499.  
  500. function AsyncManager() {
  501. const AM = this;
  502.  
  503. // Ongoing xhr count
  504. this.taskCount = 0;
  505.  
  506. // Whether generate finish events
  507. let finishEvent = false;
  508. Object.defineProperty(this, 'finishEvent', {
  509. configurable: true,
  510. enumerable: true,
  511. get: () => (finishEvent),
  512. set: (b) => {
  513. finishEvent = b;
  514. b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
  515. }
  516. });
  517.  
  518. // Add one task
  519. this.add = () => (++AM.taskCount);
  520.  
  521. // Finish one task
  522. this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
  523. }
  524.  
  525. function queueTask(task, queueId='default') {
  526. init();
  527.  
  528. return new Promise((resolve, reject) => {
  529. queueTask.hasOwnProperty(queueId) || (queueTask[queueId] = { tasks: [], ongoing: 0 });
  530. queueTask[queueId].tasks.push({task, resolve, reject});
  531. checkTask(queueId);
  532. });
  533.  
  534. function init() {
  535. if (!queueTask[queueId]?.initialized) {
  536. queueTask[queueId] = {
  537. // defaults
  538. tasks: [],
  539. ongoing: 0,
  540. max: 3,
  541. sleep: 500,
  542.  
  543. // user's pre-sets
  544. ...(queueTask[queueId] || {}),
  545.  
  546. // initialized flag
  547. initialized: true
  548. }
  549. };
  550. }
  551.  
  552. function checkTask() {
  553. const queue = queueTask[queueId];
  554. setTimeout(() => {
  555. if (queue.ongoing < queue.max && queue.tasks.length) {
  556. const task = queue.tasks.shift();
  557. queue.ongoing++;
  558. setTimeout(
  559. () => task.task().then(v => {
  560. queue.ongoing--;
  561. task.resolve(v);
  562. checkTask(queueId);
  563. }).catch(e => {
  564. queue.ongoing--;
  565. task.reject(e);
  566. checkTask(queueId);
  567. }),
  568. queue.sleep
  569. );
  570. }
  571. });
  572. }
  573. }
  574.  
  575. const [testChecker, registerChecker, loadFuncs] = (function() {
  576. const checkers = {
  577. switch: value => value,
  578. url: value => location.href === value,
  579. path: value => location.pathname === value,
  580. regurl: value => !!location.href.match(value),
  581. regpath: value => !!location.pathname.match(value),
  582. starturl: value => location.href.startsWith(value),
  583. startpath: value => location.pathname.startsWith(value),
  584. func: value => value()
  585. };
  586.  
  587. // Check whether current page url matches FuncInfo.checker rule
  588. // This code is copy and modified from FunctionLoader.check
  589. function testChecker(checker) {
  590. if (!checker) {return true;}
  591. const values = Array.isArray(checker.value) ? checker.value : [checker.value];
  592. return values[checker.all ? 'every' : 'some'](value => {
  593. const type = checker.type;
  594. if (checkers.hasOwnProperty(type)) {
  595. try {
  596. return checkers[type](value);
  597. } catch (err) {
  598. DoLog(LogLevel.Error, 'Checker function raised an error');
  599. DoLog(LogLevel.Error, err);
  600. return false;
  601. }
  602. } else {
  603. DoLog(LogLevel.Error, 'Invalid checker type');
  604. return false;
  605. }
  606. });
  607. }
  608.  
  609. function registerChecker(name, func) {
  610. Assert(['Symbol', 'string', 'number'].includes(typeof name), 'name should be symbol, string or number');
  611. Assert(typeof func === 'function', 'func should be a function');
  612. checkers[name] = func;
  613. }
  614.  
  615. // Load all function-objs provided in funcs asynchronously, get their return values in one obj
  616. // funcobj: {[id], [readonly], [checker], [detectDom], func}
  617. // Provide id for oFunc if you want to get its return value or want it to be a dependency for other oFuncs
  618. function loadFuncs(oFuncs) {
  619. // Load
  620. const loading_promises = new Map();
  621. const returnObj = {};
  622. oFuncs.forEach(oFunc => load(oFunc));
  623. return returnObj;
  624.  
  625. // Call do_load and store returned promise
  626. function load(oFunc, stack) {
  627. const promise = do_load(oFunc, stack)
  628. loading_promises.set(oFunc, promise);
  629. return promise;
  630. }
  631.  
  632. // Check availability and then execute
  633. async function do_load(oFunc, stack = []) {
  634. const getFunc = id => oFuncs.find(oFunc => oFunc.id == id);
  635.  
  636. // Prevent repeat loading
  637. if (oFunc.hasOwnProperty('id') && returnObj.hasOwnProperty(oFunc.id)) {
  638. // Already loaded
  639. return;
  640. }
  641. if (loading_promises.has(oFunc)) {
  642. // Still loading
  643. return await loading_promises.get(oFunc);
  644. }
  645. if (oFunc.hasOwnProperty('id') && stack.includes(oFunc.id)) {
  646. // Circular depending
  647. Err(`loadFuncs.load: loop dependencies: [${stack.join(' > ')}]`);
  648. }
  649.  
  650. // Test checker
  651. const checker = oFunc.checker;
  652. if (checker && !testChecker(checker)) { return; }
  653.  
  654. // Load dependencies
  655. let dps = oFunc.dependencies || [];
  656. if (!Array.isArray(dps)) { dps = [dps]; }
  657. await Promise.all(dps.map(
  658. dp => load(
  659. getFunc(dp),
  660. oFunc.hasOwnProperty('id') ? stack.concat(oFunc.id) : [...stack]
  661. )
  662. ));
  663.  
  664. // Execute function
  665. if (oFunc.detectDom) {
  666. const selectors = Array.isArray(oFunc.detectDom) ? oFunc.detectDom : [oFunc.detectDom];
  667. await Promise.all(selectors.map(selector => detectDom(selector))).then(async node => await execute(oFunc));
  668. } else {
  669. await execute(oFunc);
  670. }
  671. }
  672.  
  673. // Execute directly
  674. function execute(oFunc) {
  675. return new Promise((resolve, reject) => {
  676. setTimeout(async e => {
  677. const rval = isAsyncFunction(oFunc.func) ? await oFunc.func(returnObj) : oFunc.func(returnObj);
  678. typeof rval === 'object' && oFunc.id &&
  679. (returnObj[oFunc.id] = returnObj.readonly ? MakeReadonlyObj(rval) : rval);
  680. resolve();
  681. }, 0);
  682. });
  683.  
  684. function MakeReadonlyObj(val) {
  685. return isObject(val) ? new Proxy(val, {
  686. get: function(target, property, receiver) {
  687. return MakeReadonlyObj(target[property]);
  688. },
  689. set: function(target, property, value, receiver) {},
  690. has: function(target, prop) {}
  691. }) : val;
  692.  
  693. function isObject(value) {
  694. return ['object', 'function'].includes(typeof value) && value !== null;
  695. }
  696. }
  697.  
  698. function isAsyncFunction(fn) {
  699. return fn.constructor.toString()=='function AsyncFunction() { [native code] }';
  700. }
  701. }
  702. }
  703.  
  704. return [testChecker, registerChecker, loadFuncs];
  705. }) ();
  706.  
  707. return [
  708. // Console & Debug
  709. LogLevel, DoLog, Err, Assert,
  710.  
  711. // DOM
  712. $, $All, $CrE, $AEL, $$CrE, addStyle, detectDom, destroyEvent,
  713.  
  714. // Data
  715. copyProp, copyProps, parseArgs, escJsStr, replaceText,
  716.  
  717. // Environment & Browser
  718. getUrlArgv, dl_browser, dl_GM,
  719.  
  720. // Logic & Task
  721. AsyncManager, queueTask, testChecker, registerChecker, loadFuncs
  722. ];
  723. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址