/** * # 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.tags.display` and * `terminal.tags.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.tags.commandline.hidePrompt() * terminal.tags.display.add('Hello! inputs will now come to this app.') * if (input == 'exit') { * terminal.tags.display.add('Bye! Returning control to the shell.') * terminal.tags.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 /** * 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. * - Empty the command line of input. * - Update the display. */ process(prompt, input) { input = he.encode(input) this.tags.display.add(prompt + input + '\n') var response = this.active.run(input, this) if (typeof response === 'string') { this.tags.display.add(response) } if (typeof response === 'object' && typeof response.run === 'function') { this.active = response response.run(input, this) } this.tags.commandline.command.value = '' this.update() } /** * Make the shell the active process. * * For app objects to return control to the shell when finished. */ returnToShell() { this.active = this.shell }
this.prompt = opts.prompt || '$ ' this.visible = true this.on('mount', function() { document.getElementsByName('command')[0].focus() }) process() { var prompt = this.visible ? this.prompt : ''; this.parent.process(prompt, this.command.value) } /** * Set the prompt value. * * Note about `lhs.write()` check: * Terminal tag setup fires `setPrompt()` before `lhs.write()` exists. * At that point, `lhs` is initialised with just its `content` attribute. * After that, `lhs.write()` is called to make all subsequent prompt changes. */ setPrompt(value) { if (typeof this.tags.lhs.write !== 'undefined') { this.tags.lhs.write(value) } this.prompt = value } hidePrompt() { this.visible = false } showPrompt() { this.visible = true }
add(text) { if (text) { text = this.preserveWhiteSpace(text) this.output.push({ 'content': text }) } } set(text) { if (text) { this.clear() this.add(text) } } /** * Empty the display. * * An extra update is needed for subsequent changes to `output` (e.g. `add()`, * `restore()`) to display properly. */ clear() { this.output.length = 0 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) } } /** * Make sure whitespace displays correctly. * * Keep the following: * * - Newlines. * - Whitespace for display. This means all spaces that are not within tags. */ preserveWhiteSpace(text) { text = text.replace(/(?:\r\n|\r|\n)/g, '
') // Search for tags or whitespace. Replace whitespace, leave the tags. text = text.replace(/<[^<]+>|( )/g, function(match, group1) { if (group1 == " ") { return ' ' } return match }) return text } this.output = [] this.add(opts.welcome)
// Initialise contents from tag options, but expose `write()` for updating. write(text) { this.root.innerHTML = text } this.write(opts.content)