if (!Array.prototype.filter) { Array.prototype.filter = function(fun/*, thisArg*/) { 'use strict'; if (this === void 0 || this === null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (typeof fun !== 'function') { throw new TypeError(); } var res = []; var thisArg = arguments.length >= 2 ? arguments[1] : void 0; for (var i = 0; i < len; i++) { if (i in t) { var val = t[i]; // NOTE: Technically this should Object.defineProperty at // the next index, as push can be affected by // properties on Object.prototype and Array.prototype. // But that method's new, and collisions should be // rare, so use the more-compatible alternative. if (fun.call(thisArg, val, i, t)) { res.push(val); } } } return res; }; } if (!String.prototype.startsWith) { String.prototype.startsWith = function(searchString, position) { position = position || 0; return this.indexOf(searchString, position) === position; }; } /** * Stack for holding previous commands for retrieval with the up arrow. * Stores data in localStorage. Won't push consecutive duplicates. * * @author Jake Gully, chimpytk@gmail.com * @license MIT License */ /** * Constructor * @param {string} id Unique id for this stack * @param {integer} max_size Number of commands to store */ function CmdStack(id, max_size) { "use strict"; var instance_id = id, cur = 0, arr = []; // This is a fairly meaningless name but // makes it sound like this function was // written by a pirate. I'm keeping it. if (typeof id !== 'string') { throw 'Stack error: id should be a string.'; } if (typeof max_size !== 'number') { throw 'Stack error: max_size should be a number.'; } /** * Store the array in localstorage */ function setArray(arr) { localStorage['cmd_stack_' + instance_id] = JSON.stringify(arr); } /** * Load array from localstorage */ function getArray() { if (!localStorage['cmd_stack_' + instance_id]) { arr = []; setArray(arr); } try { arr = JSON.parse(localStorage['cmd_stack_' + instance_id]); } catch (err) { return []; } return arr; } /** * Push a command to the array * @param {string} cmd Command to append to stack */ function push(cmd) { arr = getArray(); // don't push if same as last command if (cmd === arr[arr.length - 1]) { return false; } arr.push(cmd); // crop off excess while (arr.length > max_size) { arr.shift(); } cur = arr.length; setArray(arr); } /** * Get previous command from stack (up key) * @return {string} Retrieved command string */ function prev() { cur -= 1; if (cur < 0) { cur = 0; } return arr[cur]; } /** * Get next command from stack (down key) * @return {string} Retrieved command string */ function next() { cur = cur + 1; // Return a blank string as last item if (cur === arr.length) { return ""; } // Limit if (cur > (arr.length - 1)) { cur = (arr.length - 1); } return arr[cur]; } /** * Move cursor to last element */ function reset() { arr = getArray(); cur = arr.length; } /** * Is stack empty * @return {Boolean} True if stack is empty */ function isEmpty() { arr = getArray(); return (arr.length === 0); } /** * Empty array and remove from localstorage */ function empty() { arr = undefined; localStorage.clear(); reset(); } /** * Get current cursor location * @return {integer} Current cursor index */ function getCur() { return cur; } /** * Get entire stack array * @return {array} The stack array */ function getArr() { return arr; } /** * Get size of the stack * @return {Integer} Size of stack */ function getSize(){ return arr.size; } return { push: push, prev: prev, next: next, reset: reset, isEmpty: isEmpty, empty: empty, getCur: getCur, getArr: getArr, getSize: getSize }; } /** * HTML5 Command Line Terminal * * @author Jake Gully (chimpytk@gmail.com) * @license MIT License */ (function (root, factory) { if ( typeof define === 'function' && define.amd ) { define(factory); } else if ( typeof exports === 'object' ) { module.exports = factory(); } else { root.Cmd = factory(); } }(this, function () { "use strict"; var Cmd = function (user_config) { this.keys_array = [9, 13, 38, 40, 27], this.style = 'dark', this.popup = false, this.prompt_str = '$ ', this.speech_synth_support = ('speechSynthesis' in window && typeof SpeechSynthesisUtterance !== 'undefined'), this.options = { busy_text: 'Communicating...', external_processor: function() {}, filedrop_enabled: false, file_upload_url: 'ajax/uploadfile.php', history_id: 'cmd_history', remote_cmd_list_url: '', selector: '#cmd', tabcomplete_url: '', talk: false, unknown_cmd: 'Unrecognised command', typewriter_time: 32 }, this.voices = false; this.remote_commands = []; this.all_commands = []; this.local_commands = [ 'clear', 'clr', 'cls', 'clearhistory', 'invert', 'shh', 'talk' ]; this.autocompletion_attempted = false; $.extend(this.options, user_config); if (this.options.remote_cmd_list_url) { $.ajax({ url: this.options.remote_cmd_list_url, context: this, dataType: 'json', method: 'GET', success: function (data) { this.remote_commands = data; this.all_commands = $.merge(this.remote_commands, this.local_commands) } }); } else { this.all_commands = this.local_commands; } if (!$(this.options.selector).length) { throw 'Cmd err: Invalid selector.'; } this.cmd_stack = new CmdStack(this.options.history_id, 30); if (this.cmd_stack.isEmpty()) { this.cmd_stack.push('secretmagicword!'); } this.cmd_stack.reset(); this.setupDOM(); this.input.focus(); } // ====== Layout / IO / Alter Interface ========= /** * Create DOM elements, add click & key handlers */ Cmd.prototype.setupDOM = function() { this.wrapper = $(this.options.selector).addClass('cmd-interface'); this.container = $('
') .addClass('cmd-container') .appendTo(this.wrapper); if (this.options.filedrop_enabled) { setupFiledrop(); // adds dropzone div } this.clearScreen(); // adds output, input and prompt $(this.options.selector).on('click', $.proxy(this.focusOnInput, this)); $(window).resize($.proxy(this.resizeInput, this)); this.wrapper.keydown($.proxy(this.handleKeyDown, this)); this.wrapper.keyup($.proxy(this.handleKeyUp, this)); this.wrapper.keydown($.proxy(this.handleKeyPress, this)); } /** * Changes the input type */ Cmd.prototype.showInputType = function(input_type) { switch (input_type) { case 'password': this.input = $('') .attr('type', 'password') .attr('maxlength', 512) .addClass('cmd-in'); break; case 'textarea': this.input = $('