-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathtermino.js
395 lines (287 loc) · 11.9 KB
/
termino.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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/**!
* @license Termino.js - A JavaScript library to make custom terminals in the browser with support for executing your own custom functions!
* VERSION: 2.0.0
* LICENSED UNDER MIT LICENSE
* MORE INFO CAN BE FOUND AT https://github.com/MarketingPipeline/Termino.js/
*/
/* DEV & CONTRIBUTOR NOTES IE: TODO LIST -
- NEED TO IMPROVE / WRITE DOCUMENTATION FOR LIBRARY - URGENT TASK NEEDS HELP BIG TIME!
- CREATE DOCUMENTATION WEBSITE HOSTED VIA GITHUB PAGES BRANCH
- REMOVE WIKI FROM REPO.
- SUPPORT FOR MULTIPLE KEYBINDS / KEYBOARD SHORT CUTS (VIA MOUSETRAP ETC ON NPM / GITHUB)
- IMPROVE ERROR HANDLING (CHECK IF PASSED PROPER ARGUMENTS - CHECK IF VARIABLE IS ARRAY TYPE / JSON TYPE ETC...)
- REMOVE EVENT HANDLERS WHEN TERMINAL INSTANCE IS KILLED.
- CREATE TESTS + ACTION / WORKFLOW
- BROWSER AUTOMATION TESTS VIA PUPPETEER ETC (CHECK ALL DEVICES / BROWSER COMPABILITY + POSSIBLY SCREENSHOTS).
- OTHER TESTS.
- CREATE ACTION THAT AUTO TESTS ON PR.
- IF ANYONE COULD HELP WRITING TESTS / THESE WOULD BE APPRECIATED.
- LOTS OF OTHER IMPROVEMENTS THAT CAN BE MADE THO THIS. IF YOU ARE WILLING TO IMPROVE IT. FEEL FREE! :)
*/
// POLYFILL SUPPORT (AUTO-DETECTED ON LOAD FOR DEVICE) - REMOVED AS OF V2.0.0
// import 'https://polyfill.io/v3/polyfill.min.js?features=Array.prototype.filter,console,document,JSON,Promise'
export function Termino(terminalSelector, keyCodes, settings) {
try {
// CHECK IF QUERY SELECTOR WAS PROVIDED
if(!terminalSelector){
throw({message:"No Query Selector was provided."})
return;
}
// DEFAULT TERMINAL SETTINGS
let DEF_SETTINGS = {
allow_scroll: true, // allow scroll up & down on terminal
prompt: "> ", // default prompt
command_key: 13, // default command key
terminal_killed_placeholder: "TERMINAL DISABLED", // default terminal input placeholder when killed.
terminal_output: ".termino-console", // default output query selector
terminal_input: ".termino-input", // default input query selector
disable_terminal_input: false // disable any user commands / inputs. --- Useful for making terminal animations etc!
}
/// ALLOW DEVS TO PASS CUSTOM SETTINGS FOR TERMINAL
if (settings) {
// function to compare custom settings
function compare(json1, json2) {
let keys1 = Object.keys(json1);
let keys2 = Object.keys(json2);
if (keys1.length != keys2.length) {
return false;
}
for (var i = 0; i < keys1.length; i++) {
if (keys1[i] != keys2[i]) {
return false;
}
}
return true;
}
// CUSTOM SETTINGS PASSED ARE NOT VALID
if (compare(DEF_SETTINGS, settings) != true) {
throw {
message: "Settings Error: Your overwritten Termino settings are not valid"
}
} else {
// CUSTOM SETTINGS ARE VALID
DEF_SETTINGS = settings
}
}
let terminal_console = terminalSelector.querySelector(DEF_SETTINGS.terminal_output)
/// DEFAULT TERMINAL KEY CODES
let KEYCODES = [{
"id": "SCROLL_UP_KEY", /// DEFAULT SCROLL UP KEY - "SCROLL_UP_KEY" ID NAME IS REQUIRED.
"key_code": 38
// "function": example() // you can add your own custom function to the scroll up button if needed!
}, {
"id": "SCROLL_DOWN_KEY", // DFEAULT SCROLL DOWN KEY - "SCROLL_DOWN_KEY" ID NAME IS REQUIRED.
"key_code": 40
// "function": example() // you can add your own custom function to the scroll down button if needed!
}];
// DEFAULT SCROLL BTNS
/// UP ARROW
let Scroll_Up_Key = KEYCODES[0].key_code
/// DOWN ARROW
let Scroll_Down_Key = KEYCODES[1].key_code
/// ALLOW DEVS TO PASS CUSTOM KEYCODE FUNCTIONS FOR TERMINAL
if (keyCodes) {
// Check if scroll up key has been set
if (keyCodes.filter(x => x.id === "SCROLL_UP_KEY").length != 0) {
if (keyCodes.filter(x => x.id === "SCROLL_UP_KEY")[0].key_code != undefined) {
// set custom scroll up key
Scroll_Up_Key = keyCodes.filter(x => x.id === "SCROLL_UP_KEY")[0].key_code
}
}
// Check if scroll down key has been set
if (keyCodes.filter(x => x.id === "SCROLL_DOWN_KEY").length != 0) {
if (keyCodes.filter(x => x.id === "SCROLL_DOWN_KEY")[0].key_code != undefined) {
// set custom scroll down key
Scroll_Down_Key = keyCodes.filter(x => x.id === "SCROLL_DOWN_KEY")[0].key_code
}
}
KEYCODES = keyCodes
}
// DEFAULT COMMAND KEY - ie - ENTER BTN
let Command_Key = DEF_SETTINGS.command_key
// Handle keyboard events for the Termino.js instance.
terminalSelector.addEventListener('keydown', e => {
//// DISABLE TERMINAL SCROLL UP / DOWN FOR ANIMATIONS ETC...
if (DEF_SETTINGS.disable_terminal_input != true) {
/// HANDLE INPUTS ON COMMAND KEY.
checkIfCommand()
}
/// SCROLL UP / DOWN TERMINAL FUNCTION
if (DEF_SETTINGS.allow_scroll === true) {
if (e.keyCode == Scroll_Up_Key) {
/// SCROLL TERMINAL UP
terminal_console.scrollTo({
top: 0,
behavior: 'smooth'
});
} else if (e.keyCode == Scroll_Down_Key) {
/// SCROLL TERMINAL DOWN
terminal_console.scrollTop = terminal_console.scrollHeight;
}
}
});
// TERMINAL INPUT STATE / TERMINAL PROMPT FUNCTION
let InputState = false;
function termInput(question) {
return new Promise(function(resolve) {
/// add the question value to terminal
terminal_console.innerHTML += question
termClearValue()
scrollTerminalToBottom()
InputState = true;
function handleCommandForQuestion(event) {
if (event.keyCode == Command_Key) {
if (window.event.preventDefault) {
window.event.preventDefault()
}
let value = terminalSelector.querySelector(DEF_SETTINGS.terminal_input).value
termClearValue()
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).removeEventListener('keypress', handleCommandForQuestion);
InputState = false;
if (value.length != 0) {
// echo value to terminal
termEcho(value)
resolve(value)
} else {
// return an empty prompt
termEcho("")
resolve()
}
}
}
/// Handle inputs for question state.
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).addEventListener('keypress', handleCommandForQuestion);
})
}
// FUNCTION TO OUTPUT TO TERMINAL (WITH TERMINAL PROMPT)
function termEcho(command) {
terminal_console.innerHTML += `<pre>${DEF_SETTINGS.prompt} ${command}</pre>`
scrollTerminalToBottom()
}
// FUNCTION TO OUTPUT TO TERMINAL (WITHOUT TERMINAL PROMPT)
function termOutput(command) {
terminal_console.innerHTML += `<pre>${command}</pre>`
scrollTerminalToBottom()
}
// FUNCTION TO CLEAR TERMINAL CONSOLE OUTPUT
function termClear() {
terminal_console.innerHTML = ``
}
/// DEFAULT FUNCTION TO KILL TERMINAL - DEVS CAN PROGRAM THEIR OWN IF THEY WANT.
function termKill() {
/// TODO - REMOVE EVENT LISTENERS
/// clear terminal
termClear()
/// set the terminal text values to disabled
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).setAttribute("disabled", "");
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).setAttribute("placeholder", DEF_SETTINGS.terminal_killed_placeholder);
}
/// FUNCTION TO ENABLE TERMINAL INPUT
function termEnable() {
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).removeAttribute("disabled", "");
}
/// FUNCTION TO DISABLE TERMINAL INPUT
function termDisable() {
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).setAttribute("disabled", "");
}
/// FUNCTION TO REMOVE / CLEAR INPUT VALUE
function termClearValue() {
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).value = ""
}
/// FUNCTION TO DELAY TERMINAL OUTPUTS / ECHO ETC (AWAIT / PROMISE BASED) - EXAMPLE : await term.delay(xxx) ...
const termDelay = ms => new Promise(res => setTimeout(res, ms));
/// FUNCTION TO SCROLL TERMINAL TO THE BOTTOM
function scrollTerminalToBottom() {
terminal_console.scrollTop = terminal_console.scrollHeight;
}
/// FUNCTION TO SCROLL TERMINAL TO THE TOP
function scrollTerminalToTop() {
terminal_console.scrollTo({
top: 0,
behavior: 'smooth'
});
}
/// DISABLE INPUT IF DEFAULT SETTING IS SET TO TRUE
if (DEF_SETTINGS.disable_terminal_input === true) {
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).setAttribute("disabled", "");
}
/// ADD ELEMENT TO TERMINAL BY ID, HTML & CLASS NAME
function addElementWithID(id, html, class_name) {
let g = null;
g = document.createElement('div');
if (class_name) {
g.setAttribute("class", class_name);
}
g.setAttribute("id", id);
if (html) {
g.innerHTML = html
}
terminal_console.appendChild(g);
}
/// REMOVE ADDED ELEMENT FROM TERMINAL BY ID
function removeElementWithID(id) {
try {
terminalSelector.querySelector("#" + id).outerHTML = "";
} catch (error) {
throw {
message: `Error could not find ${error.message}`
}
}
}
// If the user has pressed COMMAND btn - default btn is enter
async function checkIfCommand() {
let key = window.event.keyCode;
/// RUN ANY FUNCTIONS FOR KEYCODES / KEYBIND SHORTCUTS / BUTTONS.
if (KEYCODES.filter(x => x.key_code === key).length != 0) {
KEYCODES = KEYCODES.filter(x => x.key_code === key)
if (KEYCODES.length != 0) {
if (KEYCODES[0].function != undefined)
try {
await eval(KEYCODES[0].function)
} catch (error) {
throw {
message: `KeyCode Function Error: ${error.message}`
}
}
}
}
/// MAKE SURE USER IS NOT ANSWERING A QUESTION
if (InputState != true) {
/// ECHO INPUT VALUE ON COMMAND BUTTON - BY DEFAULT IS ENTER.
if (key === Command_Key) {
/// STOP ENTER FROM GOING DOWN / DOING WEIRD THINGS..
if (window.event.preventDefault) {
window.event.preventDefault()
}
/// ECHO USER INPUT
termEcho(terminalSelector.querySelector(DEF_SETTINGS.terminal_input).value)
/// CLEAR OUTPUT
termClearValue()
}
}
} /// DEFAULT TERMINO FUNCTIONS FOR DEVELOPER USAGE
return {
echo: termEcho, // ECHO MESSAGE TO TERM WITH CAROT
output: termOutput, // ECHO MESSAGE TO TERM WITHOUT CAROT
clear: termClear, // CLEAR THE TERMINAL
delay: termDelay, // DELAY FUNCTION BY X VALUE OF SECONDS
disable_input: termDisable, // DISABLE TERMINAL INPUT
enable_input: termEnable, // ENABLE TERMINAL INPUT
input: termInput, // ASK USER QUESTION & RETURN VALUE
scroll_to_bottom: scrollTerminalToBottom, // SCROLL TERMINAL TO THE BOTTOM
scroll_to_top: scrollTerminalToTop, // SCROLL TERMINAL TO TOP
add_element: addElementWithID, // ADD HTML ELEMENT WITH ID TO TERMINAL,
remove_element: removeElementWithID, // REMOVE HTML ELEMENT WITH ID TO TERMINAL,
kill: termKill // KILL THE TERMIMAL - IE.. SET INPUT TO DISABLED & CLEAR THE TERMINAL.
};
} catch (error) {
// Something went wrong!
console.error(`Termino.js Error: ${error.message}`)
throw {
message: `Termino.js Error: ${error.message}`
}
}
}
if (typeof document === 'undefined') {
console.error("Termino.js is only supported for the browser")
}