- /* GitHub userscript utilities v0.2.3
- * Copyright © 2022 Rob Garrison
- * License: MIT
- */
- /* exported
- * $ $$
- * addClass removeClass toggleClass
- * removeEls removeSelection
- * on off make
- * debounce
- */
- // 'use strict';
-
- var REGEX = {
- WHITESPACE: /\s+/,
- NAMESPACE: /[.:]/,
- COMMA: /\s*,\s*/,
- };
-
- /* DOM utilities */
- /**
- * Find & return a single DOM node
- * @param {String} selector - CSS selector string
- * @param {HTMLElement} el - DOM node to start the query (defaults to document)
- * @returns {HTMLElement|null}
- */
- const $ = (selector, el) => (el || document).querySelector(selector);
-
- /**
- * Find & return multiple DOM nodes
- * @param {String} selector - CSS selector string
- * @param {HTMLElement} el - DOM node to start the query (defaults to document)
- * @returns {HTMLElement[]}
- */
- const $$ = (selector, el) => [...(el || document).querySelectorAll(selector)];
-
- /**
- * Common functions
- */
- var _ = {};
- /**
- * Return an array of elements
- * @param {HTMLElement|HTMLElement[]|NodeList} elements
- * @returns {HTMLElement[]}
- */
- _.createElementArray = (elements) => {
- if (Array.isArray(elements)) {
- return elements;
- }
- return elements instanceof NodeList ? [...elements] : [elements];
- };
- /**
- * Common event listener code
- * @param {String} type - "add" or "remove" event listener
- * @param {HTMLElement[]} els - DOM node array that need listeners
- * @param {String} name - Event name, e.g. "click", "mouseover", etc
- * @param {Function} handler - Event callback
- * @param {Object} options - Event listener options or useCapture - see
- * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters
- */
- _.eventListener = (type, els, name, handler, options) => {
- const events = name.split(REGEX.WHITESPACE);
- _.createElementArray(els).forEach((el) => {
- events.forEach((ev) => {
- el?.[`${type}EventListener`](ev, handler, options);
- });
- });
- };
- /**
- * Create an array of classes/event types from a space or comma separated string
- * @param {String} classes - space or comma separated list of classes or events
- * @returns {String[]}
- */
- _.getClasses = (classes) => {
- if (Array.isArray(classes)) {
- return classes;
- }
- const names = classes.toString();
- return names.includes(',') ? names.split(REGEX.COMMA) : [names];
- };
-
- /**
- * Add class name(s) to one or more elements
- * @param {HTMLElements[]|Nodelist|HTMLElement|Node} elements
- * @param {string|array} classes - class name(s) to add; string can contain a
- * comma separated list
- */
- var addClass = (elements, classes) => {
- const classNames = _.getClasses(classes);
- const els = _.createElementArray(elements);
- let index = els.length;
- while (index--) {
- els[index]?.classList.add(...classNames);
- }
- };
-
- /**
- * Remove class name(s) from one or more elements
- * @param {HTMLElements[]|NodeList|HTMLElement|Node} elements
- * @param {string|array} classes - class name(s) to add; string can contain a
- * comma separated list
- */
- var removeClass = (elements, classes) => {
- const classNames = _.getClasses(classes);
- const els = _.createElementArray(elements);
- let index = els.length;
- while (index--) {
- els[index]?.classList.remove(...classNames);
- }
- };
-
- /**
- * Toggle class name of DOM element(s)
- * @param {HTMLElement|HTMLElement[]|NodeList} els
- * @param {string} name - class name to toggle (toggle only accepts one name)
- * @param {boolean} flag - force toggle; true = add class, false = remove class;
- * if undefined, the class will be toggled based on the element's class name
- */
- // flag = true, then add class
- var toggleClass = (elements, className, flag) => {
- const els = _.createElementArray(elements);
- let index = elms.length;
- while (index--) {
- els[index]?.classList.toggle(className, flag);
- }
- };
-
- /**
- * Remove DOM nodes
- * @param {String} selector - CSS selector string
- * @param {HTMLElement|undefined} el - parent DOM node (defaults to document)
- */
- var removeEls = (selector, el) => {
- let els = $$(selector, el);
- let index = els.length;
- while (index--) {
- els[index].parentNode.removeChild(els[index]);
- }
- };
-
- /**
- * Remove text selection
- */
- var removeSelection = () => {
- // remove text selection - https://stackoverflow.com/a/3171348/145346
- const sel = window.getSelection ? window.getSelection() : document.selection;
- if (sel) {
- if (sel.removeAllRanges) {
- sel.removeAllRanges();
- } else if (sel.empty) {
- sel.empty();
- }
- }
- };
-
- /**
- * Add/remove event listener
- * @param {HTMLElement|HTMLElement[]|NodeList} els
- * @param {string} name - event name(s) to bind, e.g. "mouseup mousedown"; also
- * accpets a comma separated string, e.g. "mouseup, mousedown"
- * @param {function} handler - event handler
- * @param {options} eventListener options
- */
- var on = (els, name = '', handler, options) => {
- _.eventListener('add', els, name, handler, options);
- };
- var off = (els, name = '', handler, options) => {
- _.eventListener('remove', els, name, handler, options);
- };
-
- /**
- * **** Helpers ****
- */
- /**
- * Debounce
- * @param {Function} fxn - callback executed after debounce
- * @param {Number} time - time (in ms) to delay
- * @returns {Function} debounced function
- */
- var debounce = (fxn, time = 500) => {
- let timer;
- return function () {
- clearTimeout(timer);
- timer = setTimeout(() => {
- fxn.apply(this, arguments);
- }, time);
- };
- };
-
- /**
- * @typedef Utils~makeOptions
- * @type {object}
- * @property {string} el - HTML element tag, e.g. "div" (default)
- * @property {string} appendTo - selector of target element to append menu
- * @property {string} className - CSS classes to add to the element
- * @property {object} attrs - HTML attributes (as key/value paries) to set
- * @property {object} text - string added to el using textContent
- * @property {string} html - html to be added using `innerHTML` (overrides `text`)
- * @property {array} children - array of elements to append to the created element
- */
- /**
- * Create a DOM element
- * @param {Utils~makeOptions}
- * @returns {HTMLElement} (may be already inserted in the DOM)
- * @example
- make({ el: 'ul', className: 'wrapper', appendTo: 'body' }, [
- make({ el: 'li', text: 'item #1' }),
- make({ el: 'li', text: 'item #2' })
- ]);
- */
- var make = (obj = {}, children) => {
- const el = document.createElement(obj.el || 'div');
- const { appendTo } = obj;
- const xref = {
- className: 'className',
- id: 'id',
- text: 'textContent',
- html: 'innerHTML', // overrides text setting
- };
- Object.keys(xref).forEach((key) => {
- if (obj[key]) {
- el[xref[key]] = obj[key];
- }
- });
- if (obj.attrs) {
- for (let key in obj.attrs) {
- if (obj.attrs.hasOwnProperty(key)) {
- el.setAttribute(key, obj.attrs[key]);
- }
- }
- }
- if (Array.isArray(children) && children.length) {
- children.forEach((child) => el.appendChild(child));
- }
- if (appendTo) {
- const wrap = typeof appendTo === 'string' ? $(appendTo) : appendTo;
- if (wrap) {
- wrap.appendChild(el);
- }
- }
- return el;
- };