/** * # Terminal Riot Tag * * A pretend commandline interface capable of displaying output from a "shell" * Javascript class. Multiple tags can be used on a page, each will work * completely independently. * * ## Dependencies * * - riot.js (http://riotjs.com/) * * ## Usage * * * * * * * * * ## Configuring * * The terminal tag accepts 3 optional parameters: * * - `shell` - Text specifying the shell class to interact with. * - `welcome` - Text/HTML displayed when a terminal is first mounted. * - `prompt` - Text/HTML before the commandline input. Defaults to `'$ '`. * * With no shell logic, the terminal tag will simply print commands entered. * * Note: `welcome` and `prompt` can also be specified in the shell. Shell values * take priority over parameter values. * * ## Making A Shell Class * * A shell class should be defined before the tag is mounted by riot. It should * expect a single parameter (the terminal tag itself) which will be an * observable object. * * This observable will emit `'cmd_entered'` events containing input for * processing. Here is a minimal shell: * * // Contents of myshell.js * function myshellclass(events) { * this.prompt = '' * this.welcome = '' * events.on('cmd_entered', function(input) { * // Do processing * }) * } * * ### Events * * The observable provides events to make things happen: * * events.trigger('disp_add', text) // Append `text` to the display * events.trigger('disp_set', text) // Display only `text` * events.trigger('disp_clear') // Clear the display * events.trigger('prompt_set', text) // Change the prompt to `text` * events.trigger('prompt_hide') // Hide the command prompt * events.trigger('prompt_show') // Show the command prompt * * There is also an event to swap between sets of displays and prompts: * * events.trigger('context_swap', name) * * The starting context name is `'default'`. Calling `'context_swap'` with: * * - a new name initialises a new, empty set. * - an existing name loads that set. * - no name returns to the `'default'` set. */ /** * Create a new shell with the class name given to the terminal tag. * The terminal tag object passes events between the shell and the other tags. */ var shell = window[opts.shell] ? new window[opts.shell](this) : {} this.welcome = shell.welcome || opts.welcome this.prompt = shell.prompt || opts.prompt
var ev = opts.events var self = this this.contexts = {} this.output = this.contexts['default'] = [] this.on('mount', function() { this.add(opts.welcome) }) ev.on('disp_add', function(text) { self.add(text) }) ev.on('disp_set', function(text) { if (text) { self.clear() self.add(text) } }) ev.on('disp_clear', function() { self.clear() }) ev.on('context_swap', function(name) { name = name || 'default' if (!(name in self.contexts)) { self.contexts[name] = [] } self.update({ output: [] }) // Cleanly disassociate current output first. self.update({ output: self.contexts[name] }) }) add(text) { if (text) { text = text.replace(/\r\n|\r|\n/g, '
') this.output.push({ 'content': text }) this.update() } } clear() { this.output.length = 0 this.update() }
var ev = opts.events var self = this this.contexts = {} this.prompt = this.contexts['default'] = { text: opts.prompt || '$ ', visible: true } this.on('mount', function() { this.command.focus() }) ev.on('prompt_set', function(value) { self.prompt.text = value self.tags.lhs.write(value) }) ev.on('prompt_hide', function() { self.prompt.visible = false }) ev.on('prompt_show', function() { self.prompt.visible = true }) ev.on('context_swap', function(name) { name = name || 'default' if (!(name in self.contexts)) { self.contexts[name] = { text: '', visible: true } } self.prompt = self.contexts[name] self.tags.lhs.write(self.prompt.text) }) process() { var prompt = this.prompt.visible ? this.prompt.text : '' var command = this.encode(this.command.value) ev.trigger('disp_add', prompt + command + '\n') ev.trigger('cmd_entered', command) this.command.value = '' this.command.blur() this.command.focus() // Refocus to scroll display and keep input in view. } encode(text) { return text.replace(/&/g,'&').replace(//g,'>') }
// Set initial html using `content` option, and... this.on('mount', function() { this.write(opts.content) }) // Call `write()` manually to update the html. write(text) { this.root.innerHTML = text }