Native implementation of a jsFiddle.net in-pane log console

To be used among external libraries when testing other jsFiddle.net scripts

Цей скрипт не слід встановлювати безпосередньо. Це - бібліотека для інших скриптів для включення в мета директиву // @require https://update.greasyfork.org/scripts/375910/656390/Native%20implementation%20of%20a%20jsFiddlenet%20in-pane%20log%20console.js

// ==UserScript==
// @name          Native implementation of a jsFiddle.net in-pane log console
// @version       0.1.0
// @description   To be used among external libraries when testing other jsFiddle.net scripts
// @namespace     https://greasyfork.org/en/users/15562
// @author        Jonathan Brochu (https://greasyfork.org/en/users/15562)
// @license       GPLv3 or later (http://www.gnu.org/licenses/gpl-3.0.en.html)
// @include       https://jsfiddle.net/*
// @grant         GM_addStyle
// ==/UserScript==

/***
 * History:
 *
 * 0.1.0  First implementation, based on own's previous work. (2018-12-24)
 *
 */

// Helpers, implemented on Element/Document/DocumentFragment.prototype
(function (arrTargets, arrSpecs) {
	var __fn__ = 'function',
		__und__ = 'undefined';
	arrTargets.forEach(function (target) {
		arrSpecs.forEach(function (spec) {
			if (typeof spec.prop  === __und__) {
				return;
			}
			if (typeof spec.impl === __und__) {
				return;
			}
			var propName = spec.prop,
				propImpl = spec.impl;
				accessors = spec.accessors.split('|'),
				getArgCount = spec.getArgCount || spec.setArgCount-1 || 0,
				readOnly = (typeof spec.readOnly !== __und__ ? spec.readOnly : false);
			if (target.hasOwnProperty(propName)) {
				return;
			}
			if (typeof propImpl !== __fn__) {
				return;
			}
			var desc = {
				configurable: true,
				enumerable: true,
				writable: !readOnly
			};
			['get', 'set', 'value'].forEach(function(item) {
				if (accessors.indexOf(item) == -1) return;
				switch(item) {
					case 'get': desc[item] = function() {
						return (typeof propImpl === __fn__ ? propImpl.apply(this, arguments) : propImpl);
					}; break;
					case 'set': desc[item] = function() {
						return (typeof propImpl === __fn__ ? propImpl.apply(this, arguments) : propImpl);
					}; break;
					default: desc[item] = propImpl;
				}
			});
			if (typeof desc.get !== __und__) {
				delete desc.value;
				delete desc.writable;
			}
			if (typeof desc.set !== __und__) {
				delete desc.writable;
			}
			Object.defineProperty(target, propName, desc);
		});
	});
})(
	[Element.prototype, Document.prototype, DocumentFragment.prototype],
	[
		{
			/*
			 * Source: https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append
			 * Original Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md
			 */
			prop: 'append',
			accessors: 'value',
			impl: function append() {
					console.$nativeImpl.log('.append() { console.log(this); } => ' + this);
					var argArr = Array.prototype.slice.call(arguments),
						docFrag = document.createDocumentFragment();
					argArr.forEach(function (argItem) {
						var isNode = argItem instanceof Node;
						docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
					});
					this.appendChild(docFrag);
					return this;
				}
		},
		{
			prop: 'empty',
			accessors: 'value',
			impl: function empty() {
					while (this.childNodes.length) this.removeChild(this.firstChild);
					return this;
				}
		},
		{
			prop: 'html',
			accessors: 'value',
			impl: function html() {
					if (arguments.length) {
						switch(typeof arguments[0]) {
							case 'string':
								this.innerHTML = arguments[0];
								break;
							default:
								this.empty();
								this.append(arguments[0]);
						}
						return this;
					} else {
						return this.innerHTML;
					}
				}
		}
	]
);

// doc.gEBI() & createElementFromHTML() Wrapper
$ = function() {
	if (!arguments.length) return;
	if (typeof arguments[0] === 'string') {
		var arg = arguments[0];
		if (/\s/.test(arg)) {
			/*
			 * function createElementFromHTML()
			 * Source: https://stackoverflow.com/a/494348/3865919
			 * Author: Crescent Fresh <https://stackoverflow.com/users/45433/crescent-fresh>
			 */
			try {
				var tmpDiv = document.createElement('div');
				tmpDiv.innerHTML = arg.trim();
				return tmpDiv.firstChild;
			} catch(e) {
				return null;
			}
		} else {
			return document.getElementById(arg);
		}
	} else if (arguments[0] instanceof Element) {
		return arguments[0];
	} else return null;
};
$.addCSS = function(css, media) {
	/*
	 * Rewrite of own's addStyle() using code from...
	 * Source: https://stackoverflow.com/a/524721
	 * Author: Christoph <https://stackoverflow.com/users/48015/christoph>
	 */
	if (typeof(GM_addStyle) !== 'undefined' && !media) {
		GM_addStyle(css);
	} else {
		if (!media) { media = 'all'; }
		var head = document.head || document.getElementsByTagName('head')[0],
			styleNode = document.createElement('style');
		styleNode.type = 'text/css';
		if (media) styleNode.media = media;
		if (styleNode.styleSheet){
			// This is required for IE8 and below.
			styleNode.styleSheet.cssText = css;
		} else {
			styleNode.appendChild(document.createTextNode(css));
		}
		head.appendChild(styleNode);
	}
};

// console.{} implementation
(function(_) {
	// make sure we don't override native implementation more than once
	if (_.console.clear.toString() !== 'function clear() { [native code] }') { return; }
	var __fn__ = 'function',
		consoleId = 'console-log',
		consoleLine = '<p id="%" class="*-line"></p>',
		_console = _.console || {},
		methods = ['log', 'info', 'warn', 'error', 'trace'];
	_.console = {
		$init: function() {
			if ($(consoleId)) { return; }
			var container = '<div id="console-log" class="scrollbar"></div>',
				css = 
					'body {\n' +
					'	/*background: #1F252D;\n*/' +
					'}\n' +
					'#console-log {\n' +
					'	font-family: "SF Mono", Monaco, "Andale Mono", "Lucida Console", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;\n' +
					'	font-size: 12px;\n' +
					'	font-variant-ligatures: contextual;\n' +
					'	color: rgb(207, 208, 210);\n' +
					'	background: #313B47;\n' +
					'	border: 3px solid #1B2027;\n' +
					'	border-radius: 5px;\n' +
					'	padding: 10px; \n' +
					'	margin: 5px;\n' +
					'	width: 93%;\n' +
					'	min-height: 50px;\n' +
					'	max-height: 150px;\n' +
					'	position: absolute;\n' +
					'	bottom: 5px;\n' +
					'	overflow: scroll;\n' +
					'}\n' +
					'.log-line,\n' +
					'.info-line,\n' +
					'.warn-line,\n' +
					'.error-line,\n' +
					'.trace-line {\n' +
					'	font-family: monospace;\n' +
					'	margin: 2px;\n' +
					'	white-space: nowrap;\n' +
					'}\n' +
					'.log-line {\n' +
					'	color: rgb(207, 208, 210);\n' +
					'}\n' +
					'.info-line {\n' +
					'	color: #6ce890;\n' +
					'}\n' +
					'.warn-line {\n' +
					'	color: #f8b068;\n' +
					'}\n' +
					'.error-line {\n' +
					'	color: #ff4f68;\n' +
					'	font-weight: bold;\n' +
					'}\n' +
					'.trace-line {\n' +
					'	color: #b896ed;\n' +
					'}\n' +
					'#console-log.scrollbar::-webkit-scrollbar-track {\n' +
					'	-webkit-box-shadow: inset 0 0 4px rgba(0,0,0,0.3);\n' +
					'	border-radius: 5px;\n' +
					'	background-color: #313B47;\n' +
					'}\n' +
					'#console-log.scrollbar::-webkit-scrollbar {\n' +
					'	width: 10px;\n' +
					'	height: 10px;\n' +
					'	background-color: #313B47;\n' +
					'}\n' +
					'#console-log.scrollbar::-webkit-scrollbar-thumb {\n' +
					'	border-radius: 5px;\n' +
					'	-webkit-box-shadow: inset 0 0 4px rgba(0,0,0,.3);\n' +
					'	background-color: #262E38;\n' +
					'}\n' +
					'#console-log.scrollbar::-webkit-scrollbar-corner {\n' +
					'	background: #262E38;\n' +
					'}\n',
				body = document.body || document.getElementsByTagName('body')[0];
			$.addCSS(css);
			body.append($(container));
		},
		$typedLog: function(logMethod, text) {
			if (methods.indexOf(logMethod) == -1) { return; }
			if (!this.$enabled) { return; }
			this.$init();
			var consoleEl = $(consoleId),
				lineHTML = consoleLine
					.replace(/\*.{0}/, logMethod)
					.replace(/\%/, 'console-line-'+this.$lineCounter),
				newLineEl = $(lineHTML);
			newLineEl.html(text);
			consoleEl.append(newLineEl);
			consoleEl.scrollTop = consoleEl.scrollHeight;
			this.$lineCounter++;
		},
		$enabled: true,
		$echoNative: false,
		$lineCounter: 0,
		$nativeImpl: _console,
		clear: function() {
			this.$init();
			$(consoleId).empty();
		}
	};
	for (var prop in _.console) {
		if (!Object.prototype.hasOwnProperty.call(_.console, prop)) continue;
		if (typeof _.console[prop] !== __fn__) continue;
		_.console[prop] = _.console[prop].bind(_.console);
	}
	methods.forEach(function(method) {
		_.console[method] = function() { this.$typedLog(method,  Array.prototype.slice.call(arguments).join(' ')); }.bind(_.console);
	});
	var doEcho = function(method, args) {
			if (!this.$echoNative && this.$enabled) { return; }
			if (typeof _console.clear !== __fn__ ) { return; }
			if (typeof _console[method] !== __fn__ ) { return; }
			_console[method].apply(this, args);
		}.bind(_.console);
	// wrap methods to include echo calls
	(function(target) {
		for (var prop in target) {
			if (!Object.prototype.hasOwnProperty.call(target, prop)) continue;
			if (typeof target[prop] !== __fn__) continue;
			if (target[prop].$extended) continue;
			if (prop.charAt(0) === '$') continue;
			target[prop] = (function(){
				var _prop = prop,
					_fn = target[prop];
				return function() {
					doEcho(_prop, Array.prototype.slice.call(arguments));
					_fn.apply(_fn, arguments);
				};
			})();
			target[prop].$extended = true;
		}
	})(_.console);
})(window);

/*
 * Examples:
 *
console.$echoNative = true;
console.clear();
console.log("Hello console");
console.info("Need some info?");
console.warn("I might not have what you need though");
console.error("ERROR: Data not found!!");
console.trace("This is somevery long line of unnecessary text his is somevery long line of unnecessary text his is somevery long line of unnecessary text");
 */