|
|
@@ -2,23 +2,24 @@
|
|
|
* # 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
|
|
|
+ * "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.
|
|
|
+ * The default prompt is `'$ '`.
|
|
|
+ * The default welcome message is empty.
|
|
|
+ * The default shell performs no actions: pressing enter will simply move the
|
|
|
+ * cursor to the next prompt line.
|
|
|
*
|
|
|
* ## Usage
|
|
|
*
|
|
|
- * <terminal shell='jsclass' welcome='text' prompt='text'></terminal>
|
|
|
+ * <terminal shell='myshellclass' welcome='text' prompt='text'></terminal>
|
|
|
*
|
|
|
* <script src='riot+compiler.min.js'></script>
|
|
|
* <script src='he.js'></script>
|
|
|
- * <script src='shell.js'></script>
|
|
|
+ * <script src='myshell.js'></script>
|
|
|
* <script src='terminal.tag' type='riot/tag'></script>
|
|
|
* <script>riot.mount('terminal')</script>
|
|
|
*
|
|
|
@@ -29,101 +30,45 @@
|
|
|
*
|
|
|
* ## 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.
|
|
|
+ * A shell class must be defined before the tag is mounted. Shells can keep
|
|
|
+ * prompt and welcome message settings, and should listen to the `'cmd_entered'`
|
|
|
+ * event to process input from the `commandline` tag.
|
|
|
+ *
|
|
|
* Here is the structure of a minimal shell that does nothing:
|
|
|
*
|
|
|
* // Contents of myshell.js
|
|
|
- * function() myshell() {
|
|
|
+ * function myshellclass(events) {
|
|
|
* this.prompt = ''
|
|
|
* this.welcome = ''
|
|
|
- * this.run = function(input, terminal) {
|
|
|
- * return ''
|
|
|
- * }
|
|
|
+ * events.on('cmd_entered', function(input) {
|
|
|
+ * // Do nothing
|
|
|
+ * })
|
|
|
* }
|
|
|
*
|
|
|
- * // Shell class used by Terminal Riot Tag in index.html
|
|
|
- * <terminal shell='myshell'></terminal>
|
|
|
- *
|
|
|
- * 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
|
|
|
+ * The shell can trigger events available on the `display` and `commandline`
|
|
|
+ * tags to make things happen:
|
|
|
*
|
|
|
- * 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.
|
|
|
+ * 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('disp_hide') // Save the display, then clear it
|
|
|
+ * events.trigger('disp_restore') // Restore the saved display
|
|
|
+ * events.trigger('prompt_set', text) // Change the command prompt to `text`
|
|
|
+ * events.trigger('prompt_hide') // Hide the command prompt
|
|
|
+ * events.trigger('prompt_show') // Show the command prompt, if hidden
|
|
|
*
|
|
|
- * 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`.
|
|
|
*/
|
|
|
<terminal>
|
|
|
- <display welcome={ welcome } />
|
|
|
- <commandline prompt={ prompt } />
|
|
|
-
|
|
|
- // 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
|
|
|
+ <display welcome={ welcome } events={ this } />
|
|
|
+ <commandline prompt={ prompt } events={ this } />
|
|
|
|
|
|
/**
|
|
|
- * 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.
|
|
|
+ * 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 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
|
|
|
- }
|
|
|
+ var shell = window[opts.shell] ? new window[opts.shell](this) : {}
|
|
|
+ this.welcome = shell.welcome || opts.welcome
|
|
|
+ this.prompt = shell.prompt || opts.prompt
|
|
|
</terminal>
|
|
|
|
|
|
<display>
|
|
|
@@ -131,38 +76,55 @@
|
|
|
<raw content={ content } />
|
|
|
</div>
|
|
|
|
|
|
- add(text) {
|
|
|
+ var ev = opts.events
|
|
|
+ var self = this
|
|
|
+ this.output = []
|
|
|
+
|
|
|
+ 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) {
|
|
|
- text = this.preserveWhiteSpace(text)
|
|
|
- this.output.push({ 'content': text })
|
|
|
+ self.clear()
|
|
|
+ self.add(text)
|
|
|
}
|
|
|
- }
|
|
|
+ })
|
|
|
+
|
|
|
+ ev.on('disp_clear', function() {
|
|
|
+ self.clear()
|
|
|
+ })
|
|
|
|
|
|
- set(text) {
|
|
|
+ ev.on('disp_hide', function() {
|
|
|
+ self.saved = self.output.splice(0, self.output.length)
|
|
|
+ self.clear()
|
|
|
+ })
|
|
|
+
|
|
|
+ ev.on('disp_restore', function() {
|
|
|
+ if (self.saved.length > 0) {
|
|
|
+ self.clear()
|
|
|
+ self.output = self.saved.splice(0, self.saved.length)
|
|
|
+ self.update()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ add(text) {
|
|
|
if (text) {
|
|
|
- this.clear()
|
|
|
- this.add(text)
|
|
|
+ text = this.preserveWhiteSpace(text)
|
|
|
+ this.output.push({ 'content': text })
|
|
|
+ this.update()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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, '<br />')
|
|
|
// Search for tags or whitespace. Escape whitespace, leave tags.
|
|
|
@@ -172,15 +134,11 @@
|
|
|
})
|
|
|
return text
|
|
|
}
|
|
|
-
|
|
|
- this.output = []
|
|
|
- this.add(opts.welcome)
|
|
|
</display>
|
|
|
|
|
|
<commandline>
|
|
|
<form autocomplete='off' onsubmit={ process }>
|
|
|
- <raw name='lhs' content={ prompt } show={ visible } />
|
|
|
- <input type='text' name='command' />
|
|
|
+ <raw name='lhs' content={ prompt } show={ visible } /><input type='text' name='command' />
|
|
|
</form>
|
|
|
|
|
|
<style>
|
|
|
@@ -192,6 +150,8 @@
|
|
|
}
|
|
|
</style>
|
|
|
|
|
|
+ var ev = opts.events
|
|
|
+ var self = this
|
|
|
this.prompt = opts.prompt || '$ '
|
|
|
this.visible = true
|
|
|
|
|
|
@@ -199,24 +159,25 @@
|
|
|
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
|
|
|
+ ev.on('prompt_set', function(value) {
|
|
|
+ self.prompt = value
|
|
|
// `write()` is called to actually update the `raw` tag's html.
|
|
|
- this.tags.lhs.write(value)
|
|
|
- }
|
|
|
+ self.tags.lhs.write(value)
|
|
|
+ })
|
|
|
|
|
|
- hidePrompt() {
|
|
|
- this.visible = false
|
|
|
- }
|
|
|
+ ev.on('prompt_hide', function() {
|
|
|
+ self.update({ visible: false })
|
|
|
+ })
|
|
|
+
|
|
|
+ ev.on('prompt_show', function() {
|
|
|
+ self.update({ visible: true })
|
|
|
+ })
|
|
|
|
|
|
- showPrompt() {
|
|
|
- this.visible = true
|
|
|
+ process() {
|
|
|
+ var prompt = this.visible ? this.prompt : '';
|
|
|
+ ev.trigger('disp_add', prompt + this.command.value + '\n')
|
|
|
+ ev.trigger('cmd_entered', this.command.value)
|
|
|
+ this.command.value = ''
|
|
|
}
|
|
|
</commandline>
|
|
|
|