utils.js

GitHub userscript utilities

目前为 2022-08-09 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/398877/1079637/utilsjs.js

  1. /* GitHub userscript utilities v0.2.1
  2. * Copyright © 2022 Rob Garrison
  3. * License: MIT
  4. */
  5. /* exported
  6. * $ $$
  7. * addClass removeClass toggleClass
  8. * removeEls removeSelection
  9. * on off make
  10. * debounce
  11. */
  12. "use strict";
  13.  
  14. const REGEX = {
  15. WHITESPACE: /\s+/,
  16. NAMESPACE: /[.:]/,
  17. COMMA: /\s*,\s*/
  18. };
  19.  
  20. /* DOM utilities */
  21. /**
  22. * Find & return a single DOM node
  23. * @param {String} selector - CSS selector string
  24. * @param {HTMLElement} el - DOM node to start the query (defaults to document)
  25. * @returns {HTMLElement|null}
  26. */
  27. const $ = (selector, el) => (el || document).querySelector(selector);
  28.  
  29. /**
  30. * Find & return multiple DOM nodes
  31. * @param {String} selector - CSS selector string
  32. * @param {HTMLElement} el - DOM node to start the query (defaults to document)
  33. * @returns {HTMLElement[]}
  34. */
  35. const $$ = (selector, el) => [...(el || document).querySelectorAll(selector)];
  36.  
  37. /**
  38. * Common functions
  39. */
  40. const _ = {};
  41. /**
  42. * Return an array of elements
  43. * @param {HTMLElement|HTMLElement[]|NodeList} elements
  44. * @returns {HTMLElement[]}
  45. */
  46. _.createElementArray = elements => {
  47. if (Array.isArray(elements)) {
  48. return elements;
  49. }
  50. return elements instanceof NodeList ? [...elements] : [elements];
  51. };
  52. /**
  53. * Common event listener code
  54. * @param {String} type - "add" or "remove" event listener
  55. * @param {HTMLElement[]} els - DOM node array that need listeners
  56. * @param {String} name - Event name, e.g. "click", "mouseover", etc
  57. * @param {Function} handler - Event callback
  58. * @param {Object} options - Event listener options or useCapture - see
  59. * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters
  60. */
  61. _.eventListener = (type, els, name, handler, options) => {
  62. const events = name.split(REGEX.WHITESPACE);
  63. _.createElementArray(els).forEach(el => {
  64. events.forEach(ev => {
  65. el?.[`${type}EventListener`](ev, handler, options);
  66. });
  67. });
  68. };
  69. /**
  70. * Create an array of classes/event types from a space or comma separated string
  71. * @param {String} classes - space or comma separated list of classes or events
  72. * @returns {String[]}
  73. */
  74. _.getClasses = classes => {
  75. if (Array.isArray(classes)) {
  76. return classes;
  77. }
  78. const names = classes.toString();
  79. return names.includes(",") ? names.split(REGEX.COMMA) : [names];
  80. };
  81.  
  82. /**
  83. * Add class name(s) to one or more elements
  84. * @param {HTMLElements[]|Nodelist|HTMLElement|Node} elements
  85. * @param {string|array} classes - class name(s) to add; string can contain a
  86. * comma separated list
  87. */
  88. const addClass = (elements, classes) => {
  89. const classNames = _.getClasses(classes);
  90. const els = _.createElementArray(elements);
  91. let index = els.length;
  92. while (index--) {
  93. els[index]?.classList.add(...classNames);
  94. }
  95. };
  96.  
  97. /**
  98. * Remove class name(s) from one or more elements
  99. * @param {HTMLElements[]|NodeList|HTMLElement|Node} elements
  100. * @param {string|array} classes - class name(s) to add; string can contain a
  101. * comma separated list
  102. */
  103. const removeClass = (elements, classes) => {
  104. const classNames = _.getClasses(classes);
  105. const els = _.createElementArray(elements);
  106. let index = els.length;
  107. while (index--) {
  108. els[index]?.classList.remove(...classNames);
  109. }
  110. };
  111.  
  112. /**
  113. * Toggle class name of DOM element(s)
  114. * @param {HTMLElement|HTMLElement[]|NodeList} els
  115. * @param {string} name - class name to toggle (toggle only accepts one name)
  116. * @param {boolean} flag - force toggle; true = add class, false = remove class;
  117. * if undefined, the class will be toggled based on the element's class name
  118. */
  119. // flag = true, then add class
  120. const toggleClass = (elements, className, flag) => {
  121. const els = _.createElementArray(elements);
  122. let index = elms.length;
  123. while (index--) {
  124. els[index]?.classList.toggle(className, flag);
  125. }
  126. };
  127.  
  128. /**
  129. * Remove DOM nodes
  130. * @param {String} selector - CSS selector string
  131. * @param {HTMLElement|undefined} el - parent DOM node (defaults to document)
  132. */
  133. const removeEls = (selector, el) => {
  134. let els = $$(selector, el);
  135. let index = els.length;
  136. while (index--) {
  137. els[index].parentNode.removeChild(els[index]);
  138. }
  139. };
  140.  
  141. /**
  142. * Remove text selection
  143. */
  144. const removeSelection = () => {
  145. // remove text selection - https://stackoverflow.com/a/3171348/145346
  146. const sel = window.getSelection
  147. ? window.getSelection()
  148. : document.selection;
  149. if (sel) {
  150. if (sel.removeAllRanges) {
  151. sel.removeAllRanges();
  152. } else if (sel.empty) {
  153. sel.empty();
  154. }
  155. }
  156. };
  157.  
  158. /**
  159. * Add/remove event listener
  160. * @param {HTMLElement|HTMLElement[]|NodeList} els
  161. * @param {string} name - event name(s) to bind, e.g. "mouseup mousedown"; also
  162. * accpets a comma separated string, e.g. "mouseup, mousedown"
  163. * @param {function} handler - event handler
  164. * @param {options} eventListener options
  165. */
  166. const on = (els, name = "", handler, options) => {
  167. _.eventListener("add", els, name, handler, options);
  168. };
  169. const off = (els, name = "", handler, options) => {
  170. _.eventListener("remove", els, name, handler, options);
  171. }
  172.  
  173. /**
  174. * **** Helpers ****
  175. */
  176. /**
  177. * Debounce
  178. * @param {Function} fxn - callback executed after debounce
  179. * @param {Number} time - time (in ms) to delay
  180. * @returns {Function} debounced function
  181. */
  182. const debounce = (fxn, time = 500) => {
  183. let timer;
  184. return function() {
  185. clearTimeout(timer);
  186. timer = setTimeout(() => {
  187. fxn.apply(this, arguments);
  188. }, time);
  189. }
  190. }
  191.  
  192. /**
  193. * @typedef Utils~makeOptions
  194. * @type {object}
  195. * @property {string} el - HTML element tag, e.g. "div" (default)
  196. * @property {string} appendTo - selector of target element to append menu
  197. * @property {string} className - CSS classes to add to the element
  198. * @property {object} attrs - HTML attributes (as key/value paries) to set
  199. * @property {object} text - string added to el using textContent
  200. * @property {string} html - html to be added using `innerHTML` (overrides `text`)
  201. * @property {array} children - array of elements to append to the created element
  202. */
  203. /**
  204. * Create a DOM element
  205. * @param {Utils~makeOptions}
  206. * @returns {HTMLElement} (may be already inserted in the DOM)
  207. * @example
  208. make({ el: 'ul', className: 'wrapper', appendTo: 'body' }, [
  209. make({ el: 'li', text: 'item #1' }),
  210. make({ el: 'li', text: 'item #2' })
  211. ]);
  212. */
  213. const make = (obj = {}, children) => {
  214. const el = document.createElement(obj.el || "div");
  215. const { appendTo } = obj;
  216. const xref = {
  217. className: "className",
  218. id: "id",
  219. text: "textContent",
  220. html: "innerHTML", // overrides text setting
  221. };
  222. Object.keys(xref).forEach(key => {
  223. if (obj[key]) {
  224. el[xref[key]] = obj[key];
  225. }
  226. });
  227. if (obj.attrs) {
  228. for (let key in obj.attrs) {
  229. if (obj.attrs.hasOwnProperty(key)) {
  230. el.setAttribute(key, obj.attrs[key]);
  231. }
  232. }
  233. }
  234. if (Array.isArray(children) && children.length) {
  235. children.forEach(child => el.appendChild(child));
  236. }
  237. if (appendTo) {
  238. const wrap = typeof appendTo === "string"
  239. ? $(appendTo)
  240. : appendTo;
  241. if (wrap) {
  242. wrap.appendChild(el);
  243. }
  244. }
  245. return el;
  246. }