| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- /**
- * # 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
- *
- * <terminal shell='jsclass' welcome='text' prompt='text'></terminal>
- *
- * <script src='riot+compiler.min.js'></script>
- * <script src='he.js'></script>
- * <script src='shell.js'></script>
- * <script src='terminal.tag' type='riot/tag'></script>
- * <script>riot.mount('terminal')</script>
- *
- * ## 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
- * <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
- *
- * 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`.
- */
- <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
- /**
- * 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
- }
- </terminal>
- <display>
- <div each={ output }>
- <raw content={ content } />
- </div>
- 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, '<br />')
- // 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)
- </display>
- <commandline>
- <form autocomplete='off' onsubmit={ process }>
- <raw name='lhs' content={ prompt } show={ visible } />
- <input type='text' name='command' />
- </form>
- <style>
- input[name='command'] {
- background: transparent;
- border: none; outline: none;
- padding: 0; margin: 0;
- width: 90%;
- }
- </style>
- 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
- }
- </commandline>
- <raw>
- <span></span>
- // Initialise HTML from tag options and expose `write()` for updating.
- write(text) {
- this.root.innerHTML = text
- }
- this.write(opts.content)
- </raw>
|