mirror of
https://github.com/jsbin/jsbin.git
synced 2026-01-18 15:18:04 +00:00
1080 lines
32 KiB
JavaScript
1080 lines
32 KiB
JavaScript
//= require "../vendor/prettyprint"
|
|
//= require "../vendor/stacktrace"
|
|
|
|
var jsconsole = (function (window) {
|
|
|
|
function sortci(a, b) {
|
|
return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
|
|
}
|
|
|
|
function htmlEntities(str) {
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
// custom because I want to be able to introspect native browser objects *and* functions
|
|
function stringify(o, simple, visited) {
|
|
var json = '', i, vi, type = '', parts = [], names = [], circular = false;
|
|
visited = visited || [];
|
|
|
|
try {
|
|
type = ({}).toString.call(o);
|
|
} catch (e) { // only happens when typeof is protected (...randomly)
|
|
type = '[object Object]';
|
|
}
|
|
|
|
// check for circular references
|
|
for (vi = 0; vi < visited.length; vi++) {
|
|
if (o === visited[vi]) {
|
|
circular = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (circular) {
|
|
json = '[circular ' + type.slice(1);
|
|
if (o.outerHTML) {
|
|
json += ":\n" + htmlEntities(o.outerHTML);
|
|
}
|
|
} else if (type == '[object String]') {
|
|
json = '"' + htmlEntities(o.replace(/"/g, '\\"')) + '"';
|
|
} else if (type == '[object Array]') {
|
|
visited.push(o);
|
|
|
|
json = '[';
|
|
for (i = 0; i < o.length; i++) {
|
|
parts.push(stringify(o[i], simple, visited));
|
|
}
|
|
json += parts.join(', ') + ']';
|
|
} else if (type == '[object Object]') {
|
|
visited.push(o);
|
|
|
|
json = '{';
|
|
for (i in o) {
|
|
names.push(i);
|
|
}
|
|
names.sort(sortci);
|
|
for (i = 0; i < names.length; i++) {
|
|
parts.push( stringify(names[i], undefined, visited) + ': ' + stringify(o[ names[i] ], simple, visited) );
|
|
}
|
|
json += parts.join(', ') + '}';
|
|
} else if (type == '[object Number]') {
|
|
json = o+'';
|
|
} else if (type == '[object Boolean]') {
|
|
json = o ? 'true' : 'false';
|
|
} else if (type == '[object Function]') {
|
|
json = o.toString();
|
|
} else if (o === null) {
|
|
json = 'null';
|
|
} else if (o === undefined) {
|
|
json = 'undefined';
|
|
} else if (simple === undefined) {
|
|
visited.push(o);
|
|
|
|
json = type + '{\n';
|
|
for (i in o) {
|
|
names.push(i);
|
|
}
|
|
names.sort(sortci);
|
|
for (i = 0; i < names.length; i++) {
|
|
try {
|
|
parts.push(names[i] + ': ' + stringify(o[names[i]], true, visited)); // safety from max stack
|
|
} catch (e) {
|
|
if (e.name == 'NS_ERROR_NOT_IMPLEMENTED') {
|
|
// do nothing - not sure it's useful to show this error when the variable is protected
|
|
// parts.push(names[i] + ': NS_ERROR_NOT_IMPLEMENTED');
|
|
}
|
|
}
|
|
}
|
|
json += parts.join(',\n') + '\n}';
|
|
} else {
|
|
visited.push(o);
|
|
try {
|
|
json = stringify(o, true, visited)+''; // should look like an object
|
|
} catch (e) {
|
|
|
|
}
|
|
}
|
|
return json;
|
|
}
|
|
|
|
function cleanse(s) {
|
|
return (s||'').replace(/[<&]/g, function (m) { return {'&':'&','<':'<'}[m];});
|
|
}
|
|
|
|
function run(cmd) {
|
|
var rawoutput = null,
|
|
className = 'response',
|
|
internalCmd = internalCommand(cmd);
|
|
if (internalCmd) {
|
|
return ['info', internalCmd];
|
|
} else {
|
|
try {
|
|
rawoutput = sandboxframe.contentWindow.eval(cmd);
|
|
} catch (e) {
|
|
rawoutput = e.message;
|
|
className = 'error';
|
|
}
|
|
return [className, cleanse(stringify(rawoutput))];
|
|
}
|
|
}
|
|
|
|
function post(cmd, blind, response /* passed in when echoing from remote console */) {
|
|
cmd = trim(cmd);
|
|
|
|
if (blind === undefined) {
|
|
history.push(cmd);
|
|
setHistory(history);
|
|
}
|
|
|
|
if ((cmd.match(commandPresent) || []).length > 1) {
|
|
// split the command up in to blocks and internal commands and run sequentially
|
|
} else {
|
|
|
|
}
|
|
|
|
echo(cmd);
|
|
|
|
// order so it appears at the top
|
|
var el = document.createElement('div'),
|
|
li = document.createElement('li'),
|
|
span = document.createElement('span'),
|
|
parent = output.parentNode;
|
|
|
|
if (!internalCommand(cmd)) {
|
|
|
|
// Fix console not having iframe
|
|
if (!(sandboxframe && sandboxframe.contentWindow)) {
|
|
// Boo. There must be a nice way to do this.
|
|
sandboxframe = $live.find('iframe')[0];
|
|
// Only force it to render if there's no live iframe
|
|
if (!(sandboxframe && sandboxframe.contentWindow)) {
|
|
renderLivePreview(false);
|
|
sandboxframe = $live.find('iframe')[0];
|
|
}
|
|
jsconsole.setSandbox(sandboxframe);
|
|
}
|
|
}
|
|
|
|
// In a setTimeout so that renderLivePreview has time for the iframe to load
|
|
setTimeout(function () {
|
|
response = response || run(cmd);
|
|
|
|
if (response !== undefined) {
|
|
el.className = 'response';
|
|
span.innerHTML = response[1];
|
|
|
|
if (response[0] != 'info') prettyPrint([span]);
|
|
el.appendChild(span);
|
|
|
|
li.className = response[0];
|
|
li.innerHTML = '<span class="gutter"></span>';
|
|
li.appendChild(el);
|
|
|
|
appendLog(li);
|
|
|
|
exec.value = '';
|
|
if (enableCC) {
|
|
try {
|
|
// document.getElementsByTagName('a')[0].focus();
|
|
if (jsbin.panels.focused.id === 'console') {
|
|
if (!jsbin.embed) {
|
|
cursor.focus();
|
|
}
|
|
document.execCommand('selectAll', false, null);
|
|
document.execCommand('delete', false, null);
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
pos = history.length;
|
|
}, 0);
|
|
|
|
}
|
|
|
|
function log(msg, className) {
|
|
var li = document.createElement('li'),
|
|
div = document.createElement('div');
|
|
|
|
div.innerHTML = msg;
|
|
prettyPrint([div]);
|
|
li.className = className || 'log';
|
|
li.innerHTML = '<span class="gutter"></span>';
|
|
li.appendChild(div);
|
|
|
|
appendLog(li);
|
|
}
|
|
|
|
function echo(cmd) {
|
|
var li = document.createElement('li');
|
|
|
|
li.className = 'echo';
|
|
li.innerHTML = '<span class="gutter"></span><div>' + cleanse(cmd) + '</div>';
|
|
|
|
logAfter = null;
|
|
|
|
// logAfter = $(output).find('li.echo:first')[0] || null;
|
|
|
|
// logAfter = output.querySelector('li.echo') || null;
|
|
appendLog(li, true);
|
|
}
|
|
|
|
window.info = function(cmd) {
|
|
var li = document.createElement('li');
|
|
|
|
li.className = 'info';
|
|
li.innerHTML = '<span class="gutter"></span><div>' + cleanse(cmd) + '</div>';
|
|
|
|
// logAfter = output.querySelector('li.echo') || null;
|
|
// appendLog(li, true);
|
|
appendLog(li);
|
|
};
|
|
|
|
function appendLog(el, echo) {
|
|
output.appendChild(el);
|
|
output.parentNode.scrollTop = output.parentNode.scrollHeight + 1000;
|
|
return;
|
|
|
|
if (echo) {
|
|
if (!output.firstChild) {
|
|
output.appendChild(el);
|
|
} else {
|
|
output.insertBefore(el, output.firstChild);
|
|
}
|
|
} else {
|
|
if (!output.lastChild) {
|
|
output.appendChild(el);
|
|
} else {
|
|
// console.log(output.lastChild.nextSibling);
|
|
output.insertBefore(el, logAfter ? logAfter : output.lastChild.nextSibling); // ? output.lastChild.nextSibling : output.firstChild
|
|
}
|
|
}
|
|
}
|
|
|
|
function internalCommand(cmd) {
|
|
var parts = [], c;
|
|
if (cmd.substr(0, 1) == ':') {
|
|
parts = cmd.substr(1).split(' ');
|
|
c = parts.shift();
|
|
return (commands[c] || noop).apply(this, parts);
|
|
}
|
|
}
|
|
|
|
function noop() {}
|
|
|
|
function showhelp() {
|
|
var commands = [
|
|
':reset - destroy state and start afresh',
|
|
':history - list current session history',
|
|
':load <url> - to inject new DOM',
|
|
':load <script_url> - to inject external library',
|
|
' load also supports following shortcuts: <br /> jquery, underscore, prototype, mootools, dojo, rightjs, coffeescript, yui.<br /> eg. :load jquery',
|
|
':clear - to clear contents of the console',
|
|
':about jsconsole'
|
|
];
|
|
return commands.join('\n');
|
|
}
|
|
|
|
function load(url) {
|
|
if (navigator.onLine) {
|
|
if (arguments.length > 1 || libraries[url] || url.indexOf('.js') !== -1) {
|
|
return loadScript.apply(this, arguments);
|
|
} else {
|
|
return loadDOM(url);
|
|
}
|
|
} else {
|
|
return "You need to be online to use :load";
|
|
}
|
|
}
|
|
|
|
function loadScript() {
|
|
var doc = sandboxframe.contentDocument || sandboxframe.contentWindow.document;
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
(function (url) {
|
|
var script = document.createElement('script');
|
|
script.src = url;
|
|
script.onload = function () {
|
|
window.top.info('Loaded ' + url, 'http://' + window.location.hostname);
|
|
if (url == libraries.coffeescript) window.top.info('Now you can type CoffeeScript instead of plain old JS!');
|
|
};
|
|
script.onerror = function () {
|
|
log('Failed to load ' + url, 'error');
|
|
};
|
|
doc.body.appendChild(script);
|
|
})(libraries[arguments[i]] || arguments[i]);
|
|
}
|
|
return "Loading script...";
|
|
}
|
|
|
|
function loadDOM(url) {
|
|
var doc = sandboxframe.contentWindow.document,
|
|
script = document.createElement('script'),
|
|
cb = 'loadDOM' + +new Date;
|
|
|
|
script.src = 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22' + encodeURIComponent(url) + '%22&format=xml&callback=' + cb;
|
|
|
|
window[cb] = function (yql) {
|
|
if (yql.results.length) {
|
|
var html = yql.results[0].replace(/type="text\/javascript"/ig,'type="x"').replace(/<body.*?>/, '').replace(/<\/body>/, '');
|
|
|
|
doc.body.innerHTML = html;
|
|
window.top.info('DOM load complete');
|
|
} else {
|
|
log('Failed to load DOM', 'error');
|
|
}
|
|
try {
|
|
window[cb] = null;
|
|
delete window[cb];
|
|
} catch (e) {}
|
|
|
|
};
|
|
|
|
document.body.appendChild(script);
|
|
|
|
return "Loading url into DOM...";
|
|
}
|
|
|
|
function trim(s) {
|
|
return (s||"").replace(/^\s+|\s+$/g,"");
|
|
}
|
|
|
|
var ccCache = {};
|
|
var ccPosition = false;
|
|
|
|
window._console = {
|
|
log: function () {
|
|
var l = arguments.length, i = 0;
|
|
for (; i < l; i++) {
|
|
log(stringify(arguments[i], true));
|
|
}
|
|
window.console.log.apply(window.console, arguments);
|
|
},
|
|
dir: function () {
|
|
var l = arguments.length, i = 0;
|
|
for (; i < l; i++) {
|
|
log(stringify(arguments[i]));
|
|
}
|
|
window.console.dir.apply(window.console, arguments);
|
|
},
|
|
props: function (obj) {
|
|
var props = [], realObj;
|
|
try {
|
|
for (var p in obj) props.push(p);
|
|
} catch (e) {}
|
|
window.console.props.apply(window.console, arguments);
|
|
return props;
|
|
},
|
|
error: function (err) {
|
|
log(err.message, 'error');
|
|
window.console.error.apply(window.console, arguments);
|
|
}
|
|
};
|
|
|
|
function about() {
|
|
return 'Ported to JS Bin from <a target="_new" href="http://jsconsole.com">jsconsole.com</a>';
|
|
}
|
|
|
|
function setHistory(history) {
|
|
if (typeof JSON == 'undefined') return;
|
|
|
|
try {
|
|
// because FF with cookies disabled goes nuts, and because sometimes WebKit goes nuts too...
|
|
sessionStorage.setItem('history', JSON.stringify(history));
|
|
} catch (e) {}
|
|
}
|
|
|
|
function getHistory() {
|
|
var history = [''];
|
|
|
|
if (typeof JSON == 'undefined') return history;
|
|
|
|
try {
|
|
// because FF with cookies disabled goes nuts, and because sometimes WebKit goes nuts too...
|
|
history = JSON.parse(sessionStorage.getItem('history') || '[""]');
|
|
} catch (e) {}
|
|
return history;
|
|
}
|
|
|
|
function showHistory() {
|
|
var h = getHistory();
|
|
h.shift();
|
|
return h.join("\n");
|
|
}
|
|
|
|
var exec = document.getElementById('exec'),
|
|
form = exec.form || {},
|
|
output = null,
|
|
sandboxframe = null,
|
|
sandbox = null,
|
|
history = getHistory(),
|
|
codeCompleteTimer = null,
|
|
fakeConsole = 'window.parent._console',
|
|
libraries = {
|
|
jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js',
|
|
prototype: 'http://ajax.googleapis.com/ajax/libs/prototype/1/prototype.js',
|
|
dojo: 'http://ajax.googleapis.com/ajax/libs/dojo/1/dojo/dojo.xd.js',
|
|
mootools: 'http://ajax.googleapis.com/ajax/libs/mootools/1/mootools-yui-compressed.js',
|
|
underscore: 'http://documentcloud.github.com/underscore/underscore-min.js',
|
|
rightjs: 'http://rightjs.org/hotlink/right.js',
|
|
coffeescript: 'http://jashkenas.github.com/coffee-script/extras/coffee-script.js',
|
|
yui: 'http://yui.yahooapis.com/3.9.1/build/yui/yui-min.js'
|
|
},
|
|
body = document.getElementsByTagName('body')[0],
|
|
logAfter = null,
|
|
lastCmd = null,
|
|
wait = false,
|
|
commandPresent = /:((?:help|about|load|clear|reset|wait|history)(?:.*))\n/gi,
|
|
commands = {
|
|
history: showHistory,
|
|
help: showhelp,
|
|
about: about,
|
|
load: load,
|
|
wait: function () {
|
|
wait = true;
|
|
return '';
|
|
},
|
|
clear: function () {
|
|
setTimeout(function () { output.innerHTML = ''; }, 10);
|
|
return 'clearing...';
|
|
},
|
|
reset: function () {
|
|
output.innerHTML = '';
|
|
jsconsole.init(output, true);
|
|
return 'Context reset';
|
|
}
|
|
},
|
|
fakeInput = null,
|
|
cursor = document.getElementById('cursor'),
|
|
// I hate that I'm browser sniffing, but there's issues with Firefox and execCommand so code completion won't work
|
|
iOSMobile = navigator.userAgent.indexOf('AppleWebKit') !== -1 && navigator.userAgent.indexOf('Mobile') !== -1,
|
|
// FIXME Remy, seriously, don't sniff the agent like this, it'll bite you in the arse.
|
|
enableCC = navigator.userAgent.indexOf('AppleWebKit') !== -1 && navigator.userAgent.indexOf('Mobile') === -1 || navigator.userAgent.indexOf('OS 5_') !== -1;
|
|
|
|
if (enableCC) {
|
|
var autofocus = jsbin.embed ? '' : 'autofocus';
|
|
exec.parentNode.innerHTML = '<div ' + autofocus + ' id="exec" autocapitalize="off" spellcheck="false"><span id="cursor" spellcheck="false" autocapitalize="off" autocorrect="off"' + (iOSMobile ? '' : ' contenteditable') + '></span></div>';
|
|
exec = document.getElementById('exec');
|
|
cursor = document.getElementById('cursor');
|
|
} else {
|
|
$('#console').addClass('plain');
|
|
}
|
|
|
|
if (enableCC && iOSMobile) {
|
|
fakeInput = document.createElement('input');
|
|
fakeInput.className = 'fakeInput';
|
|
fakeInput.setAttribute('spellcheck', 'false');
|
|
fakeInput.setAttribute('autocorrect', 'off');
|
|
fakeInput.setAttribute('autocapitalize', 'off');
|
|
exec.parentNode.appendChild(fakeInput);
|
|
}
|
|
|
|
// sandbox = sandboxframe.contentDocument || sandboxframe.contentWindow.document;
|
|
|
|
// tweaks to interface to allow focus
|
|
// if (!('autofocus' in document.createElement('input'))) exec.focus();
|
|
|
|
function whichKey(event) {
|
|
var keys = {38:1, 40:1, Up:38, Down:40, Enter:10, 'U+0009':9, 'U+0008':8, 'U+0190':190, 'Right':39,
|
|
// these two are ignored
|
|
'U+0028': 57, 'U+0026': 55 };
|
|
return keys[event.keyIdentifier] || event.which || event.keyCode;
|
|
}
|
|
|
|
function setCursorTo(str) {
|
|
str = enableCC ? cleanse(str) : str;
|
|
exec.value = str;
|
|
|
|
if (enableCC) {
|
|
document.execCommand('selectAll', false, null);
|
|
document.execCommand('delete', false, null);
|
|
document.execCommand('insertHTML', false, str);
|
|
} else {
|
|
var rows = str.match(/\n/g);
|
|
exec.setAttribute('rows', rows !== null ? rows.length + 1 : 1);
|
|
}
|
|
cursor.focus();
|
|
// window.scrollTo(0,0);
|
|
}
|
|
|
|
// output.ontouchstart = output.onclick = function (event) {
|
|
// event = event || window.event;
|
|
// if (event.target.nodeName == 'A' && event.target.className == 'permalink') {
|
|
// var command = decodeURIComponent(event.target.search.substr(1));
|
|
// setCursorTo(command);
|
|
|
|
// if (liveHistory) {
|
|
// window.history.pushState(command, command, event.target.href);
|
|
// }
|
|
|
|
// return false;
|
|
// }
|
|
// };
|
|
|
|
exec.ontouchstart = function () {
|
|
window.scrollTo(0,0);
|
|
};
|
|
|
|
exec.onkeyup = function (event) {
|
|
var which = whichKey(event);
|
|
|
|
if (enableCC && which != 9 && which != 16) {
|
|
clearTimeout(codeCompleteTimer);
|
|
codeCompleteTimer = setTimeout(function () {
|
|
codeComplete(event);
|
|
}, 200);
|
|
}
|
|
};
|
|
|
|
if (enableCC) {
|
|
// disabled for now
|
|
cursor.__onpaste = function (event) {
|
|
setTimeout(function () {
|
|
// this causes the field to lose focus - I'll leave it here for a while, see how we get on.
|
|
// what I need to do is rip out the contenteditable and replace it with something entirely different
|
|
cursor.innerHTML = cursor.innerText;
|
|
// setCursorTo(cursor.innerText);
|
|
}, 10);
|
|
};
|
|
}
|
|
|
|
function findNode(list, node) {
|
|
var pos = 0;
|
|
for (var i = 0; i < list.length; i++) {
|
|
if (list[i] == node) {
|
|
return pos;
|
|
}
|
|
pos += list[i].nodeValue.length;
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
exec.onkeydown = function (event) {
|
|
event = event || window.event;
|
|
var keys = {38:1, 40:1},
|
|
wide = body.className == 'large',
|
|
which = whichKey(event);
|
|
|
|
if (typeof which == 'string') which = which.replace(/\/U\+/, '\\u');
|
|
if (keys[which]) {
|
|
if (event.shiftKey) {
|
|
// changeView(event);
|
|
} else if (!wide) { // history cycle
|
|
if (enableCC && window.getSelection) {
|
|
window.selObj = window.getSelection();
|
|
var selRange = selObj.getRangeAt(0);
|
|
|
|
cursorPos = findNode(selObj.anchorNode.parentNode.childNodes, selObj.anchorNode) + selObj.anchorOffset;
|
|
var value = exec.value,
|
|
firstnl = value.indexOf('\n'),
|
|
lastnl = value.lastIndexOf('\n');
|
|
|
|
if (firstnl !== -1) {
|
|
if (which == 38 && cursorPos > firstnl) {
|
|
return;
|
|
} else if (which == 40 && cursorPos < lastnl) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (which == 38) { // cycle up
|
|
pos--;
|
|
if (pos < 0) pos = 0; //history.length - 1;
|
|
} else if (which == 40) { // down
|
|
pos++;
|
|
if (pos >= history.length) pos = history.length; //0;
|
|
}
|
|
if (history[pos] != undefined && history[pos] !== '') {
|
|
removeSuggestion();
|
|
setCursorTo(history[pos])
|
|
return false;
|
|
} else if (pos == history.length) {
|
|
removeSuggestion();
|
|
setCursorTo('');
|
|
return false;
|
|
}
|
|
}
|
|
} else if ((which == 13 || which == 10) && event.shiftKey == false) { // enter (what about the other one)
|
|
removeSuggestion();
|
|
if (event.shiftKey == true || event.metaKey || event.ctrlKey || !wide) {
|
|
var command = exec.textContent || exec.value;
|
|
if (command.length) post(command);
|
|
return false;
|
|
}
|
|
} else if ((which == 13 || which == 10) && !enableCC && event.shiftKey == true) {
|
|
// manually expand the textarea when we don't have code completion turned on
|
|
var rows = exec.value.match(/\n/g);
|
|
rows = rows != null ? rows.length + 2 : 2;
|
|
exec.setAttribute('rows', rows);
|
|
} else if (which == 9 && wide) {
|
|
checkTab(event);
|
|
} else if (event.shiftKey && event.metaKey && which == 8) {
|
|
output.innerHTML = '';
|
|
} else if ((which == 39 || which == 35) && ccPosition !== false) { // complete code
|
|
completeCode();
|
|
} else if (event.ctrlKey && which == 76) {
|
|
output.innerHTML = '';
|
|
} else if (enableCC) { // try code completion
|
|
if (ccPosition !== false && which == 9) {
|
|
codeComplete(event); // cycles available completions
|
|
return false;
|
|
} else if (ccPosition !== false && cursor.nextSibling) {
|
|
removeSuggestion();
|
|
}
|
|
}
|
|
};
|
|
|
|
if (enableCC && iOSMobile) {
|
|
fakeInput.onkeydown = function (event) {
|
|
removeSuggestion();
|
|
var which = whichKey(event);
|
|
|
|
if (which == 13 || which == 10) {
|
|
post(this.value);
|
|
this.value = '';
|
|
cursor.innerHTML = '';
|
|
return false;
|
|
}
|
|
};
|
|
|
|
fakeInput.onkeyup = function (event) {
|
|
cursor.innerHTML = cleanse(this.value);
|
|
var which = whichKey(event);
|
|
if (enableCC && which != 9 && which != 16) {
|
|
clearTimeout(codeCompleteTimer);
|
|
codeCompleteTimer = setTimeout(function () {
|
|
codeComplete(event);
|
|
}, 200);
|
|
}
|
|
};
|
|
|
|
var fakeInputFocused = false;
|
|
|
|
var dblTapTimer = null,
|
|
taps = 0;
|
|
|
|
form.addEventListener('touchstart', function (event) {
|
|
// window.scrollTo(0,0);
|
|
if (ccPosition !== false) {
|
|
event.preventDefault();
|
|
clearTimeout(dblTapTimer);
|
|
taps++;
|
|
|
|
if (taps === 2) {
|
|
completeCode();
|
|
fakeInput.value = cursor.textContent;
|
|
removeSuggestion();
|
|
fakeInput.focus();
|
|
} else {
|
|
dblTapTimer = setTimeout(function () {
|
|
taps = 0;
|
|
codeComplete({ which: 9 });
|
|
}, 200);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
function completeCode(focus) {
|
|
var tmp = exec.textContent, l = tmp.length;
|
|
removeSuggestion();
|
|
|
|
cursor.innerHTML = tmp;
|
|
ccPosition = false;
|
|
|
|
// daft hack to move the focus elsewhere, then back on to the cursor to
|
|
// move the cursor to the end of the text.
|
|
document.getElementsByTagName('a')[0].focus();
|
|
cursor.focus();
|
|
|
|
var range, selection;
|
|
if (document.createRange) {//Firefox, Chrome, Opera, Safari, IE 9+
|
|
range = document.createRange();//Create a range (a range is a like the selection but invisible)
|
|
range.selectNodeContents(cursor);//Select the entire contents of the element with the range
|
|
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
|
|
selection = window.getSelection();//get the selection object (allows you to change selection)
|
|
selection.removeAllRanges();//remove any selections already made
|
|
selection.addRange(range);//make the range you have just created the visible selection
|
|
} else if (document.selection) {//IE 8 and lower
|
|
range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
|
|
range.moveToElementText(cursor);//Select the entire contents of the element with the range
|
|
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
|
|
range.select();//Select the range (make it the visible selection
|
|
}
|
|
}
|
|
|
|
form.onsubmit = function (event) {
|
|
event = event || window.event;
|
|
event.preventDefault && event.preventDefault();
|
|
removeSuggestion();
|
|
post(exec.textContent || exec.value);
|
|
return false;
|
|
};
|
|
|
|
document.onkeydown = function (event) {
|
|
event = event || window.event;
|
|
var which = event.which || event.keyCode;
|
|
|
|
if (event.shiftKey && event.metaKey && which == 8) {
|
|
output.innerHTML = '';
|
|
cursor.focus();
|
|
} else if (event.target == output.parentNode && which == 32) { // space
|
|
output.parentNode.scrollTop += 5 + output.parentNode.offsetHeight * (event.shiftKey ? -1 : 1);
|
|
}
|
|
|
|
// return changeView(event);
|
|
};
|
|
|
|
exec.onclick = function () {
|
|
cursor.focus();
|
|
}
|
|
|
|
function getProps(cmd, filter) {
|
|
var surpress = {}, props = [];
|
|
|
|
if (!ccCache[cmd]) {
|
|
try {
|
|
// surpress alert boxes because they'll actually do something when we're looking
|
|
// up properties inside of the command we're running
|
|
surpress.alert = sandboxframe.contentWindow.alert;
|
|
sandboxframe.contentWindow.alert = function () {};
|
|
|
|
// loop through all of the properties available on the command (that's evaled)
|
|
ccCache[cmd] = sandboxframe.contentWindow.eval('console.props(' + cmd + ')').sort();
|
|
|
|
// return alert back to it's former self
|
|
delete sandboxframe.contentWindow.alert;
|
|
} catch (e) {
|
|
ccCache[cmd] = [];
|
|
}
|
|
|
|
// if the return value is undefined, then it means there's no props, so we'll
|
|
// empty the code completion
|
|
if (ccCache[cmd][0] == 'undefined') ccOptions[cmd] = [];
|
|
ccPosition = 0;
|
|
props = ccCache[cmd];
|
|
} else if (filter) {
|
|
// console.log('>>' + filter, cmd);
|
|
for (var i = 0, p; i < ccCache[cmd].length, p = ccCache[cmd][i]; i++) {
|
|
if (p.indexOf(filter) === 0) {
|
|
if (p != filter) {
|
|
props.push(p.substr(filter.length, p.length));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
props = ccCache[cmd];
|
|
}
|
|
|
|
return props;
|
|
}
|
|
|
|
function codeComplete(event) {
|
|
var cmd = cursor.textContent.split(/[;\s]+/g).pop(),
|
|
parts = cmd.split('.'),
|
|
which = whichKey(event),
|
|
cc,
|
|
props = [];
|
|
|
|
if (cmd) {
|
|
// get the command without the dot to allow us to introspect
|
|
if (cmd.substr(-1) == '.') {
|
|
// get the command without the '.' so we can eval it and lookup the properties
|
|
cmd = cmd.substr(0, cmd.length - 1);
|
|
|
|
// returns an array of all the properties from the command
|
|
props = getProps(cmd);
|
|
} else {
|
|
props = getProps(parts.slice(0, parts.length - 1).join('.') || 'window', parts[parts.length - 1]);
|
|
}
|
|
if (props.length) {
|
|
if (which == 9) { // tabbing cycles through the code completion
|
|
// however if there's only one selection, it'll auto complete
|
|
if (props.length === 1) {
|
|
ccPosition = false;
|
|
} else {
|
|
if (event.shiftKey) {
|
|
// backwards
|
|
ccPosition = ccPosition == 0 ? props.length - 1 : ccPosition-1;
|
|
} else {
|
|
ccPosition = ccPosition == props.length - 1 ? 0 : ccPosition+1;
|
|
}
|
|
}
|
|
} else {
|
|
ccPosition = 0;
|
|
}
|
|
|
|
if (ccPosition === false) {
|
|
completeCode();
|
|
} else {
|
|
// position the code completion next to the cursor
|
|
if (!cursor.nextSibling) {
|
|
cc = document.createElement('span');
|
|
cc.className = 'suggest';
|
|
exec.appendChild(cc);
|
|
}
|
|
|
|
cursor.nextSibling.innerHTML = props[ccPosition];
|
|
exec.value = exec.textContent;
|
|
}
|
|
|
|
if (which == 9) return false;
|
|
} else {
|
|
ccPosition = false;
|
|
}
|
|
} else {
|
|
ccPosition = false;
|
|
}
|
|
|
|
if (ccPosition === false && cursor.nextSibling) {
|
|
removeSuggestion();
|
|
}
|
|
|
|
exec.value = exec.textContent;
|
|
}
|
|
|
|
function removeSuggestion() {
|
|
if (!enableCC) exec.setAttribute('rows', 1);
|
|
if (enableCC && cursor.nextSibling) cursor.parentNode.removeChild(cursor.nextSibling);
|
|
}
|
|
|
|
var jsconsole = {
|
|
run: post,
|
|
clear: commands.clear,
|
|
reset: function () {
|
|
this.run(':reset');
|
|
},
|
|
focus: function () {
|
|
if (enableCC) {
|
|
cursor.focus();
|
|
} else {
|
|
$(exec).focus();
|
|
}
|
|
},
|
|
echo: echo,
|
|
setSandbox: function (newSandbox) {
|
|
// sandboxframe.parentNode.removeChild(sandboxframe);
|
|
sandboxframe = newSandbox;
|
|
|
|
sandbox = sandboxframe.contentDocument || sandboxframe.contentWindow.document;
|
|
// sandbox.open();
|
|
// stupid jumping through hoops if Firebug is open, since overwriting console throws error
|
|
// sandbox.write('<script>(function () { var fakeConsole = ' + fakeConsole + '; if (window.console != undefined) { for (var k in fakeConsole) { console[k] = fakeConsole[k]; } } else { console = fakeConsole; } })();</script>');
|
|
// sandbox.write('<script>window.print=function(){};window.alert=function(){};window.prompt=function(){};window.confirm=function(){};</script>');
|
|
// sandbox.open();
|
|
// sandbox.write(getPreparedCode(true));
|
|
sandboxframe.contentWindow.eval('(function () { var fakeConsole = ' + fakeConsole + '; if (window.console != undefined) { for (var k in fakeConsole) { console[k] = fakeConsole[k]; } } else { console = fakeConsole; } })();');
|
|
|
|
// sandbox.close();
|
|
|
|
this.sandboxframe = sandboxframe;
|
|
|
|
if (sandbox.readyState !== 'complete') {
|
|
this.ready = false;
|
|
} else {
|
|
jsconsole.onload();
|
|
}
|
|
|
|
sandbox.onreadystatechange = function () {
|
|
if (sandbox.readyState === 'complete') {
|
|
jsconsole.ready = true;
|
|
jsconsole.onload();
|
|
}
|
|
};
|
|
|
|
getProps('window'); // cache
|
|
},
|
|
_onloadQueue: [],
|
|
onload: function (fn) {
|
|
var i = 0, length = this._onloadQueue.length;
|
|
if (this.ready === false && fn) { // if not ready and callback passed - cache it
|
|
this._onloadQueue.push(fn);
|
|
} else if (this.ready === true && !fn) { // if ready and not callback - flush cache
|
|
for (; i < length; i++) {
|
|
this._onloadQueue[i].call(this);
|
|
}
|
|
this._onloadQueue = [];
|
|
} else if (fn) { // if callback and ready - run callback
|
|
fn.call(this);
|
|
}
|
|
},
|
|
init: function (outputElement, nohelp) {
|
|
output = outputElement;
|
|
|
|
// closure scope
|
|
sandboxframe = $live.find('iframe')[0]; //document.createElement('iframe');
|
|
|
|
// var oldsandbox = document.getElementById('jsconsole-sandbox');
|
|
// if (oldsandbox) {
|
|
// body.removeChild(oldsandbox);
|
|
// }
|
|
|
|
// body.appendChild(sandboxframe);
|
|
// sandboxframe.setAttribute('id', 'jsconsole-sandbox');
|
|
if (sandboxframe) this.setSandbox(sandboxframe);
|
|
|
|
if (nohelp === undefined) post(':help', true);
|
|
},
|
|
rawMessage: function (data) {
|
|
if (data.type && data.type == 'error') {
|
|
post(data.cmd, true, ['error', data.response]);
|
|
} else if (data.type && data.type == 'info') {
|
|
window.top.info(data.response);
|
|
} else {
|
|
if (data.cmd.indexOf('console.log') === -1) data.response = data.response.substr(1, data.response.length - 2); // fiddle to remove the [] around the repsonse
|
|
echo(data.cmd);
|
|
log(data.response, 'response');
|
|
}
|
|
},
|
|
stringify: stringify
|
|
};
|
|
|
|
return jsconsole;
|
|
|
|
})(this);
|
|
|
|
var msgType = '';
|
|
|
|
jsconsole.init(document.getElementById('output'));
|
|
jsconsole.queue = [];
|
|
jsconsole.remote = {
|
|
log: function () {
|
|
// window.console.log('remote call');
|
|
var cmd = 'console.log';
|
|
try {
|
|
throw new Error();
|
|
} catch (e) {
|
|
// var trace = printStackTrace({ error: e }),
|
|
// code = jsbin.panels.panels.javascript.getCode().split('\n'),
|
|
// allcode = getPreparedCode().split('\n'),
|
|
// parts = [],
|
|
// line,
|
|
// n;
|
|
|
|
// for (var i = 0; i < trace.length; i++) {
|
|
// if (trace[i].indexOf(window.location.toString()) !== -1) {
|
|
// parts = trace[i].split(':');
|
|
// n = parts.pop();
|
|
// if (isNaN(parseInt(n, 10))) {
|
|
// n = parts.pop();
|
|
// }
|
|
// line = n - 2;
|
|
// if (code[line] && code[line].indexOf('console.') !== -1) {
|
|
// cmd = $.trim(code[line]);
|
|
// console.log(cmd);
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
|
|
var argsObj = jsconsole.stringify(arguments.length == 1 ? arguments[0] : [].slice.call(arguments, 0));
|
|
var response = [];
|
|
[].forEach.call(arguments, function (args) {
|
|
response.push(jsconsole.stringify(args, true));
|
|
});
|
|
|
|
var msg = { response: response, cmd: cmd, type: msgType };
|
|
|
|
if (jsconsole.ready) {
|
|
jsconsole.rawMessage(msg);
|
|
} else {
|
|
jsconsole.queue.push(msg);
|
|
}
|
|
|
|
msgType = '';
|
|
},
|
|
info: function () {
|
|
msgType = 'info';
|
|
remote.log.apply(this, arguments);
|
|
},
|
|
echo: function () {
|
|
var args = [].slice.call(arguments, 0),
|
|
plain = args.pop(),
|
|
cmd = args.pop(),
|
|
response = args;
|
|
|
|
var argsObj = jsconsole.stringify(response, plain),
|
|
msg = { response: argsObj, cmd: cmd };
|
|
if (jsconsole.ready) {
|
|
jsconsole.rawMessage(msg);
|
|
} else {
|
|
jsconsole.queue.push(msg);
|
|
}
|
|
},
|
|
error: function (error, cmd) {
|
|
var msg = { response: error.message, cmd: cmd, type: 'error' };
|
|
if (jsconsole.ready) {
|
|
jsconsole.rawMessage(msg);
|
|
} else {
|
|
jsconsole.queue.push(msg);
|
|
}
|
|
},
|
|
flush: function () {
|
|
for (var i = 0; i < jsconsole.queue.length; i++) {
|
|
jsconsole.rawMessage(jsconsole.queue[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
// just for extra support
|
|
jsconsole.remote.debug = jsconsole.remote.dir = jsconsole.remote.log;
|
|
jsconsole.remote.warn = jsconsole.remote.info;
|
|
|
|
// window.top._console = jsconsole.remote;
|
|
|
|
function upgradeConsolePanel(console) {
|
|
// console.init = function () {
|
|
console.$el.click(function () {
|
|
jsconsole.focus();
|
|
});
|
|
console.reset = function () {
|
|
jsconsole.reset();
|
|
};
|
|
console.settings.render = function (withAlerts) {
|
|
var html = editors.html.render().trim();
|
|
if (html === "") {
|
|
var code = editors.javascript.render().trim();
|
|
jsconsole.run(code);
|
|
} else {
|
|
renderLivePreview(withAlerts || false);
|
|
}
|
|
};
|
|
console.settings.show = function () {
|
|
jsconsole.clear();
|
|
// renderLivePreview(true);
|
|
// setTimeout because the renderLivePreview creates the iframe after a timeout
|
|
setTimeout(function () {
|
|
// jsconsole.setSandbox($live.find('iframe')[0]);
|
|
if (editors.console.ready && !jsbin.embed) jsconsole.focus();
|
|
}, 0);
|
|
};
|
|
console.settings.hide = function () {
|
|
// Removal code is commented out so that the
|
|
// output iframe is never removed
|
|
if (!editors.live.visible) {
|
|
// $live.find('iframe').remove();
|
|
}
|
|
};
|
|
// jsconsole.ready = true;
|
|
jsconsole.remote.flush();
|
|
|
|
$document.one('jsbinReady', function () {
|
|
var hidebutton = function () {
|
|
$('#runconsole')[this.visible ? 'hide' : 'show']();
|
|
};
|
|
|
|
jsbin.panels.panels.live.on('show', hidebutton).on('hide', hidebutton);
|
|
|
|
if (jsbin.panels.panels.live.visible) {
|
|
$('#runconsole').hide();
|
|
}
|
|
|
|
});
|
|
// editors.console.fakeConsole = window._console
|
|
// };
|
|
|
|
// console.init();
|
|
}
|