/** * # Terminal Riot Tag * * Provides a pretend commandline interface, capable of displaying output from a * "shell" javascript class. Multiple terminal tags can be used on a page, using * the same shell class or different ones - each terminal will work * independently. * * ## Defaults * * The default prompt is `'$'`. There is no default the welcome message. The * default shell performs no actions: pressing enter will simply move the cursor * to the next prompt line. * * ## Usage * * * * * * * * * * ## Dependencies * * - riot.js (http://riotjs.com/) * - he.js (https://github.com/mathiasbynens/he) for HTML Entity conversion. * * ## Making A Shell Class * * The shell class must be defined before the tag is mounted. Shells can keep * prompt and welcome message settings, and must define a `run()` function. * Here is the structure of a minimal shell that does nothing: * * // Contents of myshell.js * function() myshell() { * this.prompt = '' * this.welcome = '' * this.run = function(input, terminal) { * return '' * } * } * * // Shell class used by Terminal Riot Tag in index.html * * * The `run()` function can take 2 parameters. The first will contain user input * from the command line. The second will contain the terminal object itself - * this will allow the shell access to all of the terminal's functions, mainly * through its 2 child tags, `terminal.display` and * `terminal.commandline`. Refer those tags below for capabilities. * * A shell can, at its most basic operation, return a string of text or HTML, * and this will be appended to the display. * * ## Apps * * A shell can also return an object that will temporarily process all input, * until the object returns control back to the shell. This can be thought of as * the shell returning an "app" object. * * An "app" object should implement a `run()` function, just like the shell * class. It should also have exit conditions under which it will return control * to the shell by calling `terminal.returnToShell()`. * * { * 'run': function(input, terminal) { * terminal.commandline.hidePrompt() * terminal.display.add('Hello! inputs will now come to this app.') * if (input == 'exit') { * terminal.display.add('Bye! Returning control to the shell.') * terminal.commandline.showPrompt() * terminal.returnToShell() * } * } * } * * An "app" is an object, so can keep state information for a current run with * `this`. For more persistent saving of information, it is also possible to * save data to `terminal` or to `terminal.shell`. */ // Create a new shell with the class name given to the terminal tag. var shell = { 'run': function() { return ''; } } this.shell = window[opts.shell] ? new window[opts.shell] : shell this.active = this.shell this.welcome = this.shell.welcome || opts.welcome this.prompt = this.shell.prompt || opts.prompt this.display = this.tags.display this.commandline = this.tags.commandline /** * How to process a command: * * - Make the input safe by transforming html entities. * - Keep the last command line on display before other output. * - Based on shell response, append output, or give control to an app object. * - Update the display. */ var self = this this.commandline.on('cmdEntered', function(prompt, input) { input = he.encode(input) self.display.add(prompt + input + '\n') var response = self.active.run(input, self) if (typeof response === 'string') { self.display.add(response) } if (typeof response === 'object' && typeof response.run === 'function') { self.active = response response.run(input, self) } self.update() }) /** * Make the shell the active process. * * For app objects to return control to the shell when finished. */ returnToShell() { this.active = this.shell }
add(text) { if (text) { text = this.preserveWhiteSpace(text) this.output.push({ 'content': text }) } } set(text) { if (text) { this.clear() this.add(text) } } clear() { this.output.length = 0 // This is needed for subsequent changes to `output` to display properly. this.update() } hide() { this.saved = this.output.splice(0, this.output.length) this.clear() } restore() { if (this.saved.length > 0) { this.clear() this.output = this.saved.splice(0, this.saved.length) } } preserveWhiteSpace(text) { text = text.replace(/(?:\r\n|\r|\n)/g, '
') // Search for tags or whitespace. Escape whitespace, leave tags. text = text.replace(/<[^<]+>|( )/g, function(match, group1) { if (group1 == " ") { return ' ' } return match }) return text } this.output = [] this.add(opts.welcome)
this.prompt = opts.prompt || '$ ' this.visible = true this.on('mount', function() { document.getElementsByName('command')[0].focus() }) process() { var prompt = this.visible ? this.prompt : ''; this.trigger('cmdEntered', prompt, this.command.value) this.command.value = '' } setPrompt(value) { this.prompt = value // `write()` is called to actually update the `raw` tag's html. this.tags.lhs.write(value) } hidePrompt() { this.visible = false } showPrompt() { this.visible = true }
// Initialise HTML from tag options and expose `write()` for updating. write(text) { this.root.innerHTML = text } this.write(opts.content)