|
/*! |
|
* hnl.mobileConsole - javascript mobile console - v1.3.5 - 19/4/2018 |
|
* Adds html console to webpage. Especially useful for debugging JS on mobile devices. |
|
* Supports 'log', 'trace', 'info', 'warn', 'error', 'group', 'groupEnd', 'table', 'assert', 'clear' |
|
* Inspired by code by jakub fiala (https://gist.github.com/jakubfiala/8fe3461ab6508f46003d) |
|
* Licensed under the MIT license |
|
* |
|
* Original author: @hnldesign |
|
* Further changes, comments: @hnldesign |
|
* Copyright (c) 2014-2016 HN Leussink |
|
* Dual licensed under the MIT and GPL licenses. |
|
* |
|
* Info: http://www.hnldesign.nl/work/code/javascript-mobile-console/ |
|
* Demo: http://code.hnldesign.nl/demo/hnl.MobileConsole.html |
|
*/ |
|
|
|
//Polyfills |
|
|
|
//Date.now polyfill |
|
if (!Date.now) { |
|
Date.now = function now() { |
|
return new Date().getTime(); |
|
}; |
|
} |
|
//Array.isArray polyfill |
|
if (typeof Array.isArray === 'undefined') { |
|
Array.isArray = function(obj) { |
|
return Object.prototype.toString.call(obj) === '[object Array]'; |
|
}; |
|
} |
|
//Array.filter polyfill |
|
if (!Array.prototype.filter) { |
|
Array.prototype.filter = function(fun/*, thisArg*/) { |
|
if (this === void 0 || this === null) { |
|
throw new TypeError(); |
|
} |
|
var t = Object(this); |
|
var len = t.length >>> 0; |
|
if (typeof fun !== 'function') { |
|
throw new TypeError(); |
|
} |
|
var res = []; |
|
var thisArg = arguments.length >= 2 ? arguments[1] : void 0; |
|
for (var i = 0; i < len; i++) { |
|
if (i in t) { |
|
var val = t[i]; |
|
if (fun.call(thisArg, val, i, t)) { |
|
res.push(val); |
|
} |
|
} |
|
} |
|
|
|
return res; |
|
}; |
|
} |
|
//Function.bind polyfill |
|
if (!Function.prototype.bind) { |
|
Function.prototype.bind = function(oThis) { |
|
if (typeof this !== 'function') { |
|
// closest thing possible to the ECMAScript 5 |
|
// internal IsCallable function |
|
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); |
|
} |
|
var aArgs = Array.prototype.slice.call(arguments, 1), |
|
fToBind = this, |
|
fNOP = function() {}, |
|
fBound = function() { |
|
return fToBind.apply(this instanceof fNOP |
|
? this |
|
: oThis, |
|
aArgs.concat(Array.prototype.slice.call(arguments))); |
|
}; |
|
if (this.prototype) { |
|
// Function.prototype doesn't have a prototype property |
|
fNOP.prototype = this.prototype; |
|
} |
|
fBound.prototype = new fNOP(); |
|
return fBound; |
|
}; |
|
} |
|
//Array.prototype.indexOf polyfill |
|
// Production steps of ECMA-262, Edition 5, 15.4.4.14 |
|
// Referentie: http://es5.github.io/#x15.4.4.14 |
|
if (!Array.prototype.indexOf) { |
|
Array.prototype.indexOf = function(searchElement, fromIndex) { |
|
var k; |
|
if (this == null) { |
|
throw new TypeError('"this" is null or not defined'); |
|
} |
|
var o = Object(this); |
|
var len = o.length >>> 0; |
|
if (len === 0) { |
|
return -1; |
|
} |
|
var n = +fromIndex || 0; |
|
if (Math.abs(n) === Infinity) { |
|
n = 0; |
|
} |
|
if (n >= len) { |
|
return -1; |
|
} |
|
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); |
|
while (k < len) { |
|
if (k in o && o[k] === searchElement) { |
|
return k; |
|
} |
|
k++; |
|
} |
|
return -1; |
|
}; |
|
} |
|
//String.prototype.trim polyfill |
|
if (!String.prototype.trim) { |
|
String.prototype.trim = function () { |
|
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); |
|
}; |
|
} |
|
//Array.prototype.map polyfill |
|
// Production steps of ECMA-262, Edition 5, 15.4.4.19 |
|
// Reference: http://es5.github.io/#x15.4.4.19 |
|
if (!Array.prototype.map) { |
|
Array.prototype.map = function(callback/*, thisArg*/) { |
|
var T, A, k; |
|
if (this == null) { |
|
throw new TypeError('this is null or not defined'); |
|
} |
|
var O = Object(this); |
|
var len = O.length >>> 0; |
|
if (typeof callback !== 'function') { |
|
throw new TypeError(callback + ' is not a function'); |
|
} |
|
if (arguments.length > 1) { |
|
T = arguments[1]; |
|
} |
|
A = new Array(len); |
|
k = 0; |
|
while (k < len) { |
|
var kValue, mappedValue; |
|
if (k in O) { |
|
kValue = O[k]; |
|
mappedValue = callback.call(T, kValue, k, O); |
|
A[k] = mappedValue; |
|
} |
|
k++; |
|
} |
|
return A; |
|
}; |
|
} |
|
|
|
// DocReady - Fires supplied function when document is ready |
|
if (typeof 'docReady' !== 'function') { |
|
(function (funcName, baseObj) { |
|
// The public function name defaults to window.docReady |
|
// but you can pass in your own object and own function name and those will be used |
|
// if you want to put them in a different namespace |
|
funcName = funcName || 'docReady'; |
|
baseObj = baseObj || window; |
|
var i, len, readyList = [], readyFired = false, readyEventHandlersInstalled = false; |
|
|
|
// call this when the document is ready |
|
// this function protects itself against being called more than once |
|
function ready() { |
|
if (!readyFired) { |
|
// this must be set to true before we start calling callbacks |
|
readyFired = true; |
|
for (i = 0, len = readyList.length; i < len; i = i + 1) { |
|
// if a callback here happens to add new ready handlers, |
|
// the docReady() function will see that it already fired |
|
// and will schedule the callback to run right after |
|
// this event loop finishes so all handlers will still execute |
|
// in order and no new ones will be added to the readyList |
|
// while we are processing the list |
|
readyList[i].fn.call(window, readyList[i].ctx); |
|
} |
|
// allow any closures held by these functions to free |
|
readyList = []; |
|
} |
|
} |
|
|
|
function readyStateChange() { |
|
if (document.readyState === 'complete') { |
|
ready(); |
|
} |
|
} |
|
|
|
// This is the one public interface |
|
// docReady(fn, context); |
|
// the context argument is optional - if present, it will be passed |
|
// as an argument to the callback |
|
baseObj[funcName] = function (callback, context) { |
|
// if ready has already fired, then just schedule the callback |
|
// to fire asynchronously, but right away |
|
if (readyFired) { |
|
setTimeout(function () {callback(context); }, 1); |
|
return; |
|
} |
|
// add the function and context to the list |
|
readyList.push({fn: callback, ctx: context}); |
|
// if document already ready to go, schedule the ready function to run |
|
if (document.readyState === 'complete') { |
|
setTimeout(ready, 1); |
|
} else if (!readyEventHandlersInstalled) { |
|
// otherwise if we don't have event handlers installed, install them |
|
if (document.addEventListener) { |
|
// first choice is DOMContentLoaded event |
|
document.addEventListener('DOMContentLoaded', ready, false); |
|
// backup is window load event |
|
window.addEventListener('load', ready, false); |
|
} else { |
|
// must be IE |
|
document.attachEvent('onreadystatechange', readyStateChange); |
|
window.attachEvent('onload', ready); |
|
} |
|
readyEventHandlersInstalled = true; |
|
} |
|
}; |
|
}('docReady', window)); |
|
} |
|
|
|
//define console variable |
|
var console = window.console; |
|
|
|
var mobileConsole = (function () { |
|
'use strict'; |
|
|
|
//options and other variable containers |
|
var options = { |
|
overrideAutorun: false, |
|
version : '1.3.5', |
|
baseClass : 'mobileConsole_', |
|
animParams: 'all 200ms ease', |
|
browserinfo: { |
|
isMobile: (function (a) { |
|
return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))); |
|
}(navigator.userAgent || navigator.vendor || window.opera)), |
|
browserChrome: /chrome/.test(navigator.userAgent.toLowerCase()), |
|
ffox: /firefox/.test(navigator.userAgent.toLowerCase()) && !/chrome/.test(navigator.userAgent.toLowerCase()), |
|
safari: /safari/.test(navigator.userAgent.toLowerCase()) && !/chrome/.test(navigator.userAgent.toLowerCase()), |
|
trident: /trident/.test(navigator.userAgent.toLowerCase()), |
|
evtLstn: typeof window.addEventListener === 'function' |
|
}, |
|
methods : ['log', 'trace', 'info', 'warn', 'error', 'group', 'groupCollapsed', 'groupEnd', 'table', 'assert', 'time', 'timeEnd', 'clear'], |
|
hideButtons : ['group', 'groupCollapsed', 'groupEnd', 'table', 'assert', 'time', 'timeEnd'], |
|
ratio: 0.4, |
|
paddingLeft: 0, |
|
groupDepth: 0 |
|
}, |
|
messages = { |
|
clear : 'Console was cleared', |
|
empty: '(Empty string)' |
|
}, |
|
status = { |
|
initialized: false, |
|
acActive : false, |
|
acHovered : false, |
|
acInput : '', |
|
timers : {} |
|
}, |
|
history = { |
|
output : { |
|
prevMsg : '', |
|
prevMethod : '', |
|
counter : 0 |
|
}, |
|
input : { |
|
commands : window.sessionStorage ? (sessionStorage.getItem('mobileConsoleCommandHistory') ? JSON.parse(sessionStorage.getItem('mobileConsoleCommandHistory')) : []) : [], |
|
commandIdx: window.sessionStorage ? (sessionStorage.getItem('mobileConsoleCommandHistory') ? JSON.parse(sessionStorage.getItem('mobileConsoleCommandHistory')).length : 0) : 0, |
|
acIdx: 0, |
|
acHovered: false |
|
} |
|
}, |
|
//'backup' original console for reference & internal debugging |
|
missingMethod = function() { return true; }, //method is not supported on this device's original console, return dummy |
|
originalConsole = { |
|
log: (console && typeof console.log === 'function') ? console.log.bind(console) : missingMethod, |
|
info: (console && typeof console.info === 'function') ? console.info.bind(console) : missingMethod, |
|
dir: (console && typeof console.dir === 'function') ? console.dir.bind(console) : missingMethod, |
|
group: (console && typeof console.group === 'function') ? console.group.bind(console) : missingMethod, |
|
groupEnd: (console && typeof console.groupEnd === 'function') ? console.groupEnd.bind(console) : missingMethod, |
|
warn: (console && typeof console.warn === 'function') ? console.warn.bind(console) : missingMethod, |
|
error: (console && typeof console.error === 'function') ? console.error.bind(console) : missingMethod, |
|
trace: (console && typeof console.trace === 'function') ? console.trace.bind(console) : missingMethod, |
|
clear: (console && typeof console.clear === 'function') ? console.clear.bind(console) : missingMethod |
|
}, |
|
// reference variables |
|
mobileConsole, consoleElement, commandLine; |
|
|
|
//helpers for all sub functions |
|
function setCSS(el, css) { |
|
var i; |
|
for (i in css) { |
|
if (css.hasOwnProperty(i)) { |
|
el.style[i] = css[i]; |
|
} |
|
} |
|
return el; |
|
} |
|
function htmlToString(html) { |
|
var string; |
|
try { string = String(html); } catch(e) { string = JSON.stringify(html); } //this should be done differently, but works for now |
|
return string.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/ /g, '\u00a0').replace(/(?:\r\n|\r|\n)/g, '<br />').trim(); |
|
} |
|
function createElem(type, className, css) { |
|
if (!type || typeof setCSS !== 'function') { return; } |
|
var element = setCSS(document.createElement(type), css); |
|
if (className) { element.className = options.baseClass + className; } |
|
return setCSS(element, css); |
|
} |
|
function storeCommand(command) { |
|
if (history) { |
|
history.input.commands.push(encodeURI(command.trim())); |
|
history.input.commandIdx = history.input.commands.length; |
|
if (window.sessionStorage) { sessionStorage.setItem('mobileConsoleCommandHistory', JSON.stringify(history.input.commands)); } |
|
} |
|
} |
|
function valBetween(val, min, max) { |
|
return (Math.min(max, Math.max(min, val))); |
|
} |
|
function getMaxHeight() { |
|
return valBetween(Math.floor((window.innerHeight || document.documentElement.clientHeight) * options.ratio), 55, 300); |
|
} |
|
function getClass(item) { |
|
var returnVal = ''; |
|
if (item && item.constructor) { |
|
returnVal = item.constructor.name; |
|
} else { |
|
returnVal = Object.prototype.toString.call(item); |
|
} |
|
return String(returnVal); |
|
} |
|
|
|
// elements |
|
var elements = { |
|
lines: [], |
|
acItems: [], |
|
base: createElem('div', 'base', { |
|
boxSizing: 'border-box', |
|
position: 'fixed', |
|
resize: 'none', |
|
fontSize: '12px', |
|
lineHeight: '14px', |
|
bottom: 0, |
|
top: 'auto', |
|
right: 0, |
|
width: '100%', |
|
zIndex: 10000, |
|
padding: 0, |
|
paddingBottom: options.browserinfo.isMobile ? '35px' : '25px', |
|
margin: 0, |
|
border: '0 none', |
|
borderTop: '1px solid #808080', |
|
backgroundColor: '#ffffff' |
|
}), |
|
topbar : createElem('div', 'topbar', { |
|
boxSizing: 'border-box', |
|
position: 'absolute', |
|
height: '28px', |
|
left: 0, |
|
right: 0, |
|
display: 'block', |
|
padding: '0 2px', |
|
overflow: 'hidden', |
|
webkitOverflowScrolling: 'touch', |
|
color: '#444444', |
|
backgroundColor: '#f3f3f3', |
|
border: '0 none', |
|
borderTop: '1px solid #a3a3a3', |
|
borderBottom: '1px solid #a3a3a3', |
|
whiteSpace: 'nowrap', |
|
overflowX: 'auto' |
|
}), |
|
scrollcontainer : createElem('div', 'scroller', { |
|
boxSizing: 'border-box', |
|
border: '0 none', |
|
fontFamily: 'Consolas, monaco, monospace', |
|
position: 'relative', |
|
display: 'block', |
|
height: getMaxHeight() + 'px', |
|
overflow: 'auto', |
|
webkitOverflowScrolling: 'touch', |
|
'-webkit-transition': options.animParams, |
|
'-moz-transition': options.animParams, |
|
'-o-transition': options.animParams, |
|
'transition': options.animParams |
|
}), |
|
table : createElem('table', 'table', { |
|
border: '0 none', |
|
margin: 0, |
|
position: 'relative', |
|
tableLayout: 'auto', |
|
width: '100%', |
|
borderCollapse: 'collapse' |
|
}), |
|
stackTraceTable : createElem('table', 'stackTraceTable', { |
|
border: '0 none', |
|
margin: 0, |
|
display: 'none', |
|
marginLeft: '10px', |
|
marginTop: options.browserinfo.isMobile ? '8px' : '4px', |
|
tableLayout: 'auto', |
|
maxWidth: '100%', |
|
color: '#333333' |
|
}), |
|
tr : createElem('tr', 'table_row', { |
|
verticalAlign: 'top' |
|
}), |
|
td : createElem('td', 'table_row', { |
|
border: '0 none', |
|
padding: '2px 4px', |
|
verticalAlign: 'top' |
|
}), |
|
msgContainer : createElem('span', 'msgContainer', { |
|
border: '0 none', |
|
margin: 0, |
|
display: 'inline', |
|
overflow: 'hidden' |
|
}), |
|
tdLeft : createElem('td', 'table_row_data', { |
|
border: '0 none', |
|
textAlign: 'left', |
|
padding: options.browserinfo.isMobile ? '8px 12px' : '4px 8px' |
|
}), |
|
tdRight : createElem('td', 'table_row_data', { |
|
border: '0 none', |
|
textAlign: 'left', |
|
padding: options.browserinfo.isMobile ? '8px 12px' : '4px 8px', |
|
whiteSpace: 'nowrap', |
|
overflow: 'hidden' |
|
}), |
|
link : createElem('a', 'link', { |
|
color: '#1155cc', |
|
textDecoration: 'underline' |
|
}), |
|
dot : createElem('div', 'table_row_data_dot', { |
|
display: 'inline', |
|
borderRadius: '50%', |
|
fontSize: '80%', |
|
fontWeight: 'bold', |
|
padding: '2px 5px', |
|
textAlign: 'center', |
|
marginRight: '5px', |
|
backgroundColor: '#333333', |
|
color: '#ffffff' |
|
}), |
|
button : createElem('button', 'button', { |
|
display: 'inline-block', |
|
fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif', |
|
fontWeight: 'normal', |
|
textTransform: 'capitalize', |
|
fontSize: '12px', |
|
lineHeight: '26px', |
|
height: '26px', |
|
padding: '0 8px', |
|
margin: 0, |
|
textAlign: 'center', |
|
marginRight: '5px', |
|
border: '0 none', |
|
backgroundColor: 'transparent', |
|
color: 'inherit', |
|
cursor: 'pointer' |
|
}), |
|
buttons : { |
|
}, |
|
input : createElem('div', 'input', { |
|
boxSizing: 'border-box', |
|
height: options.browserinfo.isMobile ? '35px' : '29px', |
|
fontFamily: 'Consolas, monaco, monospace', |
|
position: 'absolute', |
|
bottom: 0, |
|
left: 0, |
|
right: 0, |
|
margin: 0, |
|
border: '0 none', |
|
borderTop: '1px solid #EEEEEE' |
|
}), |
|
gt : createElem('DIV', 'gt', { |
|
position: 'absolute', |
|
bottom: 0, |
|
width: '25px', |
|
lineHeight: options.browserinfo.isMobile ? '34px' : '28px', |
|
height: options.browserinfo.isMobile ? '34px' : '28px', |
|
textAlign: 'center', |
|
fontSize: '16px', |
|
fontFamily: 'Consolas, monaco, monospace', |
|
fontWeight: 'bold', |
|
color: '#3577B1', |
|
zIndex: 2 |
|
}), |
|
consoleinput : createElem('input', 'consoleinput', { |
|
boxSizing: 'border-box', |
|
position: 'absolute', |
|
bottom: 0, |
|
width : '100%', |
|
fontSize: options.browserinfo.isMobile ? '16px' : 'inherit', //prevents ios safari's zoom on focus |
|
fontFamily: 'Consolas, monaco, monospace', |
|
paddingLeft: '25px', |
|
margin: 0, |
|
height: options.browserinfo.isMobile ? '35px' : '25px', |
|
border: '0 none', |
|
outline: 'none', |
|
outlineWidth: 0, |
|
boxShadow: 'none', |
|
'-moz-appearance': 'none', |
|
'-webkit-appearance': 'none', |
|
backgroundColor: 'transparent', |
|
color: '#000000', |
|
zIndex: 1 |
|
}), |
|
autocomplete : createElem('div', 'autocomplete', { |
|
display: 'none', |
|
position: 'absolute', |
|
bottom: options.browserinfo.isMobile ? '35px' : '28px', |
|
left: 0, |
|
boxShadow: '1px 2px 5px rgba(0,0,0,0.1)', |
|
color: '#000000', |
|
backgroundColor: '#FFFFFF', |
|
border: '1px solid #b5b5b5' |
|
}), |
|
autocompleteItem : createElem('a', 'autocompleteitem', { |
|
display: 'block', |
|
textDecoration: 'none', |
|
fontSize: options.browserinfo.isMobile ? '16px' : 'inherit', |
|
padding: '5px 8px', |
|
wordWrap: 'break-word', |
|
whiteSpace: 'nowrap' |
|
}), |
|
arrowUp: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAD///93d3eMZ/YKAAAAAnRSTlMAAHaTzTgAAAAdSURBVHgBY2CEAAYgYAJiEMXEBKHADCYIgKmD0QAFdAA2OHJXEwAAAABJRU5ErkJggg==">', |
|
arrowDown: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAG0lEQVR42mNgwAfKy8v/48I4FeA0AacVDFQBAP9wJkE/KhUMAAAAAElFTkSuQmCC">', |
|
arrowRight: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAJUlEQVR42mNgAILy8vL/DLgASBKnApgkVgXIkhgKiNKJ005s4gDLbCZBiSxfygAAAABJRU5ErkJggg==">' |
|
}; |
|
|
|
//shared functions |
|
|
|
var setLineStyle = (function () { |
|
var lineStyles = function (style) { |
|
switch (style) { |
|
case 'log': |
|
return { |
|
text : { |
|
borderBottom: '1px solid #DDDDDD', |
|
color: '#000000' |
|
}, |
|
dot : { |
|
color: '#FFFFFF', |
|
backgroundColor: '#8097bd' |
|
} |
|
}; |
|
case 'info': |
|
return { |
|
text : { |
|
borderBottom: '1px solid #DDDDDD', |
|
color: '#1f3dc4' |
|
}, |
|
dot : { |
|
color: '#FFFFFF', |
|
backgroundColor: '#367AB4' |
|
} |
|
}; |
|
case 'warn': |
|
return { |
|
text : { |
|
borderBottom: '1px solid #DDDDDD', |
|
color: '#CE8724', |
|
backgroundColor : '#fff6e0' |
|
}, |
|
dot : { |
|
color: '#FFFFFF', |
|
backgroundColor: '#e8a400' |
|
} |
|
}; |
|
case 'error': |
|
case 'table': |
|
return { |
|
text : { |
|
borderBottom: '1px solid #DDDDDD', |
|
color: '#FF0000', |
|
backgroundColor : '#ffe5e5' |
|
}, |
|
dot : { |
|
color: '#FFFFFF', |
|
backgroundColor: '#FF0000' |
|
} |
|
}; |
|
case 'assert': |
|
return { |
|
text : { |
|
borderBottom: '1px solid #DDDDDD', |
|
color: '#FF0000', |
|
backgroundColor : '#ffe5e5' |
|
}, |
|
dot : { |
|
color: '#FFFFFF', |
|
backgroundColor: '#FF0000' |
|
} |
|
}; |
|
case 'trace': |
|
return { |
|
text : { |
|
borderBottom: '1px solid #DDDDDD', |
|
color: '#000000' |
|
}, |
|
dot : { |
|
//will not happen |
|
} |
|
}; |
|
case 'time': |
|
case 'timeEnd': |
|
return { |
|
text : { |
|
borderBottom: '1px solid #DDDDDD', |
|
color: '#0000ff' |
|
}, |
|
dot : { |
|
color: '#FFFFFF', |
|
backgroundColor: '#0000ff' |
|
} |
|
}; |
|
default: |
|
return { |
|
text : { |
|
borderBottom: '1px solid #DDDDDD', |
|
color: '#000000' |
|
}, |
|
dot : { |
|
color: '#FFFFFF', |
|
backgroundColor: '#8097bd' |
|
} |
|
}; |
|
} |
|
|
|
}; |
|
var color, dot; |
|
|
|
return function (element, type, msg) { |
|
if (status.initialized) { |
|
color = (typeof msg === 'undefined' || msg === htmlToString(messages.empty)) ? {color: '#808080'} : ((msg === htmlToString(messages.clear)) ? {color: '#808080', fontStyle: 'italic'} : (lineStyles(type) !== undefined ? lineStyles(type).text : lineStyles.log.text)); |
|
dot = typeof lineStyles(type) !== 'undefined' ? lineStyles(type).dot : lineStyles.log.dot; |
|
setCSS(element, color); |
|
//has dot? |
|
if (element.childNodes[0].childNodes[0].className.indexOf('dot') !== -1) { |
|
setCSS(element.childNodes[0].childNodes[0], lineStyles(type).dot); |
|
} |
|
} |
|
}; |
|
}()), |
|
getLink = function (href, textString) { |
|
var HTMLurl = elements.link.cloneNode(false); |
|
if (href) { |
|
HTMLurl.setAttribute('href', href); |
|
HTMLurl.setAttribute('target', '_blank'); |
|
} |
|
HTMLurl.innerHTML = textString || href.split('\\').pop().split('/').filter(Boolean).pop(); |
|
return HTMLurl; |
|
}, |
|
toggleHeight = function () { |
|
if (status.initialized) { |
|
var existingPadding = parseInt(document.body.style.paddingBottom, 10) - Math.abs(elements.base.offsetHeight + elements.topbar.offsetHeight); |
|
var newHeight = (elements.base.minimized) ? getMaxHeight() + 'px' : '0px'; |
|
setCSS(elements.scrollcontainer, { |
|
height: newHeight |
|
}); |
|
setCSS(document.body, { |
|
paddingBottom: existingPadding + Math.abs(parseInt(newHeight, 10) + elements.topbar.offsetHeight) + 'px' |
|
}); |
|
elements.buttons.toggler.innerHTML = (elements.base.minimized) ? elements.arrowDown : elements.arrowUp; |
|
elements.buttons.toggler.setAttribute('title', (elements.base.minimized) ? 'Minimize console' : 'Maximize console'); |
|
elements.base.minimized = !elements.base.minimized; |
|
return elements.base.minimized; |
|
} |
|
return 'Not built!'; |
|
}, |
|
about = (function () { |
|
return function () { |
|
console.info( |
|
'--==## Mobile Console ' + (status.initialized ? 'active' : 'inactive') + ' ##==--' + '\n' + |
|
'--===============================--' + '\n' + |
|
'MobileConsole v' + options.version + ', running on ' + navigator.userAgent.toLowerCase() |
|
); |
|
}; |
|
}()); |
|
|
|
// --==** sub functions start here **==-- |
|
|
|
//initializes the console HTML element |
|
function initConsoleElement() { |
|
//reference |
|
var ref; |
|
//core |
|
function toggleScroll() { |
|
elements.scrollcontainer.scrollTop = elements.scrollcontainer.scrollHeight; |
|
elements.scrollcontainer.scrollLeft = 0; |
|
} |
|
function destroyConsole() { |
|
//conan the destroyer. Very basic; just removes the console element. mobileConsole will still 'pipe' console logging |
|
//don't see any reason for now to reverse that. |
|
elements.base.parentNode.removeChild(elements.base); |
|
status.initialized = false; |
|
console.warn( |
|
'--==## Mobile Console DESTROYED ##==--' + '\n' + |
|
'To enable again: reload the page. Tip: use the minimize button instead of closing.' |
|
); |
|
} |
|
function assemble() { |
|
var i = options.methods.length, key; |
|
|
|
//add buttons |
|
while (i--) { |
|
elements.buttons[options.methods[i]] = elements.button.cloneNode(false); |
|
elements.buttons[options.methods[i]].innerHTML = options.methods[i].charAt(0).toUpperCase() + options.methods[i].slice(1); |
|
elements.buttons[options.methods[i]].setAttribute('title', (options.methods[i] !== 'clear') ? 'Toggle the display of ' + options.methods[i] + ' messages' : 'Clear the console'); |
|
} |
|
//add min/maximize button |
|
elements.buttons.toggler = elements.button.cloneNode(false); |
|
elements.buttons.toggler.innerHTML = elements.arrowDown; |
|
elements.buttons.toggler.setAttribute('title', 'Minimize console'); |
|
//add close button |
|
elements.buttons.closer = elements.button.cloneNode(false); |
|
elements.buttons.closer.innerHTML = '✕'; |
|
elements.buttons.closer.setAttribute('title', 'Close (destroy) console'); |
|
setCSS(elements.buttons.closer, { float: 'right', margin: '0'}); |
|
|
|
//assemble everything |
|
for (key in elements.buttons) { |
|
if (elements.buttons.hasOwnProperty(key)) { |
|
elements.topbar.insertBefore(elements.buttons[key], elements.topbar.firstChild); |
|
} |
|
} |
|
elements.scrollcontainer.appendChild(elements.table); |
|
|
|
elements.base.appendChild(elements.topbar); |
|
elements.base.appendChild(elements.scrollcontainer); |
|
|
|
status.initialized = true; |
|
return elements.base; |
|
} |
|
function attach(console) { |
|
document.body.appendChild(console); |
|
setCSS(elements.topbar, { |
|
top: -Math.abs(elements.topbar.offsetHeight) + 'px' |
|
}); |
|
var existingPadding = isNaN(parseInt(document.body.style.paddingBottom, 10)) ? 0 : parseInt(document.body.style.paddingBottom, 10); |
|
setCSS(document.body, { |
|
paddingBottom: existingPadding + Math.abs(console.offsetHeight + elements.topbar.offsetHeight) + 'px' |
|
}); |
|
elements.scrollcontainer.scrollTop = elements.scrollcontainer.scrollHeight; |
|
|
|
return elements.base; |
|
} |
|
function toggleLogType(e) { |
|
var button = e.currentTarget || e.srcElement; |
|
var logType = button.innerHTML.toLowerCase(); |
|
var elems = elements.lines[logType], i = elems.length; |
|
button.toggled = (typeof button.toggled === 'undefined') ? true : !button.toggled; |
|
setCSS(button, { opacity: (button.toggled) ? '0.5' : '' }); |
|
while (i--) { |
|
setCSS(elems[i], { display: (button.toggled) ? 'none' : '' }); |
|
} |
|
toggleScroll(); |
|
button.blur(); |
|
return button; |
|
} |
|
function setBinds() { |
|
var methods = options.methods, i = methods.length; |
|
while (i--) { |
|
if (methods[i] !== 'clear') { |
|
if (options.browserinfo.evtLstn) { |
|
elements.buttons[methods[i]].addEventListener('click', toggleLogType, false); |
|
} else { |
|
elements.buttons[methods[i]].attachEvent('onclick', toggleLogType); |
|
} |
|
} |
|
if (options.hideButtons.indexOf(methods[i]) !== -1) { //hide buttons that we don't want |
|
setCSS(elements.buttons[methods[i]], { display: 'none' }); |
|
} |
|
} |
|
if (options.browserinfo.evtLstn) { |
|
elements.buttons.toggler.addEventListener('click', toggleHeight, false); |
|
elements.buttons.closer.addEventListener('click', destroyConsole, false); |
|
elements.buttons.clear.addEventListener('click', console.clear, false); |
|
} else { |
|
elements.buttons.toggler.attachEvent('onclick', toggleHeight); |
|
elements.buttons.closer.attachEvent('onclick', destroyConsole); |
|
elements.buttons.clear.attachEvent('onclick', console.clear); |
|
} |
|
} |
|
//init |
|
function init() { |
|
var element = assemble(); |
|
docReady(function () { |
|
setBinds(); |
|
attach(element); |
|
}); |
|
//expose Public methods and variables |
|
return { |
|
toggleHeight : toggleHeight, |
|
toggleScroll : toggleScroll, |
|
destroy: destroyConsole |
|
}; |
|
} |
|
if (!ref) { |
|
ref = init(); |
|
} |
|
return ref; |
|
} |
|
|
|
//initializes the new console logger |
|
function initConsole() { |
|
//reference |
|
var ref; |
|
//sub helpers |
|
function isEmpty(obj) { |
|
for(var prop in obj) { |
|
if(obj.hasOwnProperty(prop)) |
|
return false; |
|
} |
|
return JSON.stringify(obj) === JSON.stringify({}); |
|
} |
|
function isElement(o) { |
|
return ( |
|
typeof HTMLElement === 'object' ? o instanceof HTMLElement : //DOM2 |
|
o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string' |
|
); |
|
} |
|
function objectToString(object) { |
|
var prop, output = ''; |
|
if (!isElement(object)) { |
|
for (prop in object) { |
|
if (!object.hasOwnProperty(prop)) { |
|
continue; |
|
} else if (typeof (object[prop]) === 'object') { |
|
output += prop + ((Array.isArray(object[prop])) ? ': Array(' + object[prop].length + ')' : ': {…}'); |
|
} else if (typeof (object[prop]) === 'function') { |
|
output += 'func: f'; |
|
} else { |
|
output += prop + ': <span style="color:#c54300;">"' + object[prop] + '"</span>'; |
|
} |
|
output += ', '; |
|
} |
|
return '<em>{' + output.slice(0, -2) + '}</em>'; // returns cleaned up JSON |
|
} |
|
return htmlToString(object.outerHTML); |
|
} |
|
function urlFromString(string) { |
|
string = String(string); |
|
//searches for url in string, returns url as string |
|
var match, uriPattern = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig; |
|
try { |
|
match = string.match(uriPattern)[0]; |
|
return match; |
|
} catch (e) { |
|
return ''; |
|
} |
|
} |
|
function filterOut(array, match) { |
|
return array.filter(function(item){ |
|
return typeof item === 'string' && item.indexOf(match) === -1; |
|
}); |
|
} |
|
function preFilterTrace(array) { |
|
var newArray = array.split('\n').filter(Boolean), //filter cleans out empty values |
|
isCommandLine = false, stealthThese, i; |
|
if (newArray[0].indexOf('http') === -1) { newArray.shift(); } //remove first line if contains no 'http' (Chrome starts with 'Error', Firefox doesn't..) |
|
if (newArray[0].indexOf('console.') !== -1 || newArray[0].indexOf('console[method]') !== -1) { newArray.shift(); } |
|
if (newArray.length > 0) { |
|
isCommandLine = newArray[newArray.length - 1].indexOf('keydown') !== -1; |
|
newArray = newArray.filter(function(item){ return item !== ''; }); |
|
|
|
if (isCommandLine) { |
|
stealthThese = ['submitCommand', 'eval', 'setBinds', 'interceptConsole', 'newConsole']; |
|
newArray.pop(); //remove last index, as it is the keydown event. |
|
i = stealthThese.length; |
|
while(i--) { |
|
newArray = filterOut(newArray, stealthThese[i]); |
|
} |
|
} |
|
} |
|
if (isCommandLine || newArray.length === 0) { |
|
newArray.push('(anonymous function) console:1:1'); |
|
} |
|
return newArray; |
|
} |
|
//core |
|
function formatStackTrace(trace, origtrace) { |
|
var callStack = []; |
|
//original stack is hidden inside trace object, if specified |
|
var stackTraceOrig = (typeof trace !== 'undefined' && typeof trace[4] !== 'undefined') ? trace[4].stack : undefined; |
|
//if the first line contains this, skip it. Meant for browsers that begin the stack with the error message itself (already captured before formatStackTrace) |
|
var traceToProcess = (origtrace && origtrace !== '') ? origtrace : stackTraceOrig, |
|
i, |
|
lines, |
|
url, |
|
txt, |
|
thisLine, |
|
lineAndColumn, |
|
caller, |
|
separator = options.browserinfo.ffox ? '@' : '()'; |
|
|
|
//stop if no source trace can be determined |
|
if (!traceToProcess) { return; } |
|
|
|
lines = preFilterTrace(traceToProcess); //pre filters all lines by filtering out all mobileConsole's own methods so mobileConsole runs Stealth and unobtrusive |
|
i = lines.length; |
|
while (i--) { |
|
thisLine = lines[i].trim(); |
|
lineAndColumn = thisLine.match(/(?::)(\d+)(?::)(\d+)/); |
|
url = urlFromString(thisLine).replace(lineAndColumn[0], '').split('#')[0] || ''; |
|
caller = htmlToString(thisLine.replace(urlFromString(thisLine), '').replace(separator, '').replace('at ', '').trim()); |
|
if (caller === '' || caller === lineAndColumn[0]) { continue; } |
|
if (url[url.length - 1] === '/') { |
|
txt = '(index)'; |
|
} else { |
|
txt = url.split('\\').pop().split('/').filter(Boolean).pop() || caller; |
|
} |
|
callStack.push({ |
|
caller: caller, |
|
url: url ? url.split(':')[0] + ':' + url.split(':')[1] : caller, |
|
linkText: txt + lineAndColumn[0], |
|
line: lineAndColumn[1], |
|
col: lineAndColumn[2], |
|
originalLine: thisLine |
|
}); |
|
} |
|
return callStack; |
|
} |
|
function traceToTable(table, trace) { |
|
var i, tdLeft, tdRight, tr; |
|
if (typeof trace === 'undefined') { |
|
return; |
|
} |
|
trace.reverse(); //reverse order of trace, just like in a browser's console |
|
i = trace.length; |
|
while (i--) { |
|
tdLeft = elements.td.cloneNode(false); |
|
tdRight = elements.td.cloneNode(false); |
|
tr = elements.tr.cloneNode(false); |
|
tdLeft.innerHTML = trace[i].caller; |
|
tdRight.innerHTML = ' @ '; |
|
tdRight.appendChild(getLink((trace[i].url || ''), trace[i].linkText)); |
|
tr.appendChild(tdLeft); |
|
tr.appendChild(tdRight); |
|
table.insertBefore(tr, table.firstChild); |
|
} |
|
return table; |
|
} |
|
function colorizeData(key, value) { |
|
var valueColor = '#3c53da', keyColor = '#ae33b7', classname = getClass(value); |
|
if (value && classname.indexOf('HTML') !== -1) { |
|
value = htmlToString(value.outerHTML); |
|
valueColor = '#ad8200'; |
|
} else if (key === 'innerHTML' || key === 'outerHTML') { |
|
value = htmlToString(value); |
|
valueColor = '#ad8200'; |
|
} |
|
if (typeof value === 'string') { |
|
valueColor = '#c54300'; |
|
//HARD limit, for speed/mem issues with consecutive logging of large strings |
|
if (value.length > 400) { |
|
value = '"' + String(value).substring(0, 400) + '" [...] <br/><span style="color:#FF0000;text-decoration: underline;">Note: string was truncated to 400 chars</span>'; |
|
} else { |
|
value = '"' + value + '"'; |
|
} |
|
} else if (value === null) { |
|
valueColor = '#808080'; |
|
value = 'null'; |
|
} else if (typeof value === 'undefined' || value === undefined) { |
|
valueColor = '#808080'; |
|
value = 'undefined'; |
|
} else if (typeof value === 'object') { |
|
if (isEmpty(value)) { |
|
value = '{}'; |
|
} else { |
|
valueColor = ''; |
|
//iterate over object to create another table inside |
|
var tempTable = createElem('table', 'stackTraceSubTable', { |
|
border: '0 none', |
|
margin: 0, |
|
display: 'none', |
|
marginLeft: '10px', |
|
marginTop: options.browserinfo.isMobile ? '8px' : '4px', |
|
tableLayout: 'auto', |
|
maxWidth: '100%', |
|
color: '#333333' |
|
}), |
|
wrap = document.createElement('div'); |
|
wrap.appendChild(objectToTable(tempTable, value).cloneNode(true)); |
|
if (Array.isArray(value)) { |
|
value = 'Array(' + value.length + ')' + wrap.innerHTML; |
|
} else { |
|
value = wrap.innerHTML; |
|
} |
|
} |
|
} |
|
|
|
return '<span style="color:' + keyColor + ';">' + key + ':</span> <span style="color:' + valueColor + ';">' + value + '</span>'; |
|
} |
|
function objectToTable(table, object) { |
|
var i, tdLeft, tr; |
|
if (isElement(object)){ |
|
tdLeft = elements.td.cloneNode(false); tr = elements.tr.cloneNode(false); |
|
tdLeft.innerHTML = htmlToString(object.outerHTML); |
|
tr.appendChild(tdLeft); |
|
table.appendChild(tr); |
|
} else { |
|
for (i in object) { |
|
if (object.hasOwnProperty(i)) { |
|
tdLeft = elements.td.cloneNode(false); tr = elements.tr.cloneNode(false); |
|
tdLeft.innerHTML = colorizeData(i, object[i]); |
|
tr.appendChild(tdLeft); |
|
table.appendChild(tr); |
|
} |
|
} |
|
} |
|
return table; |
|
} |
|
function toggleDetails(e) { |
|
var button = e.currentTarget || e.srcElement, i, hidden; |
|
if (button.getAttribute('toggles') === 'table') { |
|
var tables = button.parentElement.getElementsByTagName('table'); |
|
i = tables.length; |
|
while (i--) { |
|
hidden = (tables[i].currentStyle ? tables[i].currentStyle.display : window.getComputedStyle(tables[i], null).display) === 'none'; |
|
button.innerHTML = button.innerHTML.replace((hidden ? elements.arrowRight : elements.arrowDown), (hidden ? elements.arrowDown : elements.arrowRight)); |
|
setCSS(tables[i], { display: hidden ? 'table' : 'none' }); |
|
} |
|
} |
|
} |
|
function isRepeat(message, method) { |
|
return (history.output.prevMsg === message && history.output.prevMethod === method) && (typeof message !== 'object') && (method !== 'trace') && (method !== 'group') && (method !== 'groupCollapsed') && (method !== 'groupEnd'); |
|
} |
|
function newConsole() { |
|
try { |
|
//get arguments, set vars |
|
var method = arguments[0], className, isHTMLElement, |
|
message = (typeof arguments[1].newMessage !== 'undefined') ? arguments[1].newMessage : undefined, |
|
stackTrace = (typeof arguments[1].newStackTrace !== 'undefined') ? arguments[1].newStackTrace : undefined; |
|
|
|
//if message emtpy or undefined, show empty message-message |
|
if (message === '' || typeof message === 'undefined' || message === undefined) { message = messages.empty; } |
|
|
|
if (isRepeat(message, method) && method.indexOf('time') === -1) { |
|
// up the counter and add the dot |
|
history.output.counter = history.output.counter + 1; |
|
elements.table.lastChild.countDot = elements.table.lastChild.countDot || elements.dot.cloneNode(false); |
|
elements.table.lastChild.firstChild.insertBefore(elements.table.lastChild.countDot, elements.table.lastChild.firstChild.firstChild).innerHTML = history.output.counter; |
|
setLineStyle(elements.table.lastChild, method, message); |
|
} else { |
|
history.output.prevMsg = message; |
|
history.output.prevMethod = method; |
|
history.output.counter = 1; |
|
|
|
//an object requires some more handling |
|
if (typeof message === 'object' && method !== 'assert' && method !== 'timeEnd') { |
|
message = isElement(message) ? |
|
htmlToString(message.outerHTML.match(/<(.*?)>/g)[0] + '...' + message.outerHTML.match(/<(.*?)>/g).pop()) : //gets e.g. <div>...</div> |
|
objectToString(message); |
|
} else if (method !== 'assert' && method.indexOf('time') === -1) { |
|
message = htmlToString(message); |
|
} |
|
|
|
var detailTable, |
|
stackTable, |
|
msgContainer = elements.msgContainer.cloneNode(false), |
|
lineContainer = elements.tr.cloneNode(false), |
|
leftContainer = elements.tdLeft.cloneNode(true), |
|
rightContainer = elements.tdRight.cloneNode(false), |
|
arrows = stackTrace ? elements.arrowRight + ' ' : ''; |
|
|
|
switch (method) { |
|
case 'assert': |
|
if (message[0] === false) { |
|
msgContainer.innerHTML = arrows + 'Assertion failed: ' + message[1]; |
|
} |
|
stackTable = traceToTable(elements.stackTraceTable.cloneNode(false), stackTrace); |
|
method = 'error'; //groups it under 'error' and is thus toggleable in view |
|
break; |
|
case 'log': |
|
case 'debug': |
|
case 'info': |
|
case 'warn': |
|
if (typeof arguments[1].newMessage === 'object') { |
|
detailTable = objectToTable(elements.stackTraceTable.cloneNode(false), arguments[1].newMessage); |
|
msgContainer.innerHTML = elements.arrowRight + ' ' + message; |
|
} else { |
|
msgContainer.innerHTML = message; |
|
} |
|
break; |
|
case 'error': |
|
case 'trace': |
|
case 'dir': |
|
case 'table': |
|
//left side |
|
if (method === 'table' || typeof arguments[1].newMessage === 'object') { |
|
detailTable = objectToTable(elements.stackTraceTable.cloneNode(false), arguments[1].newMessage); |
|
msgContainer.innerHTML = elements.arrowRight + ' ' + message; |
|
} else if (method === 'trace') { |
|
message = 'console.trace()'; |
|
msgContainer.innerHTML = arrows + message; |
|
} else { |
|
msgContainer.innerHTML = arrows + message; |
|
} |
|
stackTable = traceToTable(elements.stackTraceTable.cloneNode(false), stackTrace); |
|
break; |
|
case 'group': |
|
case 'groupCollapsed': |
|
case 'groupEnd': |
|
if (method !== 'groupEnd') { |
|
options.groupDepth = options.groupDepth + 1; |
|
msgContainer.innerHTML = '<strong>' + message + '</strong>'; |
|
msgContainer.setAttribute('toggles', 'group_' + options.groupDepth); |
|
} else { |
|
options.groupDepth = valBetween(options.groupDepth - 1, 0, 99); |
|
history.output.prevMsg = ''; |
|
} |
|
if (options.groupDepth > 0) { |
|
options.paddingLeft = (options.groupDepth * 23) + 'px'; |
|
} else { |
|
options.paddingLeft = 0; |
|
} |
|
break; |
|
case 'time': |
|
case 'timeEnd': |
|
var timerName = arguments[1].newMessage || 'default', now, passed; |
|
if (method === 'time') { |
|
status.timers[timerName] = Date.now(); |
|
if (typeof arguments[1].original === 'function') { |
|
arguments[1].original.apply(console, arguments[1].originalArguments); //make sure we still call the original console.time to start the browser's console timer |
|
} |
|
return; |
|
} |
|
now = Date.now(); |
|
if (!status.timers[timerName]) { |
|
console.warn('Timer "' + timerName + '" does not exist.'); |
|
return; |
|
} |
|
passed = now - (status.timers[timerName] || 0); |
|
message = timerName + ': ' + passed + 'ms'; |
|
msgContainer.innerHTML = message; |
|
delete status.timers[timerName]; |
|
break; |
|
default: |
|
msgContainer.innerHTML = message; |
|
} |
|
|
|
if (!msgContainer.innerHTML) { return; } |
|
leftContainer.appendChild(msgContainer); |
|
|
|
if (detailTable || stackTable) { |
|
setCSS(msgContainer, {cursor : 'pointer'}); |
|
leftContainer.appendChild(detailTable || stackTable); |
|
msgContainer.setAttribute('toggles', 'table'); |
|
} |
|
|
|
//populate right side |
|
if (stackTrace && typeof stackTrace[stackTrace.length - 1] !== 'undefined') { |
|
rightContainer.appendChild(setCSS(getLink(stackTrace[0].url, stackTrace[0].linkText), {color: '#808080'})); |
|
} |
|
|
|
//add to line |
|
lineContainer.appendChild(leftContainer); |
|
lineContainer.appendChild(rightContainer); |
|
|
|
//set colors |
|
setCSS(lineContainer, { display: (elements.buttons[method].toggled ? 'none' : '') }); |
|
setLineStyle(lineContainer, method, message); |
|
|
|
//set binds |
|
if (options.browserinfo.evtLstn) { |
|
msgContainer.addEventListener('click', toggleDetails, false); |
|
} else { |
|
msgContainer.attachEvent('onclick', toggleDetails); |
|
} |
|
|
|
//store the lines in the object corresponding to the method used |
|
elements.lines[method].push(lineContainer); |
|
|
|
//handle grouping (group and groupEnd |
|
if (options.paddingLeft !== 0) { |
|
setCSS(leftContainer, {paddingLeft: options.paddingLeft}); |
|
setCSS(msgContainer, {borderLeft: '1px solid #808080', paddingLeft: '5px'}); |
|
} |
|
|
|
//add the line to the table |
|
elements.table.appendChild(lineContainer); |
|
} |
|
//scroll |
|
consoleElement.toggleScroll(); |
|
//========================================================== |
|
//make sure we still call the original method, if applicable (not window.onerror) |
|
if (typeof arguments[1].original === 'function') { |
|
arguments[1].original.apply(console, arguments[1].originalArguments); |
|
} |
|
} catch (e) { |
|
//not logging. why? throw error |
|
if (options.browserinfo.isMobile) { alert(e); } |
|
originalConsole.error('mobileConsole generated an error logging this event! (type: ' + typeof message + ')'); |
|
originalConsole.error(arguments); |
|
originalConsole.error(e); |
|
//try to re-log it as an error |
|
newConsole('error', e); |
|
} |
|
|
|
} |
|
function interceptConsole(method) { |
|
var original = console ? console[method] : missingMethod(), i, stackTraceOrig; |
|
if (!console) { console = {}; } //create empty console if we have no console (IE?) |
|
console[method] = function () { |
|
var args = Array.prototype.slice.call(arguments); |
|
args.original = original; |
|
args.originalArguments = arguments; |
|
args.newMessage = (method === 'assert') ? [args[0], args[1]] : args[0]; |
|
//create an Error and get its stack trace and format it |
|
try { throw new Error(); } catch (e) { stackTraceOrig = e.stack; } |
|
args.newStackTrace = formatStackTrace(args.newStackTrace, stackTraceOrig); |
|
if (method === 'clear') { |
|
try { |
|
elements.table.innerHTML = ''; |
|
} catch (e) { |
|
console.log('This browser does not allow clearing tables, the console cannot be cleared.'); |
|
return; |
|
} |
|
history.output.prevMethod = ''; |
|
i = options.methods.length; |
|
while (i--) { |
|
elements.lines[options.methods[i]] = []; |
|
} |
|
options.groupDepth = 0; |
|
options.paddingLeft = 0; |
|
console.log(messages.clear); |
|
originalConsole.clear(); |
|
return; |
|
} |
|
//Handle the new console logging |
|
newConsole(method, args); |
|
}; |
|
} |
|
//init |
|
function init() { |
|
//Intercept all original console methods including trace. Register the event type as a line type. |
|
var i = options.methods.length; |
|
while (i--) { |
|
elements.lines[options.methods[i]] = []; |
|
interceptConsole(options.methods[i]); |
|
} |
|
//Bind to window.onerror |
|
window.onerror = function() { |
|
var args = Array.prototype.slice.call(arguments); |
|
args.newMessage = args[0]; |
|
args.newStackTrace = formatStackTrace(arguments); |
|
newConsole('error', args); |
|
}; |
|
|
|
return { |
|
//nothing to expose |
|
}; |
|
} |
|
//return |
|
if (!ref) { |
|
ref = init(); |
|
} |
|
return ref; |
|
} |
|
|
|
//initialize the console commandline |
|
function initCommandLine() { |
|
//reference |
|
var ref; |
|
//sub helpers |
|
function getFromArrayById(id) { |
|
var pos = elements.acItems.map(function(x) {return x.id; }).indexOf(id); |
|
return { |
|
position: pos, |
|
element: (pos !== -1) ? elements.acItems[pos] : undefined |
|
}; |
|
} |
|
function findInArray(array, match) { |
|
return array.filter(function(item, index, self){ |
|
return (typeof item === 'string' && item.indexOf(match) > -1) && (index === self.indexOf(item)); |
|
}); |
|
} |
|
//core |
|
function assemble() { |
|
elements.consoleinput.setAttribute('type', 'text'); |
|
elements.consoleinput.setAttribute('autocapitalize', 'off'); |
|
elements.consoleinput.setAttribute('autocorrect', 'off'); |
|
elements.autocompleteItem.setAttribute('href', '#'); |
|
elements.gt.innerHTML = '>'; |
|
elements.input.appendChild(elements.gt); |
|
elements.input.appendChild(elements.consoleinput); |
|
elements.input.appendChild(elements.autocomplete); |
|
elements.base.appendChild(elements.input); |
|
|
|
return elements.base; |
|
} |
|
function submitCommand(command) { |
|
if (command !== '') { |
|
storeCommand(command); |
|
var result; |
|
try { |
|
result = eval.call(window, command.trim()); |
|
console.log.call(window, result); |
|
} catch(e) { |
|
console.error(e.message); |
|
} finally { |
|
elements.consoleinput.value = ''; |
|
} |
|
} |
|
} |
|
function hoverAutoComplete(e) { |
|
if (typeof e === 'undefined') { return; } |
|
//unset any already hovered elements |
|
var hovered = getFromArrayById('hover').element, target = e.target || e.srcElement, over; |
|
if (typeof hovered !== 'undefined') { |
|
setCSS(hovered, { |
|
color: '', |
|
backgroundColor: '' |
|
}).id = ''; |
|
} |
|
if (e.type === 'mouseover') { |
|
status.acHovered = true; |
|
over = true; |
|
} else { |
|
over = false; |
|
} |
|
setCSS(target, { |
|
color: over ? '#FFFFFF' : '', |
|
backgroundColor: over ? 'rgb(66,139,202)' : '' |
|
}).id = over ? 'hover' : ''; |
|
} |
|
function toggleAutoComplete(show) { |
|
var hidden = (elements.autocomplete.currentStyle ? elements.autocomplete.currentStyle.display : window.getComputedStyle(elements.autocomplete, null).display) === 'none'; |
|
show = (typeof show === 'undefined') ? hidden : show; |
|
setCSS(elements.autocomplete, {display: (show) ? 'inherit' : 'none'}); |
|
status.acActive = show; |
|
if (!show) { status.acHovered = false; } |
|
} |
|
function clickAutoComplete(e) { |
|
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } |
|
var tgt = e.target || e.srcElement; |
|
elements.consoleinput.value = tgt.innerHTML; |
|
elements.consoleinput.focus(); |
|
toggleAutoComplete(); |
|
} |
|
function autoComplete(command) { |
|
if (command.length < 1) { |
|
toggleAutoComplete(false); |
|
return; |
|
} |
|
var searchString = encodeURI(command), matches, match, row, i, maxAmount = options.browserinfo.isMobile ? 3 : 5; |
|
elements.autocomplete.innerHTML = ''; |
|
elements.acItems = []; |
|
matches = findInArray(history.input.commands, searchString); |
|
matches = matches.slice(Math.max(matches.length - maxAmount, 0)); |
|
i = matches.length; |
|
while (i--) { |
|
match = decodeURI(matches[i]); |
|
row = elements.autocompleteItem.cloneNode(false); |
|
row.innerHTML = match; |
|
row.onmouseover = hoverAutoComplete; |
|
elements.autocomplete.insertBefore(row, elements.autocomplete.firstChild); |
|
elements.acItems.unshift(row); |
|
} |
|
toggleAutoComplete(matches.length > 0); |
|
} |
|
function setBinds() { |
|
if (options.browserinfo.evtLstn) { |
|
elements.autocomplete.addEventListener('click', clickAutoComplete, false); |
|
} else { |
|
elements.autocomplete.attachEvent('onclick', clickAutoComplete); |
|
} |
|
document.onkeydown = function (e) { |
|
e = e || window.event; |
|
var tgt = e.target || e.srcElement; |
|
if (tgt === elements.consoleinput) { |
|
if ((e.key === 'Enter' || e.keyCode === 13)) { //enter |
|
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } |
|
if(!status.acHovered) { |
|
submitCommand(elements.consoleinput.value); |
|
} else { |
|
elements.consoleinput.value = getFromArrayById('hover').element.innerHTML; |
|
elements.consoleinput.focus(); |
|
} |
|
toggleAutoComplete(false); |
|
status.acInput = ''; |
|
} else if ((e.keyCode === 38 || e.keyCode === 40)) { //up and down arrows for history browsing |
|
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } |
|
var up = (e.keyCode === 40); |
|
if(status.acActive) { |
|
//autocomplete window is opened |
|
//get id of currently hovered element |
|
var hovered = getFromArrayById('hover').position; |
|
var counter = (hovered === -1) ? elements.acItems.length : hovered; |
|
//hover new (in- or decreased number) one |
|
counter = valBetween((counter += (up) ? 1 : -1), 0, elements.acItems.length - 1); |
|
hoverAutoComplete({target : elements.acItems[counter], type : 'mouseover'}); |
|
} else { |
|
//autocompete window not opened |
|
var hist = history.input.commands; |
|
history.input.commandIdx += (up) ? 1 : -1; |
|
history.input.commandIdx = valBetween(history.input.commandIdx, 0, hist.length); |
|
elements.consoleinput.value = typeof hist[history.input.commandIdx] === 'undefined' ? '' : decodeURI(hist[history.input.commandIdx]); |
|
} |
|
} |
|
} |
|
if (e.keyCode === 27 && status.acActive) { |
|
toggleAutoComplete(false); |
|
} |
|
}; |
|
document.onkeyup = function (e) { |
|
e = e || window.event; |
|
var tgt = e.target || e.srcElement; |
|
if (tgt === elements.consoleinput && status.acInput !== elements.consoleinput.value && (e.keyCode !== 38 && e.keyCode !== 40 && e.keyCode !== 27 && e.key !== 'Enter' && e.keyCode !== 13)) { |
|
status.acInput = elements.consoleinput.value.trim(); |
|
autoComplete(elements.consoleinput.value); |
|
} |
|
}; |
|
} |
|
//init |
|
function init() { |
|
assemble(); |
|
setBinds(); |
|
return { |
|
//nothing to expose |
|
}; |
|
} |
|
//return |
|
if (!ref) { |
|
ref = init(); |
|
} |
|
return ref; |
|
} |
|
|
|
function init() { |
|
if (!status.initialized) { |
|
if (consoleElement && mobileConsole) { |
|
console.error( 'Mobile Console cannot be reconstructed! Reload the page to enable Mobile Console again.' + '\n' + |
|
'Tip: use the minimize button instead of closing.' ); |
|
return; |
|
} else { |
|
status.initialized = true; |
|
//populate references |
|
if (!mobileConsole) { |
|
//taps into native console and adds new functionality |
|
mobileConsole = initConsole(); |
|
} |
|
if (!consoleElement && mobileConsole) { |
|
//creates the new HTML console element and attaches it to document |
|
consoleElement = initConsoleElement(); |
|
} |
|
if (!commandLine && consoleElement && mobileConsole) { |
|
//creates an HTML commandline and attaches it to existing console element |
|
commandLine = initCommandLine(); |
|
} |
|
} |
|
} |
|
//log a 'welcome' message |
|
console.info( '--==## Mobile Console v' + options.version + ' ' + (status.initialized ? 'active' : 'inactive' ) + ' ##==--' ); |
|
} |
|
|
|
//autorun if mobile |
|
if (options.browserinfo.isMobile || options.overrideAutorun) { |
|
init(); |
|
} |
|
|
|
//expose the mobileConsole's methods |
|
return { |
|
init : init, |
|
about: about, |
|
toggle : toggleHeight, |
|
status : status, |
|
options : options |
|
}; |
|
|
|
}()); |