/*
* Konsole.js - On-Screen Debugger Console
* @author Sandro Ducceschi [eatcodeplay.ch, Switzerland]
*/
(function() {
'use strict';
//---------------------------------------------------
//
// Constructor / Initialization
//
//---------------------------------------------------
/**
* <h3>A on-screen debugger / default console replacement class</h3><br/>
* By default Konsole will take over your Browser default <code>console</code>.<br/>
* You can prevent that by changing the <code>Konsole.takeOver</code> property.
* @see Konsole.takeOver
* @namespace Konsole
*/
var Konsole = { version: "1.1.0" },
p = Konsole.prototype = {};
p.init = function()
{
this.htmlStr = [
'<div id="konsole-highlight"></div>',
'<div id="konsole">',
'<div class="log"><div class="log wrapped"></div></div>',
'<div class="cmdline"><input type="text" value="" /></div>',
'</div>'
].join('');
this.spacer = ' ';
this.cmds = [
{ name: ['hide','close','x','exit'], func: Konsole.hide, help: 'Hides/Closes the Konsole.'},
{ name: 'clear', func: Konsole.clear, help: 'Clears the Konsole output.'},
{ name: 'resize', func: Konsole.resize, help: 'Resize Konsole in percent (0-100). Example: resize 22'},
{ name: 'snap', func: p.snapKonsole, help: 'Snap Konsole to any border (top,right,bottom,left). Example: snap right'},
{ name: 'find', func: p.highlightElement, help: 'Find/Highlight element(s) via selectors. Example: find p.myClass'},
{ name: 'get', func: p.getElement, help: 'Log element(s) via selectors. Example: get p.myClass'},
{ name: 'css', func: p.modifyElementCSS, help: [ 'This command allows various css related actions.',
'css bind {selector} -> Binds a element for css manipulation. Press ESC to exit this mode.',
'css get {selector} -> Shows the css properties for matching element(s)',
'css class {selector} add/remove {styleName} -> Attaches/Detaches a CSS Class from the matching element(s)'
].join('\n')},
{ name: 'overflow', func: p.findOverflow, help: 'Find overflowing elements.'},
{ name: 'stats', func: p.doobStats, help: 'Show FPS/MS Stats (using mrdoob/stats.js).'},
{ name: 'report', func: p.sendReport, help: 'Send a report to KonsoleReport. See Documentation for more details.'},
{ name: 'about', func: p.about, help: 'About the Konsole.'},
{ name: 'help', func: p.help, help: 'See Documentation for more details.'}
];
if (typeof $ != 'undefined')
{
$(document).ready(function(){
var $body = $('body');
$body.append(p.htmlStr);
var $konsole = $('#konsole');
var $cmdLine = $konsole.find('.cmdline input');
if (p._visible)
{
$konsole.css('display', 'block');
$konsole.find('.log.wrapped').append(p.logs.join('<br/>'));
$cmdLine.focus();
p.updateLogContent();
$konsole.find('.log.wrapped').off('mousewheel').on('mousewheel', function(evt){
evt.preventDefault(); evt.stopPropagation();
var scrollTop = $konsole.find('.log.wrapped').scrollTop();
$konsole.find('.log.wrapped').scrollTop(scrollTop - evt.originalEvent.wheelDelta);
});
}
Konsole.snap = p._konsoleSnap;
if (p._monitoringObject)
p.updateMonitoring();
$cmdLine.off('keyup').on('keyup', p.handleCmdInput);
$cmdLine.off('keydown').on('keydown', p.handleCmdResize);
$(this).off('keypress keyup').on('keypress keyup', p.handleKeyboardEvent);
// mobile console helper
var userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf('iphone') != -1 || userAgent.indexOf('ipad') != -1 || userAgent.indexOf('android') != -1)
{
var addTouchButton = function() {
var $touch = $('.konsole-touch');
if ($touch.length == 0) {
$body.append('<div class="konsole-touch"></div>');
$touch = $($touch.selector);
$touch.off('click').on('click', function(){
if (!p._visible)Konsole.show();
else Konsole.hide();
});
}
};
setInterval(function(){
addTouchButton();
}, 2500);
addTouchButton();
}
});
}
else
{
console.warn('Konsole requires jQuery to be loaded in the <head> before itself.');
}
};
//---------------------------------------------------
//
// Variables / Private Properties
//
//---------------------------------------------------
p.history = [];
p.historyIndex = 0;
p.logs = [];
p.logCount = 0;
/** @type {Object} */
p._monitoringObject = null;
p._lastResizePercentage = 0.22;
p._cssEditData = null;
p._statsTimer = 0;
p._stats = null;
//---------------------------------------------------
//
// Public Properties
//
//---------------------------------------------------
/**
* The Key to which Konsole should be bound.
* When pressed, the Konsole will either show or hide.
* @name Konsole.key
* @type {string}
* @default §
*/
Konsole.key = '§';
/**
* If the default browser console should be logged to as well
* @name Konsole.originalEnabled
* @type {Boolean}
* @default true
*/
Konsole.originalEnabled = true;
/**
* If Konsole Reporting is enabled or not. Default: false
* @name Konsole.reportingEnabled
* @type {Boolean}
* @default false
*/
Konsole.reportingEnabled = false;
/**
* URL to KonsoleReporter
* @name Konsole.reportingUrl
* @type {String}
* @default './konsolereport/log.php'
*/
Konsole.reportingUrl = './konsolereport/log.php';
/**
* Defines the maximum depth that an object should be inspected. Default: 5
* @name Konsole.maxLogDepth
* @type {Number}
* @default 5
*/
Konsole.maxLogDepth = 5;
/**
* Boolean value indicating if the Konsole is visible or not. Can also be set to either hide or show the Konsole.
* @name Konsole.visible
* @example
* // Displays the Konsole
* Konsole.visible = true;
* // Hides the Konsole
* Konsole.visible = false;
* @type {Boolean}
* @default false
*/
Object.defineProperty(Konsole, 'visible', {
get: function() {
return p._visible;
},
set: function(bool) {
if (bool)
Konsole.show();
else
Konsole.hide();
},
enumerable: true,
configurable: false
});
p._visible = false;
/**
* Boolean value indicating if Konsole logging is enabled or disabled.<br/>
* If Konsole is in Takeover-Mode, this will also disable the browser default console logging.
* @name Konsole.enabled
* @type {Boolean}
* @default true
*/
Konsole.enabled = true;
/**
* Snap the Console to any of the four borders of the screen.
* Values are: <code>top, right, bottom, left</code>
* @name Konsole.snap
* @example
* // Snaps the Konsole to the right of the browser
* Konsole.snap = 'right';
* @type {String}
* @default top
*/
Object.defineProperty(Konsole, 'snap', {
get: function() {
return p._konsoleSnap;
},
set: function(str) {
var $konsole = $('#konsole'), newCSS = {};
$konsole.css({'top':'auto','right':'auto','bottom':'auto','left':'auto'});
if (str == 'left' || str == 'right') newCSS['top'] = 0;
if (str == 'top' || str == 'bottom') newCSS['left'] = 0;
newCSS[str] = (str == 'bottom') ? 5 : 0;
$konsole.css(newCSS);
p._konsoleSnap = str;
Konsole.resize();
},
enumerable: true,
configurable: false
});
p._konsoleSnap = 'top';
/**
* Catch all Javascript errors and log them to Konsole
* @name Konsole.showErrors
* @type {Boolean}
* @default false
*/
Object.defineProperty(Konsole, 'showErrors', {
get: function() {
return p._showErrors;
},
set: function(bool) {
if (bool)
window.addEventListener('error', p.handleGlobalError);
else
window.removeEventListener('error', p.handleGlobalError);
p._showErrors = bool;
},
enumerable: true,
configurable: false
});
p._showErrors = false;
/**
* Defines if Konsole shall act as the default <code>console</code>.<br/> This allows
* you to use Konsole the same way you would use the regular </code>console</code>.<br/>
* <strong>Caution:</strong><br/>More than likely Konsole will not feature all the methods
* that your regular console has.<br/>You can however still use the regular console when this
* is enabled by calling it at <code>_console</code>.
* @name Konsole.takeOver
* @example
* // Without Takeover
* Konsole.takeOver = false;
* Konsole.log("test"); // This is Konsole (duuh)
* console.log("test"); // Your Browsers regular console
*
* // WITH Takeover
* Konsole.takeOver = true;
* console.log("test"); // Konsole is now the default console
* _console.log("test"); // This is the browser default console
* @type {Boolean}
* @default true
*/
Object.defineProperty(Konsole, 'takeOver', {
get: function() {
return p._takeOver;
},
set: function(bool) {
if (bool) {
//noinspection JSValidateTypes
if (!p.isEmpty(window.console) && window.console != Konsole)
window._console = window.console;
//noinspection JSValidateTypes
window.console = Konsole;
}
else {
if (!p.isEmpty(window._console))
window.console = window._console;
window._console = null;
}
p._takeOver = bool;
},
enumerable: true,
configurable: false
});
p._takeOver = true;
//---------------------------------------------------
//
// API / Public Methods
//
//---------------------------------------------------
/**
* Log a message to the console
* @function
* @name Konsole.log
* @example
* Konsole.log("myString", 5, true, [1,2,3,4], {myValue: "hello world"});
* @param {...*} values . Can be any valid javascript object/type
*/
Konsole.log = function(values) { p.addMessage(arguments, 'log'); };
/**
* Log a info message to the console
* @function
* @name Konsole.info
* @example
* Konsole.info("myString", 5, true, [1,2,3,4], {myValue: "hello world"});
* @param {...*} values . Can be any valid javascript object/type
*/
Konsole.info = function(values) { p.addMessage(arguments, 'info'); };
/**
* Log an event message to the console
* @function
* @name Konsole.event
* @example
* Konsole.event("myString", 5, true, [1,2,3,4], {myValue: "hello world"});
* @param {...*} values . Can be any valid javascript object/type
*/
Konsole.event = function(values) { p.addMessage(arguments, 'event'); };
/**
* Log an error message to the console
* @function
* @name Konsole.error
* @example
* Konsole.error("myString", 5, true, [1,2,3,4], {myValue: "hello world"});
* @param {...*} values . Can be any valid javascript object/type
*/
Konsole.error = function(values) { p.addMessage(arguments, 'error'); };
/**
* Log a warn message to the console
* @function
* @name Konsole.warn
* @example
* Konsole.warn("myString", 5, true, [1,2,3,4], {myValue: "hello world"});
* @param {...*} values . Can be any valid javascript object/type
*/
Konsole.warn = function(values) { p.addMessage(arguments, 'warn'); };
/**
* Log a system message to the console
* @function
* @name Konsole.system
* @example
* Konsole.system("myString", 5, true, [1,2,3,4], {myValue: "hello world"});
* @param {...*} values . Can be any valid javascript object/type
*/
Konsole.system = function(values) { p.addMessage(arguments, 'system'); };
/**
* Observe a Data Object for any modifications. This method only works in
* Browsers that support <code>Object.observe</code>
* @function
* @name Konsole.observe
* @example
* var myObject = {person: "John Doe", age: 32, usesKonsole: true};
* Konsole.observe(myObject, "PersonData");
* myObject.age = 35;
* @param {Object} model The Object/Model to observe
* @param {String} name The Name to identify the model with
*/
Konsole.observe = function(model, name) { p.observeObject(model, name); };
/**
* Monitor a value. Useful for repetitive/fast changing values.
* @function
* @name Konsole.monitor
* @example
* var incremental = 0;
* Konsole.monitor(incremental, "My Value");
* setInterval(function() {
* Konsole.monitor(incremental++, "My Value");
* }, 500);
* @param {*} value Your Value
* @param {String} name The Name to identify the value with
*/
Konsole.monitor = function(value, name) { p.monitorValue(value, name); };
/**
* Log a seperator to the console
* @function
* @name Konsole.sep
*/
Konsole.sep = function() { p.addMessage(['――――――――――――――――――――'], 'system'); };
/**
* Log a seperator to the console
* @function
* @name Konsole.separator
*/
Konsole.separator = function() { p.addMessage(['――――――――――――――――――――'], 'system'); };
/**
* Shows the console
* @function
* @name Konsole.show
*/
Konsole.show = function()
{
if (!Konsole.enabled)
return;
if ((document.readyState == 'interactive' || document.readyState == 'complete') && !p._visible)
{
var $konsole = $('#konsole');
if ($konsole.length == 0) {
$('body').append(p.htmlStr);
$konsole = $($konsole.selector);
}
Konsole.resize();
$konsole.find('.log.wrapped').html('');
$konsole.find('.log.wrapped').append(p.logs.join('<br/>'));
$konsole.css('display', 'block');
$konsole.find('.cmdline input').focus();
p.updateLogContent();
$konsole.find('.cmdline input').on('keyup', p.handleCmdInput);
$konsole.find('.cmdline input').on('keydown', p.handleCmdResize);
$konsole.find('.log.wrapped').off('mousewheel').on('mousewheel', function(evt){
evt.preventDefault(); evt.stopPropagation();
var scrollTop = $konsole.find('.log.wrapped').scrollTop();
$konsole.find('.log.wrapped').scrollTop(scrollTop - evt.originalEvent.wheelDelta);
});
}
p._visible = true;
};
/**
* Hides the console
* @function
* @name Konsole.hide
*/
Konsole.hide = function()
{
if (!Konsole.enabled)
return;
p._visible = false;
var $konsole = $('#konsole'),
$cmdLine = $konsole.find('.cmdline input');
$cmdLine.off('keyup');
$cmdLine.off('keydown');
$konsole.css('display', 'none');
};
/**
* Clears the console
* @function
* @name Konsole.clear
*/
Konsole.clear = function()
{
if (!Konsole.enabled)
return;
$('#konsole').find('.log.wrapped').html('');
p.logCount = 0;
p.logs = [];
};
/**
* Resizes the console
* @function
* @name Konsole.resize
* @param {Number} [percentage] Percentage of the screen size (0-100) the konsole should take up.
* @default 22
*/
Konsole.resize = function(percentage)
{
if (!Konsole.enabled)
return;
var $konsole = $('#konsole'), calculatedDimension;
p._lastResizePercentage = (typeof percentage != 'undefined') ? percentage/100 : p._lastResizePercentage;
if (p._konsoleSnap == 'left' || p._konsoleSnap == 'right')
{
calculatedDimension = Math.floor(($(window).width()-40)*p._lastResizePercentage);
$konsole.css({'height': '100%', 'width': calculatedDimension});
$konsole.find('.log').css({'width': calculatedDimension-10, 'height': 'calc(100% - 36px)'});
$konsole.find('.log.wrapped').css({'width': calculatedDimension-13, 'height': '100%'});
}
else
{
calculatedDimension = Math.floor(($(window).height() - 40) * p._lastResizePercentage);
$konsole.css({'width': '100%', 'height': 'auto'});
$konsole.find('.log').css({'height': calculatedDimension, 'width': 'calc(100% - 10px)'});
$konsole.find('.log.wrapped').css('height', calculatedDimension);
}
p.updateLogContent();
};
/**
* Create a Command/Function that can be called from the Konsole Input.
* @function
* @name Konsole.createCommand
* @example
* var myFunction = function(str) {
* alert(str);
* };
* Konsole.createCommand("alert", myFunction, 'Display an alert window. Example: "alert hello world"');
* @param {String} cmdName The name of the command
* @param {Function} cmdFunc The method/function that should be called
* @param {String} [cmdHelp] A short documentation of the command you are adding
*/
Konsole.createCommand = function(cmdName, cmdFunc, cmdHelp)
{
p.cmds.push({name: cmdName, func: cmdFunc, help: (p.isEmpty(cmdHelp)) ? 'No help available' : cmdHelp});
};
/**
* Get Stack Line from where this function is called (filename, line, char index)
* @function
* @name Konsole.getStackLine
* @param {Number} [index] Since the index of the line you want may vary, you can specificy the index here.
* @default 3
*/
Konsole.getStackLine = function(index)
{
index = index || 3;
if (typeof Error != 'undefined') {
try {
var err = new Error(), line, stack = err['stack'].split("\n");
if (index >= stack.length)
index = stack.length - 1;
line = stack[index];
line = line.slice(line.indexOf("at "), line.length);
return line.replace(/(http.?:\/\/.*\/)/g, '')
.replace(/\?.+?(?=:)/g, '')
//.replace(/\(|\)/g, '')
.replace(/\s:/, ' index:');
} catch(err) { return ''; }
}
return '';
};
/**
* If <code>Konsole.reportingEnabled</code> is true and a
* valid <code>Konsole.reportingUrl</code> is defined,
* allows you to send data to KonsoleReport for storage.
* @function
* @name Konsole.report
* @example
* var reportData = {
* type: "log", // Can be log, system, event, info, warn, error, report
* msg: "This is my message" // The message to show in the Report
* callee: "MyClass.js" // optional
*
* // add any number of properties you want to log, nested object are also allowed
* };
* Konsole.report(reportData);
* @param {Object} data A freely definable object containing any values you may want to log
*/
Konsole.report = function(data)
{
if (Konsole.reportingEnabled) {
try {
data = data || {};
data.client = {'agent': navigator.userAgent, 'language': navigator.language || navigator.userLanguage, 'platform': navigator.platform, 'version': navigator.appVersion};
$['post'](Konsole.reportingUrl, {'data': JSON.stringify(data)}, null, 'json');
} catch (err) {}
}
};
//---------------------------------------------------
//
// Private Methods
//
//---------------------------------------------------
p.addMessage = function(values, type)
{
if (!Konsole.enabled)
return;
var parsedStr = '',
msg = '<span class="log-system"> </span>';
if (typeof values == 'undefined' || values == null || (values.length == 1 && values[0] == null))
{
msg = p.getLineNumber();
p.logToOriginalConsole(values, type);
msg += '<p class="log-system"><i>null</i></p>';
this.logs.push(msg);
}
else if (typeof values != 'undefined')
{
msg = p.getLineNumber();
p.logToOriginalConsole(values, type);
var n = values.length;
for (var i = 0; i < n; i++)
{
var val = values[i];
if (i > 0) parsedStr += ' ';
if (typeof val == 'string' && p.isSimpleHtml(val))
parsedStr += p.prettifySimpleHtml(val);
else if (p.isArray(val))
parsedStr += p.prettifyArray(val);
else if (typeof val == 'object')
parsedStr += p.prettifyObject(val);
else
parsedStr += (typeof val == 'string') ? val.replace(/\n/g, '<br/>') : val;
}
parsedStr = parsedStr.replace(/\t/g, p.spacer);
msg += '<p class="log-'+type+'">'+parsedStr+'</p>';
this.logs.push(msg);
}
if (p._visible && parsedStr != "") {
$('#konsole').find('.log.wrapped').append(msg + '<br/>');
p.updateLogContent();
}
};
p.removeMessage = function(index, deleteCount)
{
deleteCount = deleteCount || 1;
p.logs.splice(index, deleteCount);
$('#konsole').find('.log.wrapped').append(p.logs.join('<br/>'));
p.updateLogContent();
};
p.logToOriginalConsole = function(data, type)
{
if (Konsole.originalEnabled && typeof _console != 'undefined' && _console && typeof data != 'undefined' && typeof type != 'undefined') {
if (typeof _console[type] != 'undefined' && typeof _console[type].apply != 'undefined')
_console[type].apply(_console, data);
else if (typeof _console.log.apply != 'undefined')
_console.log.apply(_console, data);
}
else if (Konsole.takeOver == false && Konsole.originalEnabled == true) {
if (typeof console[type] != 'undefined' && typeof console[type].apply != 'undefined')
console[type].apply(console, data);
else if (typeof console.log.apply != 'undefined')
console.log.apply(console, data);
}
};
p.getLineNumber = function()
{
p.logCount++;
var num = p.logCount;
if (p.logCount < 1000)
num = p.padNumber(p.logCount,4);
return '<span class="log-system">['+num+'] </span>';
};
p.updateLogContent = function()
{
var $konsole = $('#konsole'),
el = $konsole.find('.log.wrapped').get(0);
if (el) {
el.scrollTop = el.scrollHeight;
$konsole.find('.inspect').each(function() {
$(this).off('mouseenter mouseleave');
$(this).hover(p.handleDOMHoverOver, p.handleDOMHoverOut);
});
$konsole.find('.inspect.full').each(function(){
var $full = $(this), $simple = $full.prev();
$simple.off('click').on('click', function() {
$simple.css('display', 'none');
$full.css('display', 'block');
el.scrollTop = el.scrollHeight;
$full.off('click').on('click', function(){
$full.css('display', 'none');
$simple.css('display', 'block');
})
})
});
$konsole.find('span.klo').off('click').on('click', function(){
var $this = $(this),
$next = $this.next();
$this.hide(); $next.show();
$next.find('.klx').off('click').on('click', function(){
$this.show(); $next.hide();
})
});
if (typeof prettyPrint !== 'undefined')
prettyPrint();
}
};
//----------------------------------
// Prettifier Methods
//----------------------------------
p.prettifyArray = function(data, prefix, depth)
{
depth = depth || 0; depth++;
var suffix = (typeof prefix == 'undefined') ? '' : prefix;
prefix = (typeof prefix == 'undefined') ? p.spacer : p.spacer+prefix;
var str = '[';
if (depth <= Konsole.maxLogDepth) {
var n = data.length;
var lastItemType = null;
var isMixedArray = false;
for (var i = 0; i < n; i++) {
if (!lastItemType) lastItemType = typeof data[i];
if (typeof data[i] != lastItemType) {
isMixedArray = true;
break
}
}
if (lastItemType == 'object' && !isMixedArray)
isMixedArray = true;
str = (depth > 1) ? '<span class="klo">▶ Array['+n+']</span><span class="klc"><span class="klx">▼ </span>[' : '[';
if (isMixedArray)
str += '<br/>'+prefix;
for (i = 0; i < n; i++) {
if (typeof data[i] == 'object' && typeof data[i] != 'boolean' && typeof data[i] != 'string' && typeof data[i] != 'number')
str += p.prettifyObject(data[i], prefix, depth);
else if (typeof data[i] == 'string')
str += '"' + data[i] + '"';
else
str += data[i];
if (!isMixedArray && i < n -1)
str += ', ';
else if (i < n - 1)
str += ',<br/>' + prefix;
}
str += (isMixedArray) ? '<br/>'+suffix+']' : ']';
if (depth > 1)
str += '</span>';
return str;
}
return str+'…]';
};
p.prettifyObject = function(data, prefix, depth)
{
depth = depth || 0; depth++;
var key,
str = (depth > 1) ? '<span class="klo">▶ Object</span><span class="klc"><span class="klx">▼ </span>{' : '{',
suffix = (typeof prefix == 'undefined') ? '' : prefix;
prefix = (typeof prefix == 'undefined') ? p.spacer : p.spacer+prefix;
if (depth <= Konsole.maxLogDepth)
{
if (typeof data == 'object' && p.isXml(data))
return '<code class="konsole prettyprint">'+p.prettifyXml(data)+'</code>';
if (typeof data == 'object' && data == null)
return 'null';
if (p.isInDOM(data) || p.hasJQueryContext(data))
return p.prettifyDOM(data);
for (key in data)
{
if (data.hasOwnProperty(key))
{
str += '<br/>' + prefix + key + ': ';
if (typeof data[key] === 'string' && !p.isSimpleHtml(data[key]))
str += '"'+data[key]+'"';
if (typeof data[key] === 'string' && p.isSimpleHtml(data[key]))
str += '"'+p.prettifySimpleHtml(data[key])+'"';
else if (typeof data[key] === 'number' || typeof data[key] === 'boolean')
str += data[key];
else if (p.isArray(data[key]))
str += p.prettifyArray(data[key], prefix, depth);
else if (p.isFunction(data[key]))
str += '[object Function]';
else if (p.isObject(data[key]))
str += p.prettifyObject(data[key], prefix, depth);
}
}
str += '<br/>'+suffix+'}';
if (depth > 1)
str += '</span>';
return str;
}
return str+'…}';
};
p.prettifySimpleHtml = function(data)
{
data = data.replace(/</g, '<').replace(/>/, '>').replace(/\n/g, '<br/>').replace(/ /g, p.spacer+p.spacer);
return '<code class="konsole prettyprint">'+data+'</code>';
};
p.prettifyXml = function(data)
{
var prettified;
try { prettified = new XMLSerializer().serializeToString(data); }
catch (e) { prettified = data.xml; }
prettified = prettified.replace('?>', '?>\n')
.replace(/</g, '<')
.replace(/>/, '>')
.replace(/\n/g, '<br/>')
.replace(/ /g, p.spacer+p.spacer);
return prettified;
};
p.prettifyDOM = function(dataArr)
{
dataArr = (dataArr.hasOwnProperty('context') || p.isArray(dataArr)) ? dataArr : [dataArr];
var n = dataArr.length,
i, finalArr = [];
for (i = 0; i < n; i++)
{
var node = dataArr[i],
pos = $(node).offset(),
width = node.offsetWidth,
height = node.offsetHeight,
str = p.prettifyXml(node);
str = str.replace('<br>', '');
str = str.replace(/(\s?)xmlns="http:\/\/www.w3.org\/1999\/xhtml"/g, '');
var output = '', startTag = '', endTag = '', content = '', endTagMatches, hasSimple = false;
try {
startTag = str.match(/^<\w+(>|.*>)/g)[0]; // /^<\w{1,}(>|.*>)/g
endTagMatches = str.match(/<\/\w+>$/g); // /<\/\w{1,}>$/g
endTag = (endTagMatches && endTagMatches.length > 0) ? endTagMatches[0] : '';
content = str.replace(startTag, '').replace(endTag, '');
} catch (err){}
if (typeof pos != 'undefined')
{
if (content.indexOf('<') != -1) {
hasSimple = true;
output = '<code class="konsole prettyprint inspect simple cords_'+pos.left+'-'+pos.top+'-'+width+'-'+height+'">'+startTag+'…'+endTag+'</code>';
}
output += '<code class="konsole prettyprint inspect '+((hasSimple) ? 'full' : '')+' cords_'+pos.left+'-'+pos.top+'-'+width+'-'+height+'">'+str+'</code>';
if (node.className.indexOf('log-css') == -1 && node.className.indexOf('log-log') == -1 && node.className.indexOf('log-info') == -1 && node.className.indexOf('log-event') == -1 &&
node.className.indexOf('log-warn') == -1 && node.className.indexOf('log-system') == -1 && node.className.indexOf('log-error') == -1 &&
node.className.indexOf('konsole') == -1 && node.id.indexOf('konsole') == -1 && node.className != 'log' && node.className != 'log wrapped' && node.className != 'cmdline')
finalArr.push(output);
}
}
return finalArr.join(",<br/>");
};
//----------------------------------
// Command Line Methods
//----------------------------------
p.retrieveCommand = function(input)
{
if (p._cssEditData != null)
return 'acss';
var cmds = this.cmds;
var n = cmds.length, i;
for (i = 0; i < n; i++) {
if ((typeof cmds[i].name == 'string' && cmds[i].name == input) || (typeof cmds[i].name != 'string' && cmds[i].name.indexOf(input) != -1))
return cmds[i];
}
return null;
};
p.snapKonsole = function(str)
{
Konsole.snap = str;
};
p.sendReport = function(str)
{
Konsole.report({type: 'report', msg: str, callee: 'User'});
};
p.about = function()
{
p.addMessage(['Konsole '+Konsole.version+' {eatcodeplay.ch}'], 'system');
};
p.help = function(requestedCommand)
{
if (p.isEmpty(requestedCommand)) {
p.addMessage(['Available commands:'], 'info');
var cmdNames = [], cmds = p.cmds, n = cmds.length;
for (var i = 0; i < n; i++) {
var cmdName = cmds[i].name;
if (typeof cmdName != 'string')
cmdName = cmds[i].name.join(' ');
if (cmdName != 'report' || (cmdName == 'report' && Konsole.reportingEnabled))
cmdNames.push(cmdName);
}
p.addMessage(cmdNames, 'log');
p.addMessage(['Enter "help {command}" for more details'], 'system');
} else {
var cmd = p.retrieveCommand(requestedCommand);
if (cmd)
p.addMessage([requestedCommand+' -> '+cmd.help], 'system');
}
};
//----------------------------------
// DOM Methods
//----------------------------------
p.highlightElement = function(selector)
{
var kId ='#konsole ', kParent = '[class^="konsole"] ';
$(selector).not(kId).not(kId+selector).not(kParent).not(kParent+selector).css('outline', '2px solid #00FF00');
setTimeout(function(){ $(selector).css('outline', ''); }, 1500);
};
p.getElement = function(selector)
{
var selected = null;
if (typeof selector == 'undefined' || p.isEmpty(selector)) {
$(document).on('click', function(evt)
{
evt.preventDefault(); evt.stopPropagation();
var el = document.elementFromPoint(evt.clientX, evt.clientY);
if (selected == el) {
$(el).css('background', '');
p.addMessage([$(el)], 'log');
$('#konsole').find('.cmdline input').focus();
$(document).off('click');
}
else {
$(el).css('background', 'rgba(238, 190, 68, 0.4)');
setTimeout(function(){ $(el).css('background', ''); }, 800);
selected = el;
}
});
}
else {
var kId ='#konsole ', kParent = '[class^="konsole"] ';
p.addMessage([$(selector).not(kId).not(kId+selector).not(kParent).not(kParent+selector)], 'log');
}
};
//----------------------------------
// Analysis Methods
//----------------------------------
p.doobStats = function()
{
if (typeof Stats != 'function') {
console.system('Loading stats.js, just a sec..');
p.loadExternalScripts('//cdn.rawgit.com/mrdoob/stats.js/master/build/stats.min.js', 'doobStats');
}
var $stats = $('#stats');
if ($stats.length > 0) {
clearInterval(p._statsTimer);
p._stats = null;
$stats.remove();
}
else {
p._statsTimer = setInterval(function() {
if (typeof Stats == 'function') {
clearInterval(p._statsTimer);
p.removeMessage(-1);
p._stats = new Stats();
p._stats.domElement.style.position = 'fixed';
p._stats.domElement.style.right = '0';
p._stats.domElement.style.top = '0';
p._stats.domElement.style.zIndex = '100001';
document.body.appendChild(p._stats.domElement);
p._statsTimer = setInterval(function() {
p._stats.update();
}, 1000 / 60);
}
}, 100);
}
};
p.observeObject = function(obj, objName)
{
if (!Konsole.enabled)
return;
if (typeof Object.observe == 'function')
{
obj.name = objName || obj.name;
console.info('Observing: '+obj.name);
Object.observe(obj, function(changes) {
changes.forEach(function(change)
{
var operationType = '';
if (change.type == 'add')
operationType = 'New Property has been added to "'+change.object.name+'":';
else if (change.type == 'update')
operationType = 'A Property of "'+change.object.name+'" has been updated:';
else if (change.type == 'delete')
operationType = 'A Property of "'+change.object.name+'" has been deleted:';
var strArr = [
operationType,
("Property : "+change.name),
("Old Value: "+change['oldValue']),
("New Value: "+change.object[change.name])
];
if (change.type == 'add')
strArr.splice(2, 1);
else if (change.type == 'delete')
strArr.splice(3, 1);
console.info(strArr.join("\n"));
});
});
}
else {
console.system('Object.observe is not implemented. Loading Polyfill.');
p.loadExternalScripts('//cdn.rawgit.com/MaxArt2501/object-observe/master/dist/object-observe-lite.min.js', 'objectObserve');
var interval = setInterval(function(){
if (typeof Object.observe == 'function') {
clearInterval(interval);
p.observeObject(obj, objName);
}
}, 100);
}
};
p.monitorValue = function(value, valueName)
{
if (!Konsole.enabled)
return;
if (!p._monitoringObject)
p._monitoringObject = {};
p._monitoringObject[valueName] = value;
p.updateMonitoring();
};
p.updateMonitoring = function()
{
var $monitor = $('.konsole-monitor');
if ($monitor.length == 0) {
$('body').append('<div class="konsole-monitor"><div class="close">X</div><div class="values"></div></div>');
$monitor = $($monitor.selector);
$monitor.find('.close').off('click').on('click', function(){ $monitor.remove(); p._monitoringObject = null; });
$monitor.on('mousedown', function(evt) {
var el = $(this).get(0);
$monitor.data({oX: el.style.right, oY: el.style.top, cX: evt.clientX, cY: evt.clientY});
$(window).off('mousemove').on('mousemove', function(evt) {
var data = $monitor.data();
if (isNaN(data.oX)) data.oX = -parseFloat(data.oX);
if (isNaN(data.oY)) data.oY = parseFloat(data.oY);
$monitor.css({'right': -(data.oX + evt.clientX - data.cX), 'top': (data.oY + evt.clientY - data.cY), 'cursor': 'default'});
});
});
$monitor.off('mouseup').on('mouseup', function() { $(window).off('mousemove'); });
}
for (var prop in p._monitoringObject) {
if (p._monitoringObject.hasOwnProperty(prop)) {
if ($monitor.find('.values div[data-konmon="'+prop+'"]').length > 0)
$monitor.find('.values div[data-konmon="'+prop+'"]').text(prop+': '+ p._monitoringObject[prop]);
else
$monitor.find('.values').append('<div data-konmon="'+prop+'">'+prop+': '+ p._monitoringObject[prop]+'</div>');
}
}
};
p.findOverflow = function()
{
var docWidth = document.documentElement.offsetWidth,
foundOverflow = false;
[].forEach.call(
document.querySelectorAll('*'),
function(el) {
if (el.offsetWidth > docWidth) {
foundOverflow = true;
console.log(el);
}
}
);
if (!foundOverflow)
Konsole.system('No overflowing elements found');
};
//----------------------------------
// CSS Modification Methods
//----------------------------------
p.modifyElementCSS = function(cmdStr)
{
var styles, cmdArr = cmdStr.split(' '), cmd = cmdArr.shift(), params = cmdArr.join(' ');
if (p._cssEditData == null && cmd == 'bind' && $(params).length > 0)
{
p._cssEditData = {selector: params};
styles = p.getCurrentSelectorStyles(p._cssEditData);
p.logComputedStyle(styles, p._cssEditData);
p.highlightElement(params);
return;
}
else if (cmd == 'get')
{
p._cssEditData = {selector: params};
styles = p.getCurrentSelectorStyles(p._cssEditData);
p.logComputedStyle(styles, p._cssEditData);
p._cssEditData = null;
return;
}
else if (cmd == 'class')
{
var paramStr, mode;
if (params.indexOf(' add ') != -1) {
mode = 'add';
paramStr = params.split(' add ');
}
else if (params.indexOf(' remove ') != -1) {
mode = 'remove';
paramStr = params.split(' remove ');
}
if (typeof mode != 'undefined') {
var kId ='#konsole ',
kParent = '[class^="konsole"] ',
selector = paramStr[0],
$elements = $(selector).not(kId).not(kId+selector).not(kParent).not(kParent+selector);
$elements[mode + 'Class'](paramStr[1]);
return;
}
}
Konsole.warn('Selector returned 0 elements');
};
p.applyCSSBindCommands = function(input)
{
if (input && input.length > 0)
{
var inputArr, prop, value, cssData = p._cssEditData;
if (input == 'highlight') {
p.highlightElement(cssData.selector);
}
else if (input.indexOf(':') != -1)
{
cssData.keyAccess = false;
inputArr = input.split(':');
prop = p.trim(inputArr[0]);
value = p.trim(inputArr[1]).replace(/;/,'');
$(cssData.selector).css(prop.toLowerCase(), value);
var styles = p.getCurrentSelectorStyles(cssData);
p.logComputedStyle(styles, cssData);
}
else if (input.indexOf(' ') == -1)
{
cssData.keyAccess = true;
cssData.keyProperty = input;
}
}
};
p.getCurrentSelectorStyles = function(cssData)
{
var computedStyles = {},
$cssSelector = $(cssData.selector),
cStyles,
elStyles = $cssSelector.attr('style'),
classNames = $cssSelector.attr('class'),
elementId = $cssSelector.attr('id'),
styleNamesArr = [];
classNames = (typeof classNames != 'undefined' && classNames != '') ? classNames.split(' ') : [];
var n = classNames.length;
for (var i = 0; i < n; i++)
classNames[i] = '.'+classNames[i];
styleNamesArr = styleNamesArr.concat(classNames);
if (n > 0)
styleNamesArr.push(classNames.join(''));
if (elementId) {
styleNamesArr.push('#' + elementId);
for(i = 0; i < n; i++)
styleNamesArr.push('#' + elementId + classNames[i]);
styleNamesArr.push('#' + elementId + classNames.join(''));
}
if (styleNamesArr.indexOf(cssData.selector) == -1)
styleNamesArr.push(cssData.selector);
if (typeof cssData.selector == 'string') {
cStyles = p.getCurrentClassStyles(styleNamesArr);
cssData.styleName = cssData.selector;
}
if (typeof cssData.selector == 'object')
{
if (cssData.selector.className != '') {
cStyles = p.getCurrentClassStyles(cssData.selector.className.split(' '), '.');
cssData.styleName = '.' + cssData.selector.className.split(' ').join('.');
}
else if (cssData.selector.id != '') {
cStyles = p.getCurrentClassStyles(cssData.selector.id, '#');
cssData.styleName = '#' + cssData.selector;
}
else {
cStyles = [];
cssData.styleName = 'element.style';
}
}
if (typeof elStyles != 'undefined')
cStyles.push('{'+elStyles+'}');
n = cStyles.length;
for (i = 0; i < n; i++)
{
var sArr = cStyles[i].match(/{(.*)}/)[1].split(';');
var m = sArr.length;
for (var j = 0; j < m; j++)
{
var str = p.trim(sArr[j]);
if (!p.isEmpty(str)) {
var strArr = str.split(':'),
prop = p.trim(strArr[0]);
computedStyles[prop] = p.trim(strArr[1]).replace(/;/,'');
}
}
}
return computedStyles;
};
p.getCurrentClassStyles = function(selectors, prefix)
{
selectors = (typeof selectors == 'string') ? [selectors] : selectors;
var findings = [], numClassNames = selectors.length;
for (var k = 0; k < numClassNames; k++)
{
var selector = (typeof prefix != 'undefined') ? prefix + selectors[k] : selectors[k];
var styles = document.styleSheets, n = styles.length;
for(var i = 0; i < n; i++) {
var rules = styles[i].rules || styles[i].cssRules;
findings = p.getCSSStyleRules(selector, rules, findings);
}
}
return (findings.length > 0) ? findings : [];
};
p.getCSSStyleRules = function(selectorText, rules, results_array)
{
var n = rules.length;
for(var i = 0; i < n; i++) {
if (rules[i].type == 1 && rules[i].selectorText == selectorText) {
var cssText = (rules[i].cssText) ? rules[i].cssText : rules[i].style.cssText;
results_array.push(cssText);
}
else if (rules[i].type == 4) {
var innerRules = rules[i].rules || rules[i].cssRules;
results_array = p.getCSSStyleRules(selectorText, innerRules, results_array);
}
}
return results_array;
};
p.logComputedStyle = function(computedStyle, cssData)
{
var out = '<span class="selector">' + cssData.styleName + ' </span><span>{</span><br/>';
for (var propName in computedStyle) {
if (computedStyle.hasOwnProperty(propName))
{
var value = computedStyle[propName];
var fValue = value + ';';
if (value.indexOf('rgb') != -1)
{
var rgba = computedStyle[propName].match(/\((.+)\)/)[1].split(',');
var hexCode = '#' + p.c2Hex(rgba[0]) + p.c2Hex(rgba[1]) + p.c2Hex(rgba[2]);
fValue = computedStyle[propName] + '; <span class="comment"> /* '+hexCode+' */</span>';
}
out += p.spacer+'<span class="property">'+propName+"</span><span>: "+fValue+'</span><br/>';
}
}
out += "<span>}</span>";
var $log = $('#konsole').find('.log.wrapped');
if (typeof cssData['$log'] == 'undefined') {
var msg = p.getLineNumber();
msg += '<p class="log-css">'+out+'</p>';
$log.append(msg + '<br/>');
cssData['$log'] = $('.log-css:last');
this.logs.push(msg);
}
else {
cssData.$log.html(out);
this.logs[this.logs.length-1] = cssData.$log.prev().get(0).outerHTML + cssData.$log.get(0).outerHTML;
}
$log.get(0).scrollTop = $log.get(0).scrollHeight;
};
//-------------------------------------------
// Type/Value Checking/Manipulation Methods
//-------------------------------------------
p.isXml = function(v) {
var documentElement = v && (v.ownerDocument || v).documentElement;
return documentElement ? documentElement.nodeName !== "HTML" : false;
};
p.isSimpleHtml = function(v) {
return /<[a-z][\s\S]*>/i.test(v);
};
p.isInDOM = function(v) {
var documentElement = v && (v.ownerDocument || v).documentElement;
return documentElement ? documentElement.nodeName === "HTML" : false;
};
p.isArray = function(v) {
return !!(typeof v === 'object' && {}.toString.call(v) === '[object Array]');
};
p.isFunction = function(v) {
return !!(typeof v === 'function' && {}.toString.call(v) === '[object Function]');
};
p.isObject = function(v) {
return !!(typeof v === 'object' && {}.toString.call(v) === '[object Object]');
};
p.isEmpty = function(v) {
return !!(typeof v === 'undefined' || v == null || (typeof v === 'string' && v == ''));
};
p.hasJQueryContext = function(v) {
return (v.hasOwnProperty('context') && p.isInDOM(v.context)) ? true : false;
};
p.trim = function(v) {
return (String.prototype.trim) ? v.trim() : v.replace(/^\s+|\s+$/g, '');
};
p.padNumber = function(v, l, p) {
p = p || '0'; v = v + '';
return v.length >= l ? v : new Array(l - v.length + 1).join(p) + v;
};
p.c2Hex = function(c) {
var hex = parseFloat(c).toString(16);
return hex.length == 1 ? "0" + hex : hex;
};
//----------------------------------
// Resources Methods
//----------------------------------
p.loadExternalScripts = function(url, id)
{
var d = document, t = 'script', s = d.createElement(t);
s.id = id;
s.src = url;
d.body.appendChild(s);
};
//---------------------------------------------------
//
// Event Handling
//
//---------------------------------------------------
p.handleCmdInput = function(e)
{
var $cmdLine = $('#konsole').find('.cmdline input');
if (e.keyCode == 13) // ENTER
{
var inputValue = $(this).val(),
inputArr = inputValue.split(" "),
cmdStr = inputArr.shift(),
args = inputArr.join(" "),
cmd = p.retrieveCommand(cmdStr.toLowerCase());
if (p.history[p.history.length-1] != inputValue)
p.history.push(inputValue);
p.historyIndex = p.history.length;
if (cmd && cmd == 'acss')
p.applyCSSBindCommands(inputValue);
else if (cmd)
cmd.func(args);
if (!cmd) {
try {
var expression = $(this).val(),
result = window.eval(expression);
if (result != undefined)
p.addMessage([result], 'log');
} catch (err ) {
p.addMessage(['Unknown command. Type "help" for a list of available commands.'], 'system');
}
}
$cmdLine.val('');
}
else if (e.ctrlKey == true) {
// do nothing...
}
else if (e.altKey == false && (e.keyCode == 38 || e.keyCode == 40)) // ARROW_UP || ARROW_DOWN
{
var cssData = p._cssEditData;
if (cssData == null || (cssData && typeof cssData.keyAccess != 'undefined' && !cssData.keyAccess)) {
if (e.keyCode == 38 && p.history.length > 0 && p.historyIndex - 1 > -1)
p.historyIndex--;
else if (e.keyCode == 40 && p.history.length > 0 && p.historyIndex + 1 < p.history.length)
p.historyIndex++;
$cmdLine.val(p.history[p.historyIndex]);
}
}
};
p.handleCmdResize = function(e)
{
var $konsole = $('#konsole'), cssData = p._cssEditData;
if (cssData != null && typeof cssData.keyAccess != 'undefined' && cssData.keyAccess)
{
var $cssSelector = $(cssData.selector),
currentVal = $cssSelector.css(cssData.keyProperty);
if (typeof currentVal != 'undefined')
{
var unit = currentVal.match(/[a-z,A-Z,%]+/g),
value = currentVal.match(/[-?0-9]+/);
unit = (unit) ? unit[0] : '';
value = (value) ? parseFloat(value[0]) : 0;
if (unit == 'px' || unit == 'em' || unit == '%') {
if (e.keyCode == 38)
value += (e.shiftKey) ? 10 : 1;
if (e.keyCode == 40)
value -= (e.shiftKey) ? 10 : 1;
$cssSelector.css(cssData.keyProperty, value + '' + unit);
p.logComputedStyle(p.getCurrentSelectorStyles(cssData), cssData);
}
}
}
else if (e.altKey && !e.ctrlKey && (e.keyCode == 38 || e.keyCode == 40))
{
if (e.keyCode == 38)
p._lastResizePercentage -= (e.shiftKey) ? 0.05 : 0.005;
else if (e.keyCode == 40)
p._lastResizePercentage += (e.shiftKey) ? 0.05 : 0.005;
Konsole.resize();
}
else if (e.ctrlKey == true && e.altKey == true && (e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40))
{
var prop;
if (e.keyCode == 38) prop = 'top';
else if (e.keyCode == 40) prop = 'bottom';
else if (e.keyCode == 37) prop = 'left';
else if (e.keyCode == 39) prop = 'right';
Konsole.snap = prop;
}
else if (!e.altKey && e.ctrlKey && (e.keyCode == 38 || e.keyCode == 40))
{
var el = $konsole.find('.log.wrapped')[0];
if (el) {
if (e.keyCode == 38)
el.scrollTop -= (e.shiftKey) ? 26 : 13;
else if (e.keyCode == 40)
el.scrollTop += (e.shiftKey) ? 26 : 13;
}
}
};
p.handleKeyboardEvent = function(e)
{
if (e.type == 'keypress' && String.fromCharCode(e.which) == Konsole.key) {
e.preventDefault();
if (!p._visible) Konsole.show();
else Konsole.hide();
}
else if (e.keyCode == 27 && p._cssEditData != null)
{
Konsole.system('CSS Edit Mode deactivated');
p._cssEditData = null;
$('#konsole').find('.cmdline input').focus();
}
};
p.handleGlobalError = function(err)
{
try {
var loc = err.colno ? err.lineno + ':' + err.colno : err.lineno,
msg = err.message,
file = err.filename.split('/').pop();
Konsole.error(msg+' <- '+file+':'+loc);
Konsole.show();
} catch (err) {}
};
p.handleDOMHoverOver = function()
{
var $konsoleHighlight = $('#konsole-highlight'),
cords,
matches = $(this).attr('class').match(/cords_(.+)(?:\W)/);
if (matches && matches[1])
cords = matches[1].split('-');
if (cords)
{
$konsoleHighlight.css({'left': cords[0]+'px', 'top': cords[1]+'px', 'width': cords[2]+'px', 'height': cords[3]+'px'});
$konsoleHighlight.show();
$(this).css('outline', '1px dashed #777');
}
};
p.handleDOMHoverOut = function()
{
$('#konsole-highlight').hide();
$(this).css('outline', 0);
};
//---------------------------------------------------
// init
//---------------------------------------------------
window.Konsole = Konsole;
Konsole.takeOver = true;
p.init();
}());