-
Notifications
You must be signed in to change notification settings - Fork 0
/
console.js
248 lines (214 loc) · 6.37 KB
/
console.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*!
* MiniConsole
* v1.5
* Useful for debugging Javascript on iOS devices without
* a full set of developer tools.
* NOT designed for production sites. For development only.
*
* NOTE: This hijacks the browser's default console.log()
* functions, so when this is in use, you won't see
* anything in the browser's normal dev tools console,
* if it has one.
*/
// Yes, document.write() is an old, clunky way of getting content
// onto a page, but in this case it's the easiest and most
// maintainable way to embed all the following quickly.
document.write(`
<h3 style="margin-bottom: 8px">Console</h3>
<div id="js-console"></div>
<form action="#" onsubmit="console.runCommand(this.commandBox.value); this.commandBox.value=''; console.scrollWindow(); return false;">
<input id="commandBox" type="text" onkeydown="console.onKeyPress(event)" onchange="console.previewCommand(this.value)">
</form>
<style>
#js-console, #js-console * {
box-sizing: border-box;
}
#js-console > div {
border: 1px solid #ddd;
background-color: #f0f0f0;
font-family: monospace;
padding: 7px 12px;
margin-top: -1px;
}
#js-console .log {
white-space: pre-wrap;
overflow-x: auto;
}
#js-console .warn { background: #ffffcc; }
#js-console .error { background: #ffdddd; }
#js-console + form input {
width: 100%;
font: 10pt monospace;
padding: 7px 12px;
border-radius: 0;
}
#js-console .prefix {
display: inline-block;
/* display: block; */
min-width: 8em;
margin-right: 2em;
opacity: 0.33;
}
</style>
`);
let consoleDiv = document.getElementById('js-console');
// If the browser doesn't already have a console object, create an empty one
console = console || {};
// We'll keep an array of past commands, which can be recalled using
// up and down arrows
console.commandHistory = [];
console.commandHistory.cursor = null;
console.commandHistory.getPrev = function() {
if (this.cursor === null)
this.cursor = this.length;
this.cursor = this.cursor-1;
return this[this.cursor];
}
console.commandHistory.getNext = function() {
this.cursor++;
if (this.cursor >= this.length) {
this.cursor = this.length;
return '';
}
return this[this.cursor];
}
console.commandHistory.pushAndReset = function(command) {
this.push(command);
this.cursor = this.length;
}
// After running a command, we scroll down the page
// 100px to keep the result and textbox in view
console.scrollWindow = function() {
window.scrollBy({top: 200, left: 0, behavior: 'smooth'});
};
console.log = function() {
console.render('log', arguments);
};
console.warn = function() {
console.render('warn', arguments);
};
console.error = function() {
console.render('error', arguments);
};
// Render a new entry to the log
console.render = function(cssClass, items, prefix) {
// Convert everything to a string representation
items = [...items].map(i => {
if (i === null)
return 'null';
// Strings
if (typeof i == 'string') {
// Truncate long ones
if (i.length > 300)
return i.substring(0, 300).htmlEncode() + '...';
return i.htmlEncode();
}
// Functions
if (typeof i == 'function')
return i.toString().htmlEncode(); // 'function() {...}';
// Reveal properties for objects
// (those that are not arrays)
if (typeof i == 'object' && !Array.isArray(i))
i = unhideProperties(i);
// All other objects
try {
let result = JSON.stringify(i, null, 2);
if (typeof result == 'string')
result = result.htmlEncode();
return result;
} catch (e) {
return 'Error: ' + e.message;
}
});
// Render in a new DIV
let div = document.createElement('div');
div.className = cssClass;
div.innerHTML =
(prefix ? `<span class="prefix">${prefix.htmlEncode()}</span>\n` : '')
+ items.join('\n');
consoleDiv.appendChild(div);
};
console.previewCommand = function(command) {
}
/**
* Called when a user manually runs some code via the text entry box
* We'll execute that code and print the result
*/
console.runCommand = function(command) {
// Since iOS tends to type in curly quotes
// we'll strip those first
command = command
.replace(/[\u2018\u2019]/g, "'")
.replace(/[\u201c\u201d]/g, '"');
// Try assigning the value, if that works
// (since eval({a:1}) doesn't show the object)
var result;
var evalError = false;
try {
result = eval('__temp__=' + command);
} catch {
// Nope, that produced an error, so instead
// we'll attempt to just eval() it
try {
result = eval(command);
} catch (e) {
evalError = true;
console.render('error', ['Error: ' + e.message], command);
}
}
// Render the result to the screen
// If it was an evalError, it's likely that the console
// hook already caught the error separately and we don't
// need to print it here again
if (!evalError)
console.render('log', [result], command);
// Remember the command for later recall
console.commandHistory.pushAndReset(command);
};
console.textBox = document.getElementById('commandBox');
// Cycle through previous commands using up and down arrows
console.onKeyPress = function(event) {
if (event.code == 'ArrowUp') {
// Remember current command so it's not lost
if (this.textBox.value &&
(this.commandHistory.cursor === null ||
this.commandHistory.cursor === this.commandHistory.length)) {
this.commandHistory.push(this.textBox.value);
}
// Bugfix: prevent the default action, otherwise
// this causes cursor to be invisibly placed
// at start of textbox
event.preventDefault();
this.textBox.value = this.commandHistory.getPrev();
}
if (event.code == 'ArrowDown') {
this.textBox.value = this.commandHistory.getNext();
}
};
// Converts all hidden properties to visible ones
function unhideProperties(obj) {
if (typeof obj !== 'object')
return obj;
let result = {};
for (let key in obj)
result[key] = obj[key];
return result;
}
// Catch all syntax and runtime errors and log them to our custom console
window.onerror = function(errorMsg, url, line, col, e, f) {
console.render('error', [`${errorMsg} in ${url}, line ${line}, col ${col}.`], 'Javascript Error:');
};
function htmlEncode(text) {
let d = document.createElement('div');
d.innerText = text;
return d.innerHTML;
}
String.prototype.htmlEncode = function() { return htmlEncode(this); };
// Is this still needed? Maybe not
// range(2,4) => [2,3,4]
function range(a,b) {
result = [];
for (let i=a; i<=b; i++)
result.push(i);
return result;
}