diff --git a/.gitignore b/.gitignore index e608b97..80b4810 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules bower_components index.html +dist/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f5ebde2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "dependencies/jquery"] + path = dependencies/jquery + url = https://github.com/jquery/jquery.git +[submodule "dependencies/jquery.typer"] + path = dependencies/jquery.typer + url = https://github.com/yckart/jquery.typer.js.git diff --git a/Gruntfile.js b/Gruntfile.js index a4a27cd..115ff44 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,17 +1,13 @@ module.exports = function(grunt) { - - var js_files = [ - 'bower_components/typewriter/jquery.typer.js', - 'src/js/Polyfills.js', - 'src/js/CmdStack.js', - 'src/js/Cmd.js', - ]; - grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), concat: { dist: { - src: js_files, + src: [ + 'src/js/Polyfills.js', + 'src/js/CmdStack.js', + 'src/js/Cmd.js', + ], dest: 'dist/js/cmd.js' } }, @@ -53,7 +49,8 @@ module.exports = function(grunt) { } }, dist: { - files: js_files, + //All files that exist within src/ and end in .js + files: ['src/**/*.js'], tasks: [ 'concat:dist', 'uglify' @@ -62,19 +59,30 @@ module.exports = function(grunt) { nospawn: true } } + }, + copy: { + main: { + files: [ + {expand: true, flatten: true, filter: 'isFile', src: ['dependencies/jquery/dist/**'], dest: 'dist/external/jquery/'}, + {expand: true, flatten: true, filter: 'isFile', src: ['dependencies/jquery.typer/jquery.typer.js'], dest: 'dist/external/'}, + {expand: true, src: ['example.html', 'commands.json', 'tabcomplete.json'], dest: 'dist/', + rename: function(dest, src) { + return dest + src.replace('example.html','index.html'); + } + } + ] + } } }); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-less'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-focus'); + // Load grunt tasks from NPM packages + require( "load-grunt-tasks" )( grunt ); grunt.registerTask('default', [ 'concat', 'uglify', - 'less:dist' + 'less:dist', + 'copy' ]); grunt.registerTask('watch-all', [ diff --git a/README.md b/README.md index 31d8b63..aa68130 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ Features * JSON API * Text-to-speech output (optional, where available) +Install and run demo +===== + +You can build and try Cmd using npm. + + npm run build + npm run start + Bower ===== diff --git a/dependencies/jquery b/dependencies/jquery new file mode 160000 index 0000000..5f35b5b --- /dev/null +++ b/dependencies/jquery @@ -0,0 +1 @@ +Subproject commit 5f35b5b406ae7d504de86a3f0a5647b2fdf4f2af diff --git a/dependencies/jquery.typer b/dependencies/jquery.typer new file mode 160000 index 0000000..44ad357 --- /dev/null +++ b/dependencies/jquery.typer @@ -0,0 +1 @@ +Subproject commit 44ad357b353c3ebc7cda020c3e9ea01c5bf9e9b6 diff --git a/dist/css/cmd.min.css b/dist/css/cmd.min.css deleted file mode 100644 index 721bd2f..0000000 --- a/dist/css/cmd.min.css +++ /dev/null @@ -1 +0,0 @@ -.cmd-interface{background-color:#101010;color:#ccc;cursor:text;font-size:18px;font-family:Menlo,Consolas,Monaco,monospace;font-weight:300;line-height:133%;overflow:auto}.cmd-interface.inverted{background-color:#f0f0f0;color:#000}@media (max-screen-width:600px){.cmd-interface{font-size:10px;line-height:140%}}.cmd-interface *,.cmd-interface :before,.cmd-interface :after{margin:0;padding:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cmd-interface::selection,.cmd-interface::-moz-selection,.cmd-interface::-webkit-selection{background-color:#ccc;color:#101010}.cmd-interface::selection.inverted,.cmd-interface::-moz-selection.inverted,.cmd-interface::-webkit-selection.inverted{color:#f0f0f0;background-color:#000}.cmd-interface .cmd-container{background-color:transparent;padding:10px 4px 4px;margin:0;-webkit-transform:translate3d(0,0,0);-webkit-perspective:1000;width:100%}.cmd-interface .cmd-in,.cmd-interface .pass-in{background-color:transparent;border:0;color:#ccc;font-family:Menlo,Consolas,Monaco,monospace;font-size:18px;outline:0;width:80%}.inverted.cmd-interface .cmd-in,.inverted.cmd-interface .pass-in{color:#000}.cmd-interface .pass-in{display:none}.cmd-interface .cmd-out{margin:0;padding:0}.cmd-interface .autofill{cursor:pointer}.cmd-interface table{padding:0;margin:0;display:inline-table}.cmd-interface td{vertical-align:text-top;padding:0 3px 0 0;margin:0}.cmd-interface th{font-style:italic;padding:0 3px 0 0;margin:0;text-align:left}.cmd-interface ul{margin:0;padding-left:20px;display:inline-block}.cmd-interface dl{display:inline-block;margin:0}.cmd-interface dd{margin-left:18px;color:#666;display:inline-block}.cmd-interface dt:before{content:"\2022 \0020"}a{color:#ccc}a::selection,a::-webkit-selection,a::-moz-selection,a.inverted{color:#101010;background-color:#080}a::selection.cmd-container.invert,a::-webkit-selection.cmd-container.invert,a::-moz-selection.cmd-container.invert,a.inverted.cmd-container.invert{color:#f0f0f0}a:hover{color:#999;text-decoration:underline}a.grey_text{color:#666}.prompt{color:#080;font-weight:300;text-shadow:0 0 113px rgba(0,255,0,.4)}.prompt::selection,.prompt::-webkit-selection,.prompt::-moz-selection,.prompt.inverted{color:#101010;background-color:#080}.prompt::selection.cmd-container.invert,.prompt::-webkit-selection.cmd-container.invert,.prompt::-moz-selection.cmd-container.invert,.prompt.inverted.cmd-container.invert{color:#f0f0f0}.red_highlight{color:#900;font-weight:700}.red_highlight::selection,.red_highlight::-webkit-selection,.red_highlight::-moz-selection,.red_highlight.inverted{color:#101010;background-color:#080}.red_highlight::selection.cmd-container.invert,.red_highlight::-webkit-selection.cmd-container.invert,.red_highlight::-moz-selection.cmd-container.invert,.red_highlight.inverted.cmd-container.invert{color:#f0f0f0}.blue_highlight{color:#578998;font-weight:700}.blue_highlight::selection,.blue_highlight::-webkit-selection,.blue_highlight::-moz-selection,.blue_highlight.inverted{color:#101010;background-color:#080}.blue_highlight::selection.cmd-container.invert,.blue_highlight::-webkit-selection.cmd-container.invert,.blue_highlight::-moz-selection.cmd-container.invert,.blue_highlight.inverted.cmd-container.invert{color:#f0f0f0}.green_highlight{color:#669866;font-weight:300}.green_highlight::selection,.green_highlight::-webkit-selection,.green_highlight::-moz-selection,.green_highlight.inverted{color:#101010;background-color:#080}.green_highlight::selection.cmd-container.invert,.green_highlight::-webkit-selection.cmd-container.invert,.green_highlight::-moz-selection.cmd-container.invert,.green_highlight.inverted.cmd-container.invert{color:#f0f0f0}.grey_text{color:#666}.grey_text::selection,.grey_text::-webkit-selection,.grey_text::-moz-selection,.grey_text.inverted{color:#101010;background-color:#080}.grey_text::selection.cmd-container.invert,.grey_text::-webkit-selection.cmd-container.invert,.grey_text::-moz-selection.cmd-container.invert,.grey_text.inverted.cmd-container.invert{color:#f0f0f0}.ascii{line-height:50%;letter-spacing:-1px}code{font-family:'courier new',monospace;font-weight:300;font-size:15px;color:#fff} \ No newline at end of file diff --git a/dist/js/cmd.js b/dist/js/cmd.js deleted file mode 100644 index 2a7ac9b..0000000 --- a/dist/js/cmd.js +++ /dev/null @@ -1,890 +0,0 @@ -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 = $('