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 = $('')
- .addClass('cmd-in')
- break;
- default:
- this.input = $('')
- .attr('type', 'text')
- .attr('maxlength', 512)
- .addClass('cmd-in');
- }
-
- this.container.children('.cmd-in').remove();
-
- this.input.appendTo(this.container)
- .attr('title', 'Chimpcom input');
-
- this.focusOnInput();
- }
-
- /**
- * Takes the client's input and the server's output
- * and displays them appropriately.
- *
- * @param string cmd_in The command as entered by the user
- * @param string cmd_out The server output to write to screen
- */
- Cmd.prototype.displayOutput = function(cmd_out) {
- if (typeof cmd_out !== 'string') {
- cmd_out = 'Error: invalid cmd_out returned.';
- }
-
- if (this.output.html()) {
- this.output.append('
');
- }
-
- this.output.append(cmd_out + '
');
-
- if (this.options.talk) {
- this.speakOutput(cmd_out);
- }
-
- this.cmd_stack.reset();
-
- this.input.val('').removeAttr('disabled');
-
- this.enableInput();
- this.focusOnInput();
- this.activateAutofills();
- }
-
- /**
- * Take an input string and output it to the screen
- */
- Cmd.prototype.displayInput = function(cmd_in) {
- cmd_in = cmd_in.replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/"/g, """)
- .replace(/'/g, "'");
-
- this.output.append('' + this.prompt_str + ' ' +
- '' + cmd_in + '');
- }
-
- /**
- * Set the prompt string
- * @param {string} new_prompt The new prompt string
- */
- Cmd.prototype.setPrompt = function(new_prompt) {
- if (typeof new_prompt !== 'string') {
- throw 'Cmd error: invalid prompt string.';
- }
-
- this.prompt_str = new_prompt;
- this.prompt_elem.html(this.prompt_str);
- }
-
- /**
- * Post-file-drop dropzone reset
- */
- Cmd.prototype.resetDropzone = function() {
- dropzone.css('display', 'none');
- }
-
- /**
- * Add file drop handlers
- */
- Cmd.prototype.setupFiledrop = function() {
- this.dropzone = $('')
- .addClass('dropzone')
- .appendTo(wrapper)
- .filedrop({
- url: this.options.file_upload_url,
- paramname: 'dropfile', // POST parameter name used on serverside to reference file
- maxfiles: 10,
- maxfilesize: 2, // MBs
- error: function (err, file) {
- switch (err) {
- case 'BrowserNotSupported':
- alert('Your browser does not support html5 drag and drop.');
- break;
- case 'TooManyFiles':
- this.displayInput('[File Upload]');
- this.displayOutput('Too many files!');
- this.resetDropzone();
- break;
- case 'FileTooLarge':
- // FileTooLarge also has access to the file which was too large
- // use file.name to reference the filename of the culprit file
- this.displayInput('[File Upload]');
- this.displayOutput('File too big!');
- this.resetDropzone();
- break;
- default:
- this.displayInput('[File Upload]');
- this.displayOutput('Fail D:');
- this.resetDropzone();
- break;
- }
- },
- dragOver: function () { // user dragging files over #dropzone
- this.dropzone.css('display', 'block');
- },
- dragLeave: function () { // user dragging files out of #dropzone
- this.resetDropzone();
- },
- docOver: function () { // user dragging files anywhere inside the browser document window
- this.dropzone.css('display', 'block');
- },
- docLeave: function () { // user dragging files out of the browser document window
- this.resetDropzone();
- },
- drop: function () { // user drops file
- this.dropzone.append('
File dropped.');
- },
- uploadStarted: function (i, file, len) {
- this.dropzone.append('
Upload started...');
- // a file began uploading
- // i = index => 0, 1, 2, 3, 4 etc
- // file is the actual file of the index
- // len = total files user dropped
- },
- uploadFinished: function (i, file, response, time) {
- // response is the data you got back from server in JSON format.
- if (response.error !== '') {
- upload_error = response.error;
- }
- this.dropzone.append('
Upload finished! ' + response.result);
- },
- progressUpdated: function (i, file, progress) {
- // this function is used for large files and updates intermittently
- // progress is the integer value of file being uploaded percentage to completion
- this.dropzone.append('
File uploading...');
- },
- speedUpdated: function (i, file, speed) { // speed in kb/s
- this.dropzone.append('
Upload speed: ' + speed);
- },
- afterAll: function () {
- // runs after all files have been uploaded or otherwise dealt with
- if (upload_error !== '') {
- this.displayInput('[File Upload]');
- this.displayOutput('Error: ' + upload_error);
- } else {
- this.displayInput('[File Upload]');
- this.displayOutput('[File Upload]', 'Success!');
- }
-
- upload_error = '';
-
- this.dropzone.css('display', 'none');
- this.resetDropzone();
- }
- });
- }
-
- /**
- * [invert description]
- * @return {[type]} [description]
- */
- Cmd.prototype.invert = function() {
- this.wrapper.toggleClass('inverted');
- }
-
-
-
- // ====== Handlers ==============================
-
- /**
- * Do something
- */
- Cmd.prototype.handleInput = function(input_str) {
- var cmd_array = input_str.split(' ');
- var shown_input = input_str;
-
- if (this.input.attr('type') === 'password') {
- shown_input = new Array(shown_input.length + 1).join("•");
- }
-
- this.displayInput(shown_input);
-
- switch (cmd_array[0]) {
- case '':
- this.displayOutput('');
- break;
- case 'clear':
- case 'cls':
- case 'clr':
- this.clearScreen();
- break;
- case 'clearhistory':
- this.cmd_stack.empty();
- this.cmd_stack.reset();
- this.displayOutput('Command history cleared. ');
- break;
- case 'invert':
- this.invert();
- this.displayOutput('Shazam.');
- break;
- case 'shh':
- if (this.options.talk) {
- window.speechSynthesis.cancel();
- this.options.talk = false;
- this.displayOutput('Speech stopped. Talk mode is still enabled. Type TALK to disable talk mode.');
- this.options.talk = true;
- } else {
- this.displayOutput('Ok.');
- }
- break;
- case 'talk':
- if (!this.speech_synth_support) {
- this.displayOutput('You browser doesn\'t support speech synthesis.');
- return false;
- }
-
- this.options.talk = !this.options.talk;
- this.displayOutput((this.options.talk ? 'Talk mode enabled.' : 'Talk mode disabled.'));
- break;
- default:
- if (typeof this.options.external_processor !== 'function') {
- this.displayOutput(this.options.unknown_cmd);
- return false;
- }
-
- var result = this.options.external_processor(input_str, this);
-
- switch (typeof result) {
- // If undefined, external handler should
- // call handleResponse when done
- case 'boolean':
- if (!result) {
- this.displayOutput(this.options.unknown_cmd);
- }
- break;
- // If we get a response object, deal with it directly
- case 'object':
- this.handleResponse(result);
- break;
- // If we have a string, output it. This shouldn't
- // really happen but it might be useful
- case 'string':
- this.displayOutput(result);
- break;
- default:
- this.displayOutput(this.options.unknown_cmd);
- }
- }
- }
-
- /**
- * Handle JSON responses. Used as callback by external command handler
- * @param {object} res Chimpcom command object
- */
- Cmd.prototype.handleResponse = function(res) {
- if (res.redirect !== undefined) {
- document.location.href = res.redirect;
- }
-
- if (res.openWindow !== undefined) {
- window.open(res.openWindow, '_blank', res.openWindowSpecs);
- }
-
- if (res.log !== undefined && res.log !== '') {
- console.log(res.log);
- }
-
- if (res.show_pass === true) {
- this.showInputType('password');
- } else {
- this.showInputType();
- }
-
- this.displayOutput(res.cmd_out);
-
- if (res.cmd_fill !== '') {
- this.wrapper.children('.cmd-container').children('.cmd-in').first().val(res.cmd_fill);
- }
- }
-
- /**
- * Handle keypresses
- */
- Cmd.prototype.handleKeyPress = function(e) {
- var keyCode = e.keyCode || e.which,
- input_str = this.input.val(),
- autocompletions;
-
- if (keyCode === 9) { //tab
- this.tabComplete(input_str);
- } else {
- this.autocompletion_attempted = false;
- if (this.autocomplete_ajax) {
- this.autocomplete_ajax.abort();
- }
-
- if (keyCode === 13) { // enter
- if (this.input.attr('disabled')) {
- return false;
- }
-
- if (e.ctrlKey) {
- this.cmd_stack.push(input_str);
- this.goToURL(input_str);
- } else {
- this.disableInput();
-
- // push command to stack if using text input, i.e. no passwords
- if (this.input.get(0).type === 'text') {
- this.cmd_stack.push(input_str);
- }
-
- this.handleInput(input_str);
- }
- } else if (keyCode === 38) { // up arrow
- if (input_str !== "" && this.cmd_stack.cur === (this.cmd_stack.getSize() - 1)) {
- this.cmd_stack.push(input_str);
- }
-
- this.input.val(this.cmd_stack.prev());
- } else if (keyCode === 40) { // down arrow
- this.input.val(this.cmd_stack.next());
- } else if (keyCode === 27) { // esc
- if (this.container.css('opacity') > 0.5) {
- this.container.animate({'opacity': 0}, 300);
- } else {
- this.container.animate({'opacity': 1}, 300);
- }
- }
- }
- }
-
- /**
- * Prevent default action of special keys
- */
- Cmd.prototype.handleKeyUp = function(e) {
- var key = e.which;
-
- if ($.inArray(key, this.keys_array) > -1) {
- e.preventDefault();
- return false;
- }
-
- return true;
- }
-
- /**
- * Prevent default action of special keys
- */
- Cmd.prototype.handleKeyDown = function(e) {
- var key = e.which;
-
- if ($.inArray(key, this.keys_array) > -1) {
- e.preventDefault();
-
- return false;
- }
- return true;
- }
-
- /**
- * Complete command names when tab is pressed
- */
- Cmd.prototype.tabComplete = function(str) {
- // If we have a space then offload to external processor
- if (str.indexOf(' ') !== -1) {
- if (this.options.tabcomplete_url) {
- if (this.autocomplete_ajax) {
- this.autocomplete_ajax.abort();
- }
-
- this.autocomplete_ajax = $.ajax({
- url: this.options.tabcomplete_url,
- context: this,
- dataType: 'json',
- data: {
- cmd_in: str
- },
- success: function (data) {
- if (data) {
- this.input.val(data);
- }
- }
- });
- }
- this.autocompletion_attempted = false;
- return;
- }
-
- var autocompletions = this.all_commands.filter(function (value) {
- return value.startsWith(str);
- });
-
-
- if (autocompletions.length === 0) {
- return false;
- } else if (autocompletions.length === 1) {
- this.input.val(autocompletions[0]);
- } else {
- if (this.autocompletion_attempted) {
- this.displayOutput(autocompletions.join(', '));
- this.autocompletion_attempted = false;
- this.input.val(str);
- return;
- } else {
- this.autocompletion_attempted = true;
- }
- }
- }
-
-
- // ====== Helpers ===============================
-
- /**
- * Takes a user to a given url. Adds "http://" if necessary.
- */
- Cmd.prototype.goToURL = function(url) {
- if (url.substr(0, 4) !== 'http' && url.substr(0, 2) !== '//') {
- url = 'http://' + url;
- }
-
- if (popup) {
- window.open(url, '_blank');
- window.focus();
- } else {
- // break out of iframe - used by chrome plugin
- if (top.location !== location) {
- top.location.href = document.location.href;
- }
-
- location.href = url;
- }
- }
-
- /**
- * Give focus to the command input and
- * scroll to the bottom of the page
- */
- Cmd.prototype.focusOnInput = function() {
- var cmd_width;
-
- $(this.options.selector).scrollTop($(this.options.selector)[0].scrollHeight);
-
- this.input.focus();
- }
-
- /**
- * Make prompt and input fit on one line
- */
- Cmd.prototype.resizeInput = function() {
- var cmd_width = this.wrapper.width() - this.wrapper.find('.main-prompt').first().width() - 45;
-
- this.input.focus().css('width', cmd_width);
- }
-
- /**
- * Clear the screen
- */
- Cmd.prototype.clearScreen = function() {
- this.container.empty();
-
- this.output = $('')
- .addClass('cmd-output')
- .appendTo(this.container);
-
- this.prompt_elem = $('')
- .addClass('main-prompt')
- .addClass('prompt')
- .html(this.prompt_str)
- .appendTo(this.container);
-
- this.input = $('')
- .addClass('cmd-in')
- .attr('type', 'text')
- .attr('maxlength', 512)
- .appendTo(this.container);
-
- this.showInputType();
-
- this.input.val('');
- }
-
- /**
- * Attach click handlers to 'autofills' - divs which, when clicked,
- * will insert text into the input
- */
- Cmd.prototype.activateAutofills = function() {
- var input = this.input;
-
- this.wrapper
- .find('[data-type=autofill]')
- .on('click', function() {
- input.val($(this).data('autofill'));
- });
- }
-
- /**
- * Temporarily disable input while runnign commands
- */
- Cmd.prototype.disableInput = function() {
- this.input
- .attr('disabled', true)
- .val(this.options.busy_text);
- }
-
- /**
- * Reenable input after running disableInput()
- */
- Cmd.prototype.enableInput = function() {
- this.input
- .removeAttr('disabled')
- .val('');
- }
-
- /**
- * Speak output aloud using speech synthesis API
- *
- * @param {String} output Text to read
- */
- Cmd.prototype.speakOutput = function(output) {
- var msg = new SpeechSynthesisUtterance();
-
- msg.volume = 1; // 0 - 1
- msg.rate = 1; // 0.1 - 10
- msg.pitch = 2; // 0 - 2
- msg.lang = 'en-UK';
- msg.text = output;
-
- window.speechSynthesis.speak(msg);
- }
-
- return Cmd;
-}));
diff --git a/dist/js/cmd.min.js b/dist/js/cmd.min.js
deleted file mode 100644
index 8412856..0000000
--- a/dist/js/cmd.min.js
+++ /dev/null
@@ -1 +0,0 @@
-if(!Array.prototype.filter){Array.prototype.filter=function(fun){"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;imax_size){arr.shift()}cur=arr.length;setArray(arr)}function prev(){cur-=1;if(cur<0){cur=0}return arr[cur]}function next(){cur=cur+1;if(cur===arr.length){return""}if(cur>arr.length-1){cur=arr.length-1}return arr[cur]}function reset(){arr=getArray();cur=arr.length}function isEmpty(){arr=getArray();return arr.length===0}function empty(){arr=undefined;localStorage.clear();reset()}function getCur(){return cur}function getArr(){return arr}function getSize(){return arr.size}return{push:push,prev:prev,next:next,reset:reset,isEmpty:isEmpty,empty:empty,getCur:getCur,getArr:getArr,getSize:getSize}}(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()};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()}this.clearScreen();$(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))};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=$("").addClass("cmd-in");break;default:this.input=$("").attr("type","text").attr("maxlength",512).addClass("cmd-in")}this.container.children(".cmd-in").remove();this.input.appendTo(this.container).attr("title","Chimpcom input");this.focusOnInput()};Cmd.prototype.displayOutput=function(cmd_out){if(typeof cmd_out!=="string"){cmd_out="Error: invalid cmd_out returned."}if(this.output.html()){this.output.append("
")}this.output.append(cmd_out+"
");if(this.options.talk){this.speakOutput(cmd_out)}this.cmd_stack.reset();this.input.val("").removeAttr("disabled");this.enableInput();this.focusOnInput();this.activateAutofills()};Cmd.prototype.displayInput=function(cmd_in){cmd_in=cmd_in.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'");this.output.append(''+this.prompt_str+" "+''+cmd_in+"")};Cmd.prototype.setPrompt=function(new_prompt){if(typeof new_prompt!=="string"){throw"Cmd error: invalid prompt string."}this.prompt_str=new_prompt;this.prompt_elem.html(this.prompt_str)};Cmd.prototype.resetDropzone=function(){dropzone.css("display","none")};Cmd.prototype.setupFiledrop=function(){this.dropzone=$("").addClass("dropzone").appendTo(wrapper).filedrop({url:this.options.file_upload_url,paramname:"dropfile",maxfiles:10,maxfilesize:2,error:function(err,file){switch(err){case"BrowserNotSupported":alert("Your browser does not support html5 drag and drop.");break;case"TooManyFiles":this.displayInput("[File Upload]");this.displayOutput("Too many files!");this.resetDropzone();break;case"FileTooLarge":this.displayInput("[File Upload]");this.displayOutput("File too big!");this.resetDropzone();break;default:this.displayInput("[File Upload]");this.displayOutput("Fail D:");this.resetDropzone();break}},dragOver:function(){this.dropzone.css("display","block")},dragLeave:function(){this.resetDropzone()},docOver:function(){this.dropzone.css("display","block")},docLeave:function(){this.resetDropzone()},drop:function(){this.dropzone.append("
File dropped.")},uploadStarted:function(i,file,len){this.dropzone.append("
Upload started...")},uploadFinished:function(i,file,response,time){if(response.error!==""){upload_error=response.error}this.dropzone.append("
Upload finished! "+response.result)},progressUpdated:function(i,file,progress){this.dropzone.append("
File uploading...")},speedUpdated:function(i,file,speed){this.dropzone.append("
Upload speed: "+speed)},afterAll:function(){if(upload_error!==""){this.displayInput("[File Upload]");this.displayOutput("Error: "+upload_error)}else{this.displayInput("[File Upload]");this.displayOutput("[File Upload]","Success!")}upload_error="";this.dropzone.css("display","none");this.resetDropzone()}})};Cmd.prototype.invert=function(){this.wrapper.toggleClass("inverted")};Cmd.prototype.handleInput=function(input_str){var cmd_array=input_str.split(" ");var shown_input=input_str;if(this.input.attr("type")==="password"){shown_input=new Array(shown_input.length+1).join("•")}this.displayInput(shown_input);switch(cmd_array[0]){case"":this.displayOutput("");break;case"clear":case"cls":case"clr":this.clearScreen();break;case"clearhistory":this.cmd_stack.empty();this.cmd_stack.reset();this.displayOutput("Command history cleared. ");break;case"invert":this.invert();this.displayOutput("Shazam.");break;case"shh":if(this.options.talk){window.speechSynthesis.cancel();this.options.talk=false;this.displayOutput("Speech stopped. Talk mode is still enabled. Type TALK to disable talk mode.");this.options.talk=true}else{this.displayOutput("Ok.")}break;case"talk":if(!this.speech_synth_support){this.displayOutput("You browser doesn't support speech synthesis.");return false}this.options.talk=!this.options.talk;this.displayOutput(this.options.talk?"Talk mode enabled.":"Talk mode disabled.");break;default:if(typeof this.options.external_processor!=="function"){this.displayOutput(this.options.unknown_cmd);return false}var result=this.options.external_processor(input_str,this);switch(typeof result){case"boolean":if(!result){this.displayOutput(this.options.unknown_cmd)}break;case"object":this.handleResponse(result);break;case"string":this.displayOutput(result);break;default:this.displayOutput(this.options.unknown_cmd)}}};Cmd.prototype.handleResponse=function(res){if(res.redirect!==undefined){document.location.href=res.redirect}if(res.openWindow!==undefined){window.open(res.openWindow,"_blank",res.openWindowSpecs)}if(res.log!==undefined&&res.log!==""){console.log(res.log)}if(res.show_pass===true){this.showInputType("password")}else{this.showInputType()}this.displayOutput(res.cmd_out);if(res.cmd_fill!==""){this.wrapper.children(".cmd-container").children(".cmd-in").first().val(res.cmd_fill)}};Cmd.prototype.handleKeyPress=function(e){var keyCode=e.keyCode||e.which,input_str=this.input.val(),autocompletions;if(keyCode===9){this.tabComplete(input_str)}else{this.autocompletion_attempted=false;if(this.autocomplete_ajax){this.autocomplete_ajax.abort()}if(keyCode===13){if(this.input.attr("disabled")){return false}if(e.ctrlKey){this.cmd_stack.push(input_str);this.goToURL(input_str)}else{this.disableInput();if(this.input.get(0).type==="text"){this.cmd_stack.push(input_str)}this.handleInput(input_str)}}else if(keyCode===38){if(input_str!==""&&this.cmd_stack.cur===this.cmd_stack.getSize()-1){this.cmd_stack.push(input_str)}this.input.val(this.cmd_stack.prev())}else if(keyCode===40){this.input.val(this.cmd_stack.next())}else if(keyCode===27){if(this.container.css("opacity")>.5){this.container.animate({opacity:0},300)}else{this.container.animate({opacity:1},300)}}}};Cmd.prototype.handleKeyUp=function(e){var key=e.which;if($.inArray(key,this.keys_array)>-1){e.preventDefault();return false}return true};Cmd.prototype.handleKeyDown=function(e){var key=e.which;if($.inArray(key,this.keys_array)>-1){e.preventDefault();return false}return true};Cmd.prototype.tabComplete=function(str){if(str.indexOf(" ")!==-1){if(this.options.tabcomplete_url){if(this.autocomplete_ajax){this.autocomplete_ajax.abort()}this.autocomplete_ajax=$.ajax({url:this.options.tabcomplete_url,context:this,dataType:"json",data:{cmd_in:str},success:function(data){if(data){this.input.val(data)}}})}this.autocompletion_attempted=false;return}var autocompletions=this.all_commands.filter(function(value){return value.startsWith(str)});if(autocompletions.length===0){return false}else if(autocompletions.length===1){this.input.val(autocompletions[0])}else{if(this.autocompletion_attempted){this.displayOutput(autocompletions.join(", "));this.autocompletion_attempted=false;this.input.val(str);return}else{this.autocompletion_attempted=true}}};Cmd.prototype.goToURL=function(url){if(url.substr(0,4)!=="http"&&url.substr(0,2)!=="//"){url="http://"+url}if(popup){window.open(url,"_blank");window.focus()}else{if(top.location!==location){top.location.href=document.location.href}location.href=url}};Cmd.prototype.focusOnInput=function(){var cmd_width;$(this.options.selector).scrollTop($(this.options.selector)[0].scrollHeight);this.input.focus()};Cmd.prototype.resizeInput=function(){var cmd_width=this.wrapper.width()-this.wrapper.find(".main-prompt").first().width()-45;this.input.focus().css("width",cmd_width)};Cmd.prototype.clearScreen=function(){this.container.empty();this.output=$("").addClass("cmd-output").appendTo(this.container);this.prompt_elem=$("").addClass("main-prompt").addClass("prompt").html(this.prompt_str).appendTo(this.container);this.input=$("").addClass("cmd-in").attr("type","text").attr("maxlength",512).appendTo(this.container);this.showInputType();this.input.val("")};Cmd.prototype.activateAutofills=function(){var input=this.input;this.wrapper.find("[data-type=autofill]").on("click",function(){input.val($(this).data("autofill"))})};Cmd.prototype.disableInput=function(){this.input.attr("disabled",true).val(this.options.busy_text)};Cmd.prototype.enableInput=function(){this.input.removeAttr("disabled").val("")};Cmd.prototype.speakOutput=function(output){var msg=new SpeechSynthesisUtterance;msg.volume=1;msg.rate=1;msg.pitch=2;msg.lang="en-UK";msg.text=output;window.speechSynthesis.speak(msg)};return Cmd});
\ No newline at end of file
diff --git a/example.html b/example.html
index 1d8e3a4..13db8a7 100644
--- a/example.html
+++ b/example.html
@@ -3,7 +3,7 @@
Cmd example
-
+
+
+
-
-
+