vanilla-lib

Vanilla JS library

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/369430/756036/vanilla-lib.js

// ==UserScript==
// @name         vanilla-lib
// @namespace    http://dev.rsalazar.name/js/
// @version      1.2.191207.1426
// @description  Vanilla JS library
// @author       rsalazar
// @grant        none
// ==/UserScript==

function VanillaLib( ) {
	'use strict';
	let  self = { version:'1.2.191207.1426' },
	     undefined;  // ensure an 'undefined' reference

	// Logging related
	self.logging  = true;
	self.log      = function( ) { return  ! self.logging ? false : console.debug.apply(console, arguments); };
	self.logGroup = function( ) { return  ! self.logging ? false : console.groupCollapsed.apply(console, arguments); };
	self.logEnd   = function( ) { return  ! self.logging ? false : console.groupEnd.apply(console, arguments); };
	self.time     = function( ) { return  ! self.logging ? false : console.time.apply(console, arguments); };
	self.timeEnd  = function( ) { return  ! self.logging ? false : console.timeEnd.apply(console, arguments); };
	self.warn     = function( ) { return  ! self.logging ? false : console.warn.apply(console, arguments); };
	// Informative functionality
	self.isobj  = ( expr,type ) => ( 'object' === typeof expr && ( ! type || null !== expr && ( true === type ||
	             !! expr.constructor && type === ( self.isstr(type) ? expr.constructor.name : expr.constructor ) ) ) );
	self.ownsIt = ( obj,prop ) => ( !! prop && self.isobj(obj, true) && obj.hasOwnProperty(prop) );
	self.hasval = expr => ( null !== expr && ! self.ndef(expr) );
	self.isbool = expr => ( 'boolean' === typeof expr );
	self.ifnan  = ( expr,value ) => ( isNaN(expr) ? value : expr );
	self.ifndef = ( expr,value ) => ( self.ndef(expr) ? value : expr );
	self.ispojo = expr => self.isobj(expr, Object);
	self.isarr  = expr => self.isobj(expr, Array);
	self.isnum  = expr => ( 'number' === typeof expr );
	self.isstr  = expr => ( 'string' === typeof expr );
	self.isfn   = expr => ( 'function' === typeof expr );
	self.defn   = expr => ( 'undefined' !== typeof expr );
	self.ndef   = expr => ( 'undefined' === typeof expr );
	// Miscelaneous
	self.mapFlat = ( array,func ) => array.map( x => func(x) ).reduce( (a,b) => a.concat(b) );
	self.test    = ( expr,func,other ) => ( !! expr ? func(expr) : self.isfn(other) ? other(expr) : other );
	// DOM related
	self.parenth = ( elem,nth ) => self.traverse(elem, self.ifndef(nth, 1), 0);
	self.html    = ( elem,val ) => self.test(elem, el => self.ndef(val) ? el.innerHTML : (el.innerHTML = val) );
	self.text    = ( elem,val ) => self.test(elem, el => self.ndef(val) ? el.innerText : (el.innerText = val) );
	self.$$      = ( sel,elem ) => self.toArray((elem || document).querySelectorAll(sel));
	self.$       = ( sel,elem ) => (elem || document).querySelector(sel);
	// Number related
	self.aggRate = ( amnt,rate,times ) => ( times < 1 ? amnt : self.aggRate(amnt * rate, rate, times - 1) );
	self.between = ( value,from,to,open ) => ( from > to ? self.between(value, to, from, open)
	                                         : open ? from < value && value < to : from <= value && value <= to );
	self.toDec   = expr => ( Math.round(parseFloat((expr +'').replace(/\$|,/g, '')) * 100) / 100 );
	// Date/Time related
	self.secondsIn    = ( from,to,other ) => self.ifnan((to - from) /     1000, self.ifndef(other, NaN));
	self.minutesIn    = ( from,to,other ) => self.ifnan((to - from) /    60000, self.ifndef(other, NaN));
	self.hoursIn      = ( from,to,other ) => self.ifnan((to - from) /  3600000, self.ifndef(other, NaN));
	self.daysIn       = ( from,to,other ) => self.ifnan((to - from) / 86400000, self.ifndef(other, NaN));
	self.secondsSince = ( from,other ) => self.secondsIn(from, Date.now(), other);
	self.minutesSince = ( from,other ) => self.minutesIn(from, Date.now(), other);
	self.hoursSince   = ( from,other ) => self.hoursIn(from,   Date.now(), other);
	self.daysSince    = ( from,other ) => self.daysIn(from,    Date.now(), other);


	self.addClass = function( element, name ) {
		if ( !! element && !! name ) {
			if ( self.isarr(element) ) {
				return  element.map( elem => self.addClass(elem, name) );
			}

			// ToDo: Use .classList if available
			name = ( self.isarr(name) ? name : name.split(',') );
			name = name.map( nm => nm.trim() )
			       	.filter( nm => ! element.classList.contains(nm) );
			element.className = (element.className +' '+ name.join(' ')).trim();
			return  true;
		}
		return  false;
	};

	self.appendTo = function( element, parent, reference ) {
		if ( self.isarr(element) ) {
			return  element.map( elem => self.appendTo(elem, parent, reference) );
		}

		if ( !! reference ) {
			parent    = reference.parentNode;
			reference = reference.nextSibling;
		}

		if ( !! reference ) {
			return  self.prependTo(element, parent, reference);
		} else  if ( !! parent ) {
			parent.append(element);
		} else {
			self.warn('*** appendTo() could not add element: No parent or reference element provided');
		}

		return  element;
	};

	self.attr = function( element, name, value ) {
		if ( !! element ) {
			if ( self.isarr(element) ) {
				return  element.map( elem => self.attr(elem, name, value) );
			}

			return  self.keysAndValues(name, value, ( n,v ) => {
				return  ( self.hasval(v) ? element.setAttribute(n, v)
				          : null === v ? element.removeAttribute(n) : element.getAttribute(n) );
			} );
		}
		return  element;
	};

	self.choose = function( index, values ) {
		return  self.toArray(arguments)[ index ];
	}

	self.create = function( html, containerType ) {
		let  container = null,
		     result    = null,
		     attrs, style;

		if ( self.isobj(containerType) ) {
			attrs         = containerType.attrs;
			style         = containerType.style;
			containerType = containerType.container;
		}

		containerType = containerType || 'div';
		create[ containerType ] =
		container               = self.create[ containerType ] || document.createElement(containerType);
		container.innerHTML = html;
		result = self.toArray(container.childNodes)
		         	.map( elem => (elem.remove(), elem) );

		if ( !! attrs ) {
			self.attr(result, attrs);
		}
		if ( !! style ) {
			self.css(result, style);
		}

		if ( 1 == result.length ) {
			result = result[ 0 ];
		}
		return  result;
	};

	// Create sandbox, optionally adding it to `cache` as (read-only) `property` (or `_sandbox`, if missing)
	self.createSandbox = function( cache, property ) {
		const  frame = document.createElement('iframe');
		frame.src = 'about:blank',  frame.style.display = 'none';
		document.body.appendChild(frame);

		const  context = frame.contentWindow;
		!! cache && Object.defineProperty(cache, (property || '_sandbox'), { value:context, enumerable:false, writable:false, configurable:false });

		document.body.removeChild(frame);
		return  context;
	};

	self.createXHR = function( ) {
		let  xhr = new XMLHttpRequest( );
		xhr.onabort = function( ev, xhr ) { self.log('XHR Abort:', ev, xhr, this); };
		xhr.onerror = function( ev, xhr ) { self.log('XHR Error:', ev, xhr, this); };
		xhr.onload  = function( ev, xhr ) { self.log('XHR Load:',  ev, xhr, this); };
		self.on(xhr, {
			'abort': function( ev ) { self.isfn(this.onabort) && this.onabort(ev, this); },
			'error': function( ev ) { self.isfn(this.onerror) && this.onerror(ev, this); },
			'load':  function( ev ) { self.isfn(this.onload)  && this.onload(ev, this); },
		});
		return  xhr;
	};

	self.css = function( element, key, value ) {
		if ( isarr(element) ) {
			return  element.map( elem => self.css(elem, key, value) );
		}

		keysAndValues(key, value, ( k,v ) => element.style[ k ] = v );
		return  element;
	};

	self.extend = function( target, sources ) {
		for ( let  i = 1, n = arguments.length;  i < n;  i ++ ) {
			self.isobj(arguments[ i ], true) && self.copyMembers(arguments[ i ], target);
		}
		return  target;
	};

	self.extendProperties = function( target, source, overwrite, writable, enumerable, configurable ) {
		if ( !! target && !! source ) {
			overwrite    = !! overwrite;
			writable     = !! writable;
			enumerable   = !! enumerable;
			configurable = !! configurable;

			if ( self.isarr(source) ) {
				for ( let  i = 0, n = source.length;  i < n;  i ++ ) {
					self.extendProperties(target, source[ i ], overwrite, writable, enumerable, configurable);
				}
			} else if ( self.isobj(source, true) ) {
				for ( let  prop in source ) {
					if ( overwrite || self.ndef(target[ prop ]) ) {
						Object.defineProperty(target, prop,
						                      self.propDef(source[ prop ], writable, enumerable, configurable));
					}
				}
			}
		}
		return  target;
	};

	self.fire = function( element, event, args ) {
		if ( isarr(element) ) {
			return  element.map( elem => self.fire(elem, event, args) );
		}

		if ( self.isstr(event) ) {
			args  = self.ifndef(args, { 'bubbles':true, 'cancelable':true });
			event = new Event( event, args );
		}
		return  element.dispatchEvent(event);
	};

	self.keysAndValues = function( key, value, action ) {
		if ( self.ndef(action) && self.isfn(value) ) {
			action = value;
			value  = undefined;
		}

		// Case 1: key is an object (and there is no value)
		if ( self.isobj(key) && ! value ) {
			return  Object.keys(key)
			        	.map( k => action(k, key[ k ]) );
		// Case 2: key is an array
		} else if ( self.isarr(key) ) {
			// Case 1.a: value is an array of the same length
			if ( self.isarr(value) && key.length === value.length ) {
				return  key.map( ( k,i ) => action(k, value[ i ]) );
			// Case 1.b: value is considered a simple, plain value
			} else {
				return  key.map( k => action(k, value) );
			}
		// Default Case: key and value considered as simple, plain values
		} else {
			return  action(key, value);
		}
	};

	self.lazy = function( func, storage ) {
		// If the argument is a function (expected), set it as lazy getter
		if ( self.isfn(func) ) {
			let  name = 'initializer::'+ Math.random(),
			     me   = self.lazy;
			storage = storage || me.storage || (me.storage = { });
			return  ( ) => ( self.defn(storage[ name ]) ? storage[ name ] : (storage[ name ] = func()) );
		// If the argument was "something" else (not undefined), set it as the result
		} else if ( self.defn(func) ) {
			return  ( ) => func;
		}
		// Otherwise: No idea
		throw  'lazy(): The first argument (lazy getter / value) must be provided';
		//return  null;
	};

	self.localJson = function( key, value ) {
		if ( !! key && self.isstr(key) ) {
			try {
				if ( self.ndef(value) ) {
					return  JSON.parse(localStorage.getItem(key));
				} else if ( null === value ) {
					return  localStorage.removeItem(key);
				} else {
					return  localStorage.setItem(key, JSON.stringify(value));
				}
			} catch ( error ) {
				self.warn('* localJson() error:', error, '\n\tfor:', key, value);
			}
		}
		return  null;
	};

	self.off = function( element, event, callback ) {
		if ( self.ndef(callback) && self.isobj(event) ) {
			return  self.keysAndValues(event, ( k,v ) => self.off(element, k, v) );
		} else if ( self.isarr(element) ) {
			return  element.map( elem => self.off(elem, event, callback) );
		}
		return  element.removeEventListener(event, callback);
	};

	self.on = function( element, event, callback ) {
		if ( self.ndef(callback) && self.isobj(event) ) {
			return  self.keysAndValues(event, ( k,v ) => self.on(element, k, v) );
		} else if ( self.isarr(element) ) {
			return  element.map( elem => self.on(elem, event, callback) );
		}
		return  element.addEventListener(event, callback);
	};

	self.onmutate = function( element, callback, config ) {
		if ( !! element && self.isfn(callback) ) {
			config = config || { 'attributes':false, 'childList':true, 'subtree':false };

			if ( self.isarr(element) ) {
				return  element.map( elem => self.onmutate(elem, callback, config) );
			}

			let  observer = new MutationObserver( callback );
			observer.initialConfig = ( ) => config;
			observer.reconnect = function( newConfig ) {
				this.observe(element, newConfig || this.initialConfig());
				return  this;
			};
			return  observer.reconnect();
		}
		return  null;
	};

	self.pojo2query = function( pojo ) {
		if ( self.isobj(pojo) && !! pojo ) {
			let  query = Object.keys(pojo)
			             	.map( key => escape(key) +'='+ escape(pojo[ key ]) )
			             	.join('&');
			return  '?'+ query;
		}
		return  null;
	};

	self.prependTo = function( element, parent, reference ) {
		if ( ! reference && !! parent ) {
			reference = parent.childNodes[ 0 ];
		}

		if ( isarr(element) ) {
			return  element.map( elem => self.prependTo(elem, parent, reference) );
		}

		if ( !! reference ) {
			reference.parentNode.insertBefore(element, reference);
		} else if ( !! parent ) {
			parent.append(element);
		} else {
			self.warn('*** prependTo() could not add element: No parent or reference element provided');
		}

		return  element;
	};

	self.propDef = function( value, writable, enumerable, configurable ) {
		enumerable = !! enumerable;
		var  def = {
			'enumerable':   enumerable,
			'configurable': !! configurable,
		};

		if ( self.isfn(value) && ( enumerable || self.isfn(writable) ) ) {
			def.get = value;
			if ( self.isfn(writable) ) {
				def.set = writable;
			}
		} else {
			def.value    = value;
			def.writable = !! writable;
		}

		return  def;
	}

	self.query2pojo = function( query ) {
		query = (self.ifndef(query, location.search) +'')
		        	.replace(/^[?&]+|$&+/g, '');
		if ( !! query ) {
			let  segs, key, val, pojo = { };
			query.split('&')
				.forEach( item => {
					[ key, val ] =
					segs         = item.split('=');
					val = ( self.ndef(val) ? null : segs.slice(1).join('=') );
					pojo[ unescape(key) ] = unescape(val);
				} );
			return  pojo;
		}
		return  null;
	};

	self.range = function( from, to, inc ) {
		inc = ( ! inc || isNaN(inc) ? 1 : inc );
		if ( from == to - inc ) {
			return  [ from, to ];
		} else if ( from == to ) {
			return  [ to ];
		} else if ( (to - from) % inc || ! self.between(from + inc, from, to) ) {
			throw  'Cannot create range '+ from +'..'+ to +' with increments of '+ inc;
			//return  null ;//[ ];
		}
		return  self.range(from, to - inc, inc).concat(to);
	};

	self.request = function( url, verb, data, callback ) {
		self.log('* request of:', verb, url, data, callback);

		// "Validate" HTTP Verb, to an extent
		verb = (verb || 'GET').toUpperCase();
		switch ( verb ) {
			case  'P':  verb = 'POST';  break;
			case  'G':  verb = 'GET';   break;
			case  'H':  verb = 'HEAD';  break;
		}

		// Switch callback and data when ther is no data
		if ( self.ndef(callback) && self.isfn(data) ) {
			callback = data;
			data     = null;
		}

		// Set the data to a string or null
		data = self.ifndef(data, null);
		data = ( !! data && self.isobj(data) ? self.pojo2query(data).replace('?', '') : data.toString() );
		self.log('- request data:', data);

		// Add data to the URL for GET requests
		if ( 'GET' === verb && !! data ) {
			url += ( url.includes('?') ? '&' : '?' ) + data;
			data = null;
		}

		// Create & open XHR object...
		let  xhr = self.createXHR();
		self.log('-> opening request:', verb, url);
		xhr.open(verb, url);
		xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

		// Set the internal event handler, which removes itself on execution
		xhr.onabort =
		xhr.onerror =
		xhr.onload  = callback;

		// Send the actual request
		self.log('-> sending request:', data, xhr.readyState, xhr);
		return  xhr.send(data);
	};

	// Sandboxed `Function([exportedValues,] arg1, ..., argN, funcBody)`
	self.sandbox = function( exports, funcArgsAndCode ) {
		const  C = self.sandbox._sandbox || self.createSandbox(self.sandbox),
		       F = C.Function.bind(C);
		const  args = self.toArray(arguments);
		if ( 'string' !== typeof exports ) {
			args.shift();
		}

		// Do we need to pass-in any "exports" values (as arguments?)
		if ( !! exports && self.ispojo(exports) ) {
			let  inrArgs = Object.keys(exports);

			if ( inrArgs.length > 0 ) {
				inrArgs = inrArgs.concat(args);
				const  vals = Object.values(exports),
				       fn   = F.apply(null, inrArgs);
				(arguments[ arguments.length - 1 ] || '').includes('arguments') && console.warn('sandbox() - sandboxed code with `exports` should not rely on `arguments`');

				return  function _sandboxed_withExports( ){ return fn.apply(null, vals.concat(toArray(arguments))); };
			}
		}
		return  F.apply(null, args);
	};

	self.table2json = function( table, headers, filter ) {
		if ( !! table && !! table.rows ) {
			let  obj = { head:[ ], data:[ ] };

			obj.head = ( self.isfn(headers) ? headers(table) : table.rows[ 0 ] );
			if ( self.isobj(obj.head) && !! obj.head.cells ) {
				obj.head = Array.map(obj.head.cells, th => th.innerText.trim() );
			}

			if ( obj.head.length ) {
				filter = filter || (( row,i ) => ( i && 'TBODY' === row.parentNode.nodeName ));

				for ( let  r = 0, nr = table.rows.length;  r < nr;  r ++ ) {
					let  row = table.rows[ r ];

					if ( filter(row, r) ) {
						let   item = { };
						obj.head.forEach( ( col,i ) => {
							col[ 0 ] && (item[ col ] = row.cells[ i ].innerText.trim());
						} );
						obj.data.push(item);
					}
				}
			}
			return  obj.data;
		}
		return  null;
	};

	self.toArray = function( expr ) {
		if ( self.hasval(expr) && ! self.isarr(expr) ) {
			return  ( self.ndef(expr.length) ? [ expr ] : Array.prototype.slice.call(expr) );
		}
		return  expr || [ ];
	};

	self.toDec2 = function( amount ) {
		amount = self.toDec(amount);
		if ( isNaN(amount) ) {
			return  null;
		}
		let  segs = (amount +'').split('.');
		return  segs[ 0 ] +'.'+ ((segs[ 1 ] || 0) +'0').slice(0, 2);
	};

	self.toMoney = function( amount ) {
		let  dec2 = self.toDec2(amount);
		return  ( isNaN(dec2) ? null : dec2 < 0 ? '-$ '+ (-dec2) : '$ '+ dec2 );
	};

	self.traverse = function( elem, up, sideways, elementsOnly, lastIfNull ) {
		let  last = elem;
		while ( !! elem && up -- > 0 )  elem = (last = elem, elem.parentNode);

		let  prop = ( elementsOnly ? 'Element' : '' ) +'Sibling';
		if ( sideways < 0 ) {
			while ( !! elem && sideways ++ < 0 )  elem = (last = elem, elem[ 'previous'+ prop ]);
		} else if ( sideways > 0 ) {
			while ( !! elem && sideways -- > 0 )  elem = (last = elem, elem[ 'next'+ prop ]);
		}

		return  ( ! lastIfNull ? elem : elem || last );
	};

	self.wrapWith = function( content, wrapper ) {
		let  wrap = content;
		if ( !! content && !! wrapper ) {
			wrap = self.toArray( self.isstr(wrapper) ? self.create(wrapper) : wrapper )[ 0 ];
			if ( !! wrap ) {
				let  cont = self.toArray(content);

				self.prependTo(wrap, null, cont[ 0 ]);
				cont.forEach( c => self.appendTo(c, wrap) );
			}
		}
		return  wrap;
	};


	// Object for feature-detection (modern browsers only --e.g., uses lambdas)
	self.detected = Object.create(null, {
		// Detect support for passive event listeners
		// Reference: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
		'passiveEvents': self.propDef(self.lazy( ( ) => {
			var  supported = false;
			try {
				var  opts = Object.defineProperty({ }, 'passive', self.propDef( ( ) => supported = true ));
				window.addEventListener('testPassive', null, opts);
				window.removeEventListener('testPassive', null, opts);
			} catch ( e ) {
			}
			return  supported;
		} )),
	});


	// ----------------------------------------------------
	// Intended for Internal Use

	self.copyMembers = function( source, target, members, preserve ) {
		//self.log('* Copying from', source, '\n\tto', target, '\n\t'+ members, preserve);
		if ( ! self.isobj(source) || ! self.isobj(target) ) {
			self.warn('=> Cannot copy from/to non-objects');
			return  false;
		}

		let  names = Object.keys(source);
		preserve = ( self.isobj(preserve) ? preserve : false );
		//self.log('- Full list of members:', names, '\n\t', source);

		if ( self.isstr(members) ) {
			members = members.split(',').map( nm => nm.trim() );
		}
		if ( self.isarr(members) ) {
			//self.log('* Member filter:', members);
			names = names.filter( nm => members.includes(nm) );
		} else if ( self.isfn(members) ) {
			names = names.filter(members);
		}
		//self.log('- Filtered list of members:', names);

		names.forEach( nm => {
			if ( !! target[ nm ] && !! preserve ) {
				preserve[ nm ] = target[ nm ];
			}
			target[ nm ] = source[ nm ];
			//self.log('- Target members', nm, target[ nm ]);
		} );
		//self.log('=>', target);
		return  (preserve || target);
	};

	self.export = function( scope, members, overwriten ) {
		if ( ! scope ) {
			return  false;
		}
		if ( '*' === (members +'').trim() ) {
			members = null;
		}
		return  self.copyMembers(self, scope, members, overwriten);
	};

	// Avoid needing a 'new' operator; this is just a wrapper
	return  self;
}