User:Arashiryuu0/common.js

/*	jshint undef: true, noarg: true, devel: true, typed: true, jquery: true, strict: global, eqeqeq: true, freeze: true, newcap: true, esnext: true, browser: true, latedef: true, shadow: outer, varstmt: true, quotmark: single, laxbreak: true, singleGroups: true, futurehostile: true /*	globals mw, Symbol, requestIdleCallback /** * @typedef Ref * @property {*} [current] */ /** * @typedef ArticleImport * @property {!('style' | 'script')} type * @property {!string[]} articles */ 'use strict'; __main__: { if (window.UCP && window.UCP.localJS) break __main__; const raf = requestAnimationFrame; const slice = Array.prototype.slice; /**	 * List of imports. * @type {ArticleImport[]} */	const imports = [ {			type: 'script', articles: [ 'u:dev:MediaWiki:Highlight-js.js', 'u:dev:MediaWiki:Preact.js' ]		}	];	// RL sucks // importArticles(...imports); window.importArticles.apply(0, imports); mw.loader.load('/User:Arashiryuu0/common.css?action=raw&ctype=text/css', 'text/css'); /**	 * @param {*} [current] * @returns {!Ref} */   const useRef = function (current) { return { current: current }; };	/**	 * Throttles function call rate. * @param {!VoidFunction} callback * @param {!number} timeframe */	const throttle = function (callback, timeframe) { const _t = useRef(0); return function { const now = Date.now; if (now - _t.current >= timeframe) { callback.apply(this, arguments); _t.current = now; }		};	};	/**	 * A simple function debouncer. * @param {!VoidFunction} callback * @param {!number} timeframe * @param {!boolean} immediate * @returns {!VoidFunction} */	const debounce = function (callback, timeframe, immediate) { const _t = useRef(0); return function { const context = this; const args = arguments; const later = function { _t.current = 0; if (!immediate) callback.apply(context, args); };			const now = immediate && !_t.current; clearTimeout(_t.current); _t.current = setTimeout(later, timeframe); if (now) callback.apply(context, args); };	};   /**     * Creates clean objects with a `Symbol.toStringTag` description of the object. * @param {!string} value * @returns {!object} */   const _Object = function (value) { if (arguments.length < 1) value = 'NullObject'; if (typeof value !== 'string') throw new TypeError('Description must be a `string` value.'); const obj = Object.create(null); // RL sucks // Object.create(null, { [Symbol.toStringTag]: { value } }); Object.defineProperty(obj, Symbol.toStringTag, {			configurable: false,			enumerable: false,			writable: false,			value: value		}); Object.freeze(obj); return Object.create(obj); };   /**     * Cache of fired hooks. */   const fired = _Object('Hook.Cache'); /**    * @type {!IdleRequestCallback} */   const loaded = function  { if (fired.loaded) return; const has = Object.prototype.hasOwnProperty; const toString = Object.prototype.toString; /**		 * Function currying helper for creating pipelines. */		const createPipeline = function { const pipes = slice.call(arguments); return function pipeline (value) { return pipes.reduce(function (acc, fn) {					return fn(acc);				}, value); };		};       /**         * Determines if something is undefined or null. * @param {*} anything * @returns {!boolean} */       const isNil = function (anything) { return anything === undefined || anything === null; };       /**         * Simple polyfill for the nullish coalescing operator (??) * @param {*} expected * @param {*} fallback * @returns {*} */       const ifNull = function (expected, fallback) { return isNil(expected) ? fallback : expected; };       /**         * Safely traverses an object, returns undefined if a property does not exist. * @param {!object} obj * @param {!string} path * @returns {*} */       const getProp = function (obj, path) { return path.split(/\s?\.\s?/).reduce(function (o, prop) {				return o && o[prop];			}, obj); };       /**		 * Gets a more accurate description of an object than `typeof`. * @param {*} item * @returns {!string} */		const typeOf = function (item) { return toString .call(item) .replace(/\[object (\w+)\]/i, '$1') .toLowerCase; };       /**         * Checks whether something is a Node instance. * @param {*} tester * @returns {!boolean} */       const isNode = function (tester) { return tester instanceof Node; };       /**		 * Self-binds an object's methods to the object. * @param {!object} target */		const applyBinds = function (target) { const methods = Object.getOwnPropertyNames(target).filter(function (key) {				return typeof target[key] === 'function';			}); if (!methods.length) return; const counter = useRef(0); while (counter.current < methods.length) { const method = methods[counter.current++]; target[method] = target[method].bind(target); }		};       /**         * DOM element helper utilities. */       const DOM = _Object('DOM'); __dom__: { const xmlns = 'http://www.w3.org/2000/svg'; /**			 * @param {!string} type * @returns {!(HTMLElement | SVGElement)} */			const getValidElement = function (type) { const element = document.createElement(type); if (element instanceof window.HTMLUnknownElement) return document.createElementNS(xmlns, type); return element; };			/**			 * @param {*} item * @returns {!boolean} */			const isFunction = function (item) { return typeof item === 'function'; };			/**			 * @param {!string} key * @returns {!boolean} */			const isEvent = function (key) { return key.slice(0, 2) === 'on' && key[2] === key[2].toUpperCase; };			/**			 * @param {!string} name * @returns {!string} */			const normalize = function (name) { return name === 'doubleclick' ? 'dblclick' : name; };			/**			 * @param {!string} key * @returns {!boolean} */			const isDataAttr = function (key) { return key.startsWith('data') && key.toLowerCase !== key; };			/**			 * @param {!string} key * @returns {!string} */			const dataNormalize = function (key) { return key.replace(/([A-Z]{1,})/g, '-$1').toLowerCase; };			Object.assign(DOM, {				/**				 * A `document.createElement` helper function.				 * @param {!string} type				 * @param {?object} [props]				 * @returns {!(HTMLElement | SVGElement)}				 */				create: function (type, props) {					if (typeof type !== 'string') type = 'div';					const e = getValidElement(type);					const children = slice.call(arguments, 2);					if (typeOf(props) !== 'object' || !Object.keys(props).length) {						if (children.length) e.append.apply(e, children);						return e;					}					if (!has.call(props, 'children') && children.length) {						e.append.apply(e, children);					}					const counter = useRef(0);					const keys = Object.keys(props);					while (counter.current < keys.length) {						const key = keys[counter.current++];						switch (key) {							case 'textContent':							case 'text': {								e.textContent = props[key];								break;							}							case 'innerText': { e.innerText = props[key]; break; }							case 'className': { e[key] = props[key]; break; }							case 'classes': { if (!Array.isArray(props[key])) props[key] = [props[key]]; e.classList.add.apply(e.classList, props[key]); break; }							case 'style': { if (typeof props[key] === 'string') { e.setAttribute(key, props[key]); break; }								Object.assign(e[key], props[key]); break; }							case 'children': { if (!Array.isArray(props[key])) props[key] = [props[key]]; if (props[key].length) e.append.apply(e, props[key]); break; }							case 'htmlFor': { e.setAttribute('for', props[key]); break; }							default: { if (isEvent(key)) { const event = normalize(key.slice(2).toLowerCase); if (Array.isArray(props[key]) && props[key].length) { const i = useRef(0); const listeners = props[key].filter(isFunction); while (i.current < listeners.length) { const listener = listeners[i.current++]; e.addEventListener(event, listener); }										break; }									e.addEventListener(event, props[key]); break; }								if (isDataAttr(key)) { const attr = dataNormalize(key); e.setAttribute(attr, props[key]); break; }								e.setAttribute(key, props[key]); break; }						}					}					e.$$props = props; return e;				}, /**				 * A `DocumentFragment` helper function. * @param {!(string | Node)[]} [children] * @returns {!DocumentFragment} */				fragment: function (children) { const frag = new DocumentFragment; if (isNil(children)) return frag; if (!Array.isArray(children) || !children.length) return frag; frag.append.apply(frag, children); return frag; }			});			applyBinds(DOM);       }        /**         * Create Logger object.         */        const Logger = _Object('Logger');        __logger__: {			const counter = useRef(0);			/**			 * Logging levels.			 * @type {!string[]}			 */			const levels = ['log', 'dir', 'info', 'warn', 'debug', 'error'];			/**			 * Provides label data for logging methods.			 * @param {!string} name			 * @returns {!string[]}			 */			const getParts = function (name) {				return [					'%c[' + name + ']%c \u2014 %s',					'color: #82AAFF;',					'color: #F78C6A;',					new Date.toUTCString				];			};			/**			 * Fetches levels to be used for logging.			 * @param {!(string | number)} level			 * @returns {!string}			 */			const getLevel = function (level) {				const base = 'log';				if (typeof level !== 'string' && typeof level !== 'number') return base;				if (typeof level === 'number') return levels[level] || base; return levels.includes(level) ? level : base; };			/**			 * Creates log methods. * @param {!(string | number)} level * @param {!string} name * @returns {!VoidFunction} */			const makeLog = function (level, name) { const lvl = getLevel(level); return function { console.groupCollapsed.apply(0, getParts(name)); console[lvl].apply(0, arguments); console.groupEnd; };			};			while (counter.current < levels.length) { const level = levels[counter.current++]; Logger[level] = Object.freeze(makeLog(level, 'User:Arashiryuu0/common.js')); }			applyBinds(Logger); Object.freeze(Logger); }       __banners__: { const bannerItems = [ document.querySelector('.wds-global-navigation__logo') ].concat(slice.call(document.querySelectorAll('.wds-global-navigation__link'))); bannerItems.forEach(function (item) {				if (!item) return;				item.setAttribute('tabindex', '-1');			}); }		__buttons__: { if (fired.buttons) break __buttons__; const wdsButtons = document.querySelectorAll('.wiki-tools.wds-button-group'); if (!wdsButtons.length) break __buttons__; /**			 * @param {!HTMLElement} button * @returns {!Node} */			const clone = function (button) { return button.cloneNode(true); };			const firstName = getProp(wdsButtons, '0.firstElementChild.className'); if (!firstName) break __buttons__; const buttons = [ DOM.create('a', {					className: firstName.replace('wiki-tools__search', 'wiki-tools__user-js'),					text: 'JS',					title: 'User JS',					href: '/wiki/User:Arashiryuu0/common.js'				}), DOM.create('a', {					className: firstName.replace('wiki-tools__search', 'wiki-tools__user-css'),					text: 'CSS',					title: 'User CSS',					href: '/wiki/User:Arashiryuu0/common.css'				}), DOM.create('a', {					className: firstName.replace('wiki-tools__search', 'wiki-tools__random'),					text: '?',					title: 'Random Page',					href: '/wiki/Special:Random',					dataTracking: 'explore-random'				}) ];			const children = DOM.fragment; const counter = useRef(0); /**			 * @param {!HTMLElement} wds */			const handleWds = function (wds) { const cloned = buttons.map(clone); children.append.apply(children, cloned); wds.appendChild(children); /**				 * fix button positioning * layout should be: * search, discussions, recent changes, theme, my buttons, dropdown */				wds.appendChild(wds.querySelector('.wds-dropdown')); };			while (counter.current < wdsButtons.length) handleWds(wdsButtons[counter.current++]); fired.buttons = true; }		const ApexLaser = _Object('ApexLaser'); __apex_laser__: { /**			 * @param {!number} value * @returns {!string} */			const asString = function (value) { return value.toString(16).padEnd(2, '0'); };			/**			 * @param {!number} value * @returns {!boolean} */			const inRange = function (value) { return Number.isFinite(value) && value >= 0 && value <= 255; };			Object.assign(ApexLaser, {				/**				 * @param {number} [r]				 * @param {number} [g]				 * @param {number} [b]				 * @returns {!number}				 */				fromRGB: function (r, g, b) {					const args = [r || 0, g || 0, b || 0];					if (!args.every(inRange)) throw new RangeError('RGB values must be a number between 0 and 255.');					const strings = args.map(asString);					const joined = strings.slice.reverse.join();					return Number.parseInt(joined, 16);				},				/**				 * @param {!string} hex				 * @returns {!number}				 */				fromHex: function (hex) {					if (hex[0] === '#') hex = hex.slice(1);					const lengths = [3, 6];					switch (hex.length) {						case 3: {							const values = hex.split().map(function (char) { return char + char; });							const match = values.join().match(/([0-9a-f]){2}/g);							if (!match || match.length !== 3) throw new RangeError('Hex value must be a 3 or 6 character combination using numbers `0-9` and or letters `a-f`.');							const joined = values.slice.reverse.join();							return Number.parseInt(joined, 16);						}						case 6: {							const match = hex.match(/([0-9a-f]){2}/g);							if (!match || match.length !== 3) throw new RangeError('Hex value must be a 3 or 6 character combination using numbers `0-9` and or letters `a-f`.');							const slices = [								hex.slice(0, 2),								hex.slice(2, 4),								hex.slice(4, 6)							];							const joined = slices.slice.reverse.join('');							return Number.parseInt(joined, 16);						}						default: {							throw new RangeError('Hex value must be a 3 or 6 character combination using numbers `0-9` and or letters `a-f`.');						} }				},				/**				 * @param {!number} base10num * @returns {!string} */				toHex: function (base10num) { const string = base10num.toString(16).padStart(6, '0'); const slices = [ string.slice(0, 2), string.slice(2, 4), string.slice(4, 6) ];					const flipped = slices.slice.reverse; return flipped.join(''); },				/**				 * @param {!number} base10num * @returns {!number[]} */				toRGB: function (base10num) { const R = base10num & 255; const G = base10num >> 8 & 255; const B = base10num >> 16 & 255; return [R, G, B]; }			});		}		Object.freeze(ApexLaser);		window.UCP.Logger = Logger;		window.UCP.Utils = _Object('Utils');		Object.assign(window.UCP.Utils, { DOM: DOM, type: typeOf, isNil: isNil, ifNull: ifNull, isNode: isNode, getProp: getProp, debounce: debounce, throttle: throttle, ApexLaser: ApexLaser, createPipeline: createPipeline });		applyBinds(window.UCP.Utils);		Object.freeze(window.UCP.Utils);		mw.hook('dev.highlight').add(function (hljs) { /**			 * @type {!Ref} */			const ref = useRef; /**			 * List of dark-mode themes. * @type {!string[]} */			// const themes = [ // 	'monokai-sublime', // 	'solarized-dark', // 	'tomorrow-night', // 	'atom-one-dark', // 	'dracula', // 	'vs2015' // ];			/**			 * Randomly picked theme. * @type {!string} */			// const pick = themes[Math.floor(Math.random * themes.length)]; /**			 * Disables my globally added codeblock theme to allow local themes to highlight properly. */			const disable = function { const n = document.querySelector('link[href$=".min.css"]'); if (!n) { ref.current = raf(disable); return; }				n.setAttribute('disabled', ''); ref.current = null; };			/**			 * Sets the current theme. * @param {!string} theme */			const setTheme = function (theme) { return function { hljs.useTheme(theme); };			};			const setter = setTheme('atom-one-dark'); /**			 * Callback wrapper to be supplied to the `hljs.loadAllLanguages` promise's `.then` success handler. */			const useTheme = function { raf(setter); };			ref.current = raf(disable); hljs.loadAllLanguages.then(useTheme, Logger.error); });       fired.loaded = true;    };	window.UCP = window.UCP || _Object('UCP');	Object.assign(window.UCP, { localJS: fired, strings: { 'f': String.fromCodePoint(402), 'deg': String.fromCodePoint(176), 'emdash': String.fromCodePoint(8212), 'emdott': String.fromCodePoint(8226) }	});	requestIdleCallback(loaded); } /*@end@*/