cmd.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. if (!Array.prototype.filter) {
  2. Array.prototype.filter = function(fun/*, thisArg*/) {
  3. 'use strict';
  4. if (this === void 0 || this === null) {
  5. throw new TypeError();
  6. }
  7. var t = Object(this);
  8. var len = t.length >>> 0;
  9. if (typeof fun !== 'function') {
  10. throw new TypeError();
  11. }
  12. var res = [];
  13. var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
  14. for (var i = 0; i < len; i++) {
  15. if (i in t) {
  16. var val = t[i];
  17. // NOTE: Technically this should Object.defineProperty at
  18. // the next index, as push can be affected by
  19. // properties on Object.prototype and Array.prototype.
  20. // But that method's new, and collisions should be
  21. // rare, so use the more-compatible alternative.
  22. if (fun.call(thisArg, val, i, t)) {
  23. res.push(val);
  24. }
  25. }
  26. }
  27. return res;
  28. };
  29. }
  30. if (!String.prototype.startsWith) {
  31. String.prototype.startsWith = function(searchString, position) {
  32. position = position || 0;
  33. return this.indexOf(searchString, position) === position;
  34. };
  35. }
  36. /**
  37. * Stack for holding previous commands for retrieval with the up arrow.
  38. * Stores data in localStorage. Won't push consecutive duplicates.
  39. *
  40. * @author Jake Gully, chimpytk@gmail.com
  41. * @license MIT License
  42. */
  43. /**
  44. * Constructor
  45. * @param {string} id Unique id for this stack
  46. * @param {integer} max_size Number of commands to store
  47. */
  48. function CmdStack(id, max_size) {
  49. "use strict";
  50. var instance_id = id,
  51. cur = 0,
  52. arr = []; // This is a fairly meaningless name but
  53. // makes it sound like this function was
  54. // written by a pirate. I'm keeping it.
  55. if (typeof id !== 'string') {
  56. throw 'Stack error: id should be a string.';
  57. }
  58. if (typeof max_size !== 'number') {
  59. throw 'Stack error: max_size should be a number.';
  60. }
  61. /**
  62. * Store the array in localstorage
  63. */
  64. function setArray(arr) {
  65. localStorage['cmd_stack_' + instance_id] = JSON.stringify(arr);
  66. }
  67. /**
  68. * Load array from localstorage
  69. */
  70. function getArray() {
  71. if (!localStorage['cmd_stack_' + instance_id]) {
  72. arr = [];
  73. setArray(arr);
  74. }
  75. try {
  76. arr = JSON.parse(localStorage['cmd_stack_' + instance_id]);
  77. } catch (err) {
  78. return [];
  79. }
  80. return arr;
  81. }
  82. /**
  83. * Push a command to the array
  84. * @param {string} cmd Command to append to stack
  85. */
  86. function push(cmd) {
  87. arr = getArray();
  88. // don't push if same as last command
  89. if (cmd === arr[arr.length - 1]) {
  90. return false;
  91. }
  92. arr.push(cmd);
  93. // crop off excess
  94. while (arr.length > max_size) {
  95. arr.shift();
  96. }
  97. cur = arr.length;
  98. setArray(arr);
  99. }
  100. /**
  101. * Get previous command from stack (up key)
  102. * @return {string} Retrieved command string
  103. */
  104. function prev() {
  105. cur -= 1;
  106. if (cur < 0) {
  107. cur = 0;
  108. }
  109. return arr[cur];
  110. }
  111. /**
  112. * Get next command from stack (down key)
  113. * @return {string} Retrieved command string
  114. */
  115. function next() {
  116. cur = cur + 1;
  117. // Return a blank string as last item
  118. if (cur === arr.length) {
  119. return "";
  120. }
  121. // Limit
  122. if (cur > (arr.length - 1)) {
  123. cur = (arr.length - 1);
  124. }
  125. return arr[cur];
  126. }
  127. /**
  128. * Move cursor to last element
  129. */
  130. function reset() {
  131. arr = getArray();
  132. cur = arr.length;
  133. }
  134. /**
  135. * Is stack empty
  136. * @return {Boolean} True if stack is empty
  137. */
  138. function isEmpty() {
  139. arr = getArray();
  140. return (arr.length === 0);
  141. }
  142. /**
  143. * Empty array and remove from localstorage
  144. */
  145. function empty() {
  146. arr = undefined;
  147. localStorage.clear();
  148. reset();
  149. }
  150. /**
  151. * Get current cursor location
  152. * @return {integer} Current cursor index
  153. */
  154. function getCur() {
  155. return cur;
  156. }
  157. /**
  158. * Get entire stack array
  159. * @return {array} The stack array
  160. */
  161. function getArr() {
  162. return arr;
  163. }
  164. /**
  165. * Get size of the stack
  166. * @return {Integer} Size of stack
  167. */
  168. function getSize(){
  169. return arr.size;
  170. }
  171. return {
  172. push: push,
  173. prev: prev,
  174. next: next,
  175. reset: reset,
  176. isEmpty: isEmpty,
  177. empty: empty,
  178. getCur: getCur,
  179. getArr: getArr,
  180. getSize: getSize
  181. };
  182. }
  183. /**
  184. * HTML5 Command Line Terminal
  185. *
  186. * @author Jake Gully (chimpytk@gmail.com)
  187. * @license MIT License
  188. */
  189. (function (root, factory) {
  190. if ( typeof define === 'function' && define.amd ) {
  191. define(factory);
  192. } else if ( typeof exports === 'object' ) {
  193. module.exports = factory();
  194. } else {
  195. root.Cmd = factory();
  196. }
  197. }(this, function () {
  198. "use strict";
  199. var Cmd = function (user_config) {
  200. this.keys_array = [9, 13, 38, 40, 27],
  201. this.style = 'dark',
  202. this.popup = false,
  203. this.prompt_str = '$ ',
  204. this.speech_synth_support = ('speechSynthesis' in window && typeof SpeechSynthesisUtterance !== 'undefined'),
  205. this.options = {
  206. busy_text: 'Communicating...',
  207. external_processor: function() {},
  208. filedrop_enabled: false,
  209. file_upload_url: 'ajax/uploadfile.php',
  210. history_id: 'cmd_history',
  211. remote_cmd_list_url: '',
  212. selector: '#cmd',
  213. tabcomplete_url: '',
  214. talk: false,
  215. unknown_cmd: 'Unrecognised command',
  216. typewriter_time: 32
  217. },
  218. this.voices = false;
  219. this.remote_commands = [];
  220. this.all_commands = [];
  221. this.local_commands = [
  222. 'clear',
  223. 'clr',
  224. 'cls',
  225. 'clearhistory',
  226. 'invert',
  227. 'shh',
  228. 'talk'
  229. ];
  230. this.autocompletion_attempted = false;
  231. $.extend(this.options, user_config);
  232. if (this.options.remote_cmd_list_url) {
  233. $.ajax({
  234. url: this.options.remote_cmd_list_url,
  235. context: this,
  236. dataType: 'json',
  237. method: 'GET',
  238. success: function (data) {
  239. this.remote_commands = data;
  240. this.all_commands = $.merge(this.remote_commands, this.local_commands)
  241. }
  242. });
  243. } else {
  244. this.all_commands = this.local_commands;
  245. }
  246. if (!$(this.options.selector).length) {
  247. throw 'Cmd err: Invalid selector.';
  248. }
  249. this.cmd_stack = new CmdStack(this.options.history_id, 30);
  250. if (this.cmd_stack.isEmpty()) {
  251. this.cmd_stack.push('secretmagicword!');
  252. }
  253. this.cmd_stack.reset();
  254. this.setupDOM();
  255. this.input.focus();
  256. }
  257. // ====== Layout / IO / Alter Interface =========
  258. /**
  259. * Create DOM elements, add click & key handlers
  260. */
  261. Cmd.prototype.setupDOM = function() {
  262. this.wrapper = $(this.options.selector).addClass('cmd-interface');
  263. this.container = $('<div/>')
  264. .addClass('cmd-container')
  265. .appendTo(this.wrapper);
  266. if (this.options.filedrop_enabled) {
  267. setupFiledrop(); // adds dropzone div
  268. }
  269. this.clearScreen(); // adds output, input and prompt
  270. $(this.options.selector).on('click', $.proxy(this.focusOnInput, this));
  271. $(window).resize($.proxy(this.resizeInput, this));
  272. this.wrapper.keydown($.proxy(this.handleKeyDown, this));
  273. this.wrapper.keyup($.proxy(this.handleKeyUp, this));
  274. this.wrapper.keydown($.proxy(this.handleKeyPress, this));
  275. }
  276. /**
  277. * Changes the input type
  278. */
  279. Cmd.prototype.showInputType = function(input_type) {
  280. switch (input_type) {
  281. case 'password':
  282. this.input = $('<input/>')
  283. .attr('type', 'password')
  284. .attr('maxlength', 512)
  285. .addClass('cmd-in');
  286. break;
  287. case 'textarea':
  288. this.input = $('<textarea/>')
  289. .addClass('cmd-in')
  290. break;
  291. default:
  292. this.input = $('<input/>')
  293. .attr('type', 'text')
  294. .attr('maxlength', 512)
  295. .addClass('cmd-in');
  296. }
  297. this.container.children('.cmd-in').remove();
  298. this.input.appendTo(this.container)
  299. .attr('title', 'Chimpcom input');
  300. this.focusOnInput();
  301. }
  302. /**
  303. * Takes the client's input and the server's output
  304. * and displays them appropriately.
  305. *
  306. * @param string cmd_in The command as entered by the user
  307. * @param string cmd_out The server output to write to screen
  308. */
  309. Cmd.prototype.displayOutput = function(cmd_out) {
  310. if (typeof cmd_out !== 'string') {
  311. cmd_out = 'Error: invalid cmd_out returned.';
  312. }
  313. if (this.output.html()) {
  314. this.output.append('<br>');
  315. }
  316. this.output.append(cmd_out + '<br>');
  317. if (this.options.talk) {
  318. this.speakOutput(cmd_out);
  319. }
  320. this.cmd_stack.reset();
  321. this.input.val('').removeAttr('disabled');
  322. this.enableInput();
  323. this.focusOnInput();
  324. this.activateAutofills();
  325. }
  326. /**
  327. * Take an input string and output it to the screen
  328. */
  329. Cmd.prototype.displayInput = function(cmd_in) {
  330. cmd_in = cmd_in.replace(/&/g, "&amp;")
  331. .replace(/</g, "&lt;")
  332. .replace(/>/g, "&gt;")
  333. .replace(/"/g, "&quot;")
  334. .replace(/'/g, "&#039;");
  335. this.output.append('<span class="prompt">' + this.prompt_str + '</span> ' +
  336. '<span class="grey_text">' + cmd_in + '</span>');
  337. }
  338. /**
  339. * Set the prompt string
  340. * @param {string} new_prompt The new prompt string
  341. */
  342. Cmd.prototype.setPrompt = function(new_prompt) {
  343. if (typeof new_prompt !== 'string') {
  344. throw 'Cmd error: invalid prompt string.';
  345. }
  346. this.prompt_str = new_prompt;
  347. this.prompt_elem.html(this.prompt_str);
  348. }
  349. /**
  350. * Post-file-drop dropzone reset
  351. */
  352. Cmd.prototype.resetDropzone = function() {
  353. dropzone.css('display', 'none');
  354. }
  355. /**
  356. * Add file drop handlers
  357. */
  358. Cmd.prototype.setupFiledrop = function() {
  359. this.dropzone = $('<div/>')
  360. .addClass('dropzone')
  361. .appendTo(wrapper)
  362. .filedrop({
  363. url: this.options.file_upload_url,
  364. paramname: 'dropfile', // POST parameter name used on serverside to reference file
  365. maxfiles: 10,
  366. maxfilesize: 2, // MBs
  367. error: function (err, file) {
  368. switch (err) {
  369. case 'BrowserNotSupported':
  370. alert('Your browser does not support html5 drag and drop.');
  371. break;
  372. case 'TooManyFiles':
  373. this.displayInput('[File Upload]');
  374. this.displayOutput('Too many files!');
  375. this.resetDropzone();
  376. break;
  377. case 'FileTooLarge':
  378. // FileTooLarge also has access to the file which was too large
  379. // use file.name to reference the filename of the culprit file
  380. this.displayInput('[File Upload]');
  381. this.displayOutput('File too big!');
  382. this.resetDropzone();
  383. break;
  384. default:
  385. this.displayInput('[File Upload]');
  386. this.displayOutput('Fail D:');
  387. this.resetDropzone();
  388. break;
  389. }
  390. },
  391. dragOver: function () { // user dragging files over #dropzone
  392. this.dropzone.css('display', 'block');
  393. },
  394. dragLeave: function () { // user dragging files out of #dropzone
  395. this.resetDropzone();
  396. },
  397. docOver: function () { // user dragging files anywhere inside the browser document window
  398. this.dropzone.css('display', 'block');
  399. },
  400. docLeave: function () { // user dragging files out of the browser document window
  401. this.resetDropzone();
  402. },
  403. drop: function () { // user drops file
  404. this.dropzone.append('<br>File dropped.');
  405. },
  406. uploadStarted: function (i, file, len) {
  407. this.dropzone.append('<br>Upload started...');
  408. // a file began uploading
  409. // i = index => 0, 1, 2, 3, 4 etc
  410. // file is the actual file of the index
  411. // len = total files user dropped
  412. },
  413. uploadFinished: function (i, file, response, time) {
  414. // response is the data you got back from server in JSON format.
  415. if (response.error !== '') {
  416. upload_error = response.error;
  417. }
  418. this.dropzone.append('<br>Upload finished! ' + response.result);
  419. },
  420. progressUpdated: function (i, file, progress) {
  421. // this function is used for large files and updates intermittently
  422. // progress is the integer value of file being uploaded percentage to completion
  423. this.dropzone.append('<br>File uploading...');
  424. },
  425. speedUpdated: function (i, file, speed) { // speed in kb/s
  426. this.dropzone.append('<br>Upload speed: ' + speed);
  427. },
  428. afterAll: function () {
  429. // runs after all files have been uploaded or otherwise dealt with
  430. if (upload_error !== '') {
  431. this.displayInput('[File Upload]');
  432. this.displayOutput('Error: ' + upload_error);
  433. } else {
  434. this.displayInput('[File Upload]');
  435. this.displayOutput('[File Upload]', 'Success!');
  436. }
  437. upload_error = '';
  438. this.dropzone.css('display', 'none');
  439. this.resetDropzone();
  440. }
  441. });
  442. }
  443. /**
  444. * [invert description]
  445. * @return {[type]} [description]
  446. */
  447. Cmd.prototype.invert = function() {
  448. this.wrapper.toggleClass('inverted');
  449. }
  450. // ====== Handlers ==============================
  451. /**
  452. * Do something
  453. */
  454. Cmd.prototype.handleInput = function(input_str) {
  455. var cmd_array = input_str.split(' ');
  456. var shown_input = input_str;
  457. if (this.input.attr('type') === 'password') {
  458. shown_input = new Array(shown_input.length + 1).join("•");
  459. }
  460. this.displayInput(shown_input);
  461. switch (cmd_array[0]) {
  462. case '':
  463. this.displayOutput('');
  464. break;
  465. case 'clear':
  466. case 'cls':
  467. case 'clr':
  468. this.clearScreen();
  469. break;
  470. case 'clearhistory':
  471. this.cmd_stack.empty();
  472. this.cmd_stack.reset();
  473. this.displayOutput('Command history cleared. ');
  474. break;
  475. case 'invert':
  476. this.invert();
  477. this.displayOutput('Shazam.');
  478. break;
  479. case 'shh':
  480. if (this.options.talk) {
  481. window.speechSynthesis.cancel();
  482. this.options.talk = false;
  483. this.displayOutput('Speech stopped. Talk mode is still enabled. Type TALK to disable talk mode.');
  484. this.options.talk = true;
  485. } else {
  486. this.displayOutput('Ok.');
  487. }
  488. break;
  489. case 'talk':
  490. if (!this.speech_synth_support) {
  491. this.displayOutput('You browser doesn\'t support speech synthesis.');
  492. return false;
  493. }
  494. this.options.talk = !this.options.talk;
  495. this.displayOutput((this.options.talk ? 'Talk mode enabled.' : 'Talk mode disabled.'));
  496. break;
  497. default:
  498. if (typeof this.options.external_processor !== 'function') {
  499. this.displayOutput(this.options.unknown_cmd);
  500. return false;
  501. }
  502. var result = this.options.external_processor(input_str, this);
  503. switch (typeof result) {
  504. // If undefined, external handler should
  505. // call handleResponse when done
  506. case 'boolean':
  507. if (!result) {
  508. this.displayOutput(this.options.unknown_cmd);
  509. }
  510. break;
  511. // If we get a response object, deal with it directly
  512. case 'object':
  513. this.handleResponse(result);
  514. break;
  515. // If we have a string, output it. This shouldn't
  516. // really happen but it might be useful
  517. case 'string':
  518. this.displayOutput(result);
  519. break;
  520. default:
  521. this.displayOutput(this.options.unknown_cmd);
  522. }
  523. }
  524. }
  525. /**
  526. * Handle JSON responses. Used as callback by external command handler
  527. * @param {object} res Chimpcom command object
  528. */
  529. Cmd.prototype.handleResponse = function(res) {
  530. if (res.redirect !== undefined) {
  531. document.location.href = res.redirect;
  532. }
  533. if (res.openWindow !== undefined) {
  534. window.open(res.openWindow, '_blank', res.openWindowSpecs);
  535. }
  536. if (res.log !== undefined && res.log !== '') {
  537. console.log(res.log);
  538. }
  539. if (res.show_pass === true) {
  540. this.showInputType('password');
  541. } else {
  542. this.showInputType();
  543. }
  544. this.displayOutput(res.cmd_out);
  545. if (res.cmd_fill !== '') {
  546. this.wrapper.children('.cmd-container').children('.cmd-in').first().val(res.cmd_fill);
  547. }
  548. }
  549. /**
  550. * Handle keypresses
  551. */
  552. Cmd.prototype.handleKeyPress = function(e) {
  553. var keyCode = e.keyCode || e.which,
  554. input_str = this.input.val(),
  555. autocompletions;
  556. if (keyCode === 9) { //tab
  557. this.tabComplete(input_str);
  558. } else {
  559. this.autocompletion_attempted = false;
  560. if (this.autocomplete_ajax) {
  561. this.autocomplete_ajax.abort();
  562. }
  563. if (keyCode === 13) { // enter
  564. if (this.input.attr('disabled')) {
  565. return false;
  566. }
  567. if (e.ctrlKey) {
  568. this.cmd_stack.push(input_str);
  569. this.goToURL(input_str);
  570. } else {
  571. this.disableInput();
  572. // push command to stack if using text input, i.e. no passwords
  573. if (this.input.get(0).type === 'text') {
  574. this.cmd_stack.push(input_str);
  575. }
  576. this.handleInput(input_str);
  577. }
  578. } else if (keyCode === 38) { // up arrow
  579. if (input_str !== "" && this.cmd_stack.cur === (this.cmd_stack.getSize() - 1)) {
  580. this.cmd_stack.push(input_str);
  581. }
  582. this.input.val(this.cmd_stack.prev());
  583. } else if (keyCode === 40) { // down arrow
  584. this.input.val(this.cmd_stack.next());
  585. } else if (keyCode === 27) { // esc
  586. if (this.container.css('opacity') > 0.5) {
  587. this.container.animate({'opacity': 0}, 300);
  588. } else {
  589. this.container.animate({'opacity': 1}, 300);
  590. }
  591. }
  592. }
  593. }
  594. /**
  595. * Prevent default action of special keys
  596. */
  597. Cmd.prototype.handleKeyUp = function(e) {
  598. var key = e.which;
  599. if ($.inArray(key, this.keys_array) > -1) {
  600. e.preventDefault();
  601. return false;
  602. }
  603. return true;
  604. }
  605. /**
  606. * Prevent default action of special keys
  607. */
  608. Cmd.prototype.handleKeyDown = function(e) {
  609. var key = e.which;
  610. if ($.inArray(key, this.keys_array) > -1) {
  611. e.preventDefault();
  612. return false;
  613. }
  614. return true;
  615. }
  616. /**
  617. * Complete command names when tab is pressed
  618. */
  619. Cmd.prototype.tabComplete = function(str) {
  620. // If we have a space then offload to external processor
  621. if (str.indexOf(' ') !== -1) {
  622. if (this.options.tabcomplete_url) {
  623. if (this.autocomplete_ajax) {
  624. this.autocomplete_ajax.abort();
  625. }
  626. this.autocomplete_ajax = $.ajax({
  627. url: this.options.tabcomplete_url,
  628. context: this,
  629. dataType: 'json',
  630. data: {
  631. cmd_in: str
  632. },
  633. success: function (data) {
  634. if (data) {
  635. this.input.val(data);
  636. }
  637. }
  638. });
  639. }
  640. this.autocompletion_attempted = false;
  641. return;
  642. }
  643. var autocompletions = this.all_commands.filter(function (value) {
  644. return value.startsWith(str);
  645. });
  646. if (autocompletions.length === 0) {
  647. return false;
  648. } else if (autocompletions.length === 1) {
  649. this.input.val(autocompletions[0]);
  650. } else {
  651. if (this.autocompletion_attempted) {
  652. this.displayOutput(autocompletions.join(', '));
  653. this.autocompletion_attempted = false;
  654. this.input.val(str);
  655. return;
  656. } else {
  657. this.autocompletion_attempted = true;
  658. }
  659. }
  660. }
  661. // ====== Helpers ===============================
  662. /**
  663. * Takes a user to a given url. Adds "http://" if necessary.
  664. */
  665. Cmd.prototype.goToURL = function(url) {
  666. if (url.substr(0, 4) !== 'http' && url.substr(0, 2) !== '//') {
  667. url = 'http://' + url;
  668. }
  669. if (popup) {
  670. window.open(url, '_blank');
  671. window.focus();
  672. } else {
  673. // break out of iframe - used by chrome plugin
  674. if (top.location !== location) {
  675. top.location.href = document.location.href;
  676. }
  677. location.href = url;
  678. }
  679. }
  680. /**
  681. * Give focus to the command input and
  682. * scroll to the bottom of the page
  683. */
  684. Cmd.prototype.focusOnInput = function() {
  685. var cmd_width;
  686. $(this.options.selector).scrollTop($(this.options.selector)[0].scrollHeight);
  687. this.input.focus();
  688. }
  689. /**
  690. * Make prompt and input fit on one line
  691. */
  692. Cmd.prototype.resizeInput = function() {
  693. var cmd_width = this.wrapper.width() - this.wrapper.find('.main-prompt').first().width() - 45;
  694. this.input.focus().css('width', cmd_width);
  695. }
  696. /**
  697. * Clear the screen
  698. */
  699. Cmd.prototype.clearScreen = function() {
  700. this.container.empty();
  701. this.output = $('<div/>')
  702. .addClass('cmd-output')
  703. .appendTo(this.container);
  704. this.prompt_elem = $('<span/>')
  705. .addClass('main-prompt')
  706. .addClass('prompt')
  707. .html(this.prompt_str)
  708. .appendTo(this.container);
  709. this.input = $('<input/>')
  710. .addClass('cmd-in')
  711. .attr('type', 'text')
  712. .attr('maxlength', 512)
  713. .appendTo(this.container);
  714. this.showInputType();
  715. this.input.val('');
  716. }
  717. /**
  718. * Attach click handlers to 'autofills' - divs which, when clicked,
  719. * will insert text into the input
  720. */
  721. Cmd.prototype.activateAutofills = function() {
  722. var input = this.input;
  723. this.wrapper
  724. .find('[data-type=autofill]')
  725. .on('click', function() {
  726. input.val($(this).data('autofill'));
  727. });
  728. }
  729. /**
  730. * Temporarily disable input while runnign commands
  731. */
  732. Cmd.prototype.disableInput = function() {
  733. this.input
  734. .attr('disabled', true)
  735. .val(this.options.busy_text);
  736. }
  737. /**
  738. * Reenable input after running disableInput()
  739. */
  740. Cmd.prototype.enableInput = function() {
  741. this.input
  742. .removeAttr('disabled')
  743. .val('');
  744. }
  745. /**
  746. * Speak output aloud using speech synthesis API
  747. *
  748. * @param {String} output Text to read
  749. */
  750. Cmd.prototype.speakOutput = function(output) {
  751. var msg = new SpeechSynthesisUtterance();
  752. msg.volume = 1; // 0 - 1
  753. msg.rate = 1; // 0.1 - 10
  754. msg.pitch = 2; // 0 - 2
  755. msg.lang = 'en-UK';
  756. msg.text = output;
  757. window.speechSynthesis.speak(msg);
  758. }
  759. return Cmd;
  760. }));