/**
* # 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
}