| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- /**
- * # 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 `process()` function.
- * Here is the structure of a minimal shell that does nothing:
- *
- * // Contents of myshell.js
- * function() myshell() {
- * this.prompt = ''
- * this.welcome = ''
- * this.process = function(input) {
- * return ''
- * }
- * }
- *
- * // shell class used by Terminal Riot Tag in index.html
- * <terminal shell='myshell'></terminal>
- *
- * ## Special Shell Return Values
- *
- * The terminal tag recognises some predefined shell return values and upon
- * receiving them, can perform actions other than printing output:
- *
- * - `'clear'`. Terminal will clear its output history.
- *
- */
- <terminal>
- <history welcome={ welcome } />
- <commandline prompt={ prompt } />
- // Take defaults from shell, otherwise take from terminal tag options.
- var shell = { 'process': function() { return ''; } }
- this.shell = window[opts.shell] ? new window[opts.shell] : shell
- this.welcome = this.shell.welcome ? this.shell.welcome : opts.welcome
- this.prompt = this.shell.prompt ? this.shell.prompt : opts.prompt
- /**
- * How to process a command:
- * - Make the input safe by transforming html entities.
- * - Keep the last command in history as we go to the next line.
- * - Based on shell response, perform special actions or append the output.
- * - Update the prompt as required.
- */
- process(prompt, input) {
- input = he.encode(input)
- var output = prompt + input + '\n'
- var response = this.shell.process(input)
- switch(response) {
- case 'clear':
- this.clear()
- break
- default:
- this.tags.history.add(output + response)
- }
- this.tags.commandline.setPrompt(this.shell.prompt)
- this.tags.commandline.command.value = ''
- }
- clear() {
- this.tags.history.hist = []
- this.update()
- }
- </terminal>
- <commandline>
- <form autocomplete='off' onsubmit={ process }>
- <raw name='lhs' content={ prompt } /><input type='text' name='command' />
- </form>
- <style>
- input[name='command'] {
- background: transparent;
- border: none; outline: none;
- padding: 0; margin: 0;
- width: 90%;
- }
- </style>
- this.on('mount', function() {
- document.getElementsByName('command')[0].focus()
- })
- /**
- * Set the prompt to any truthy 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 (value) {
- if (typeof this.tags.lhs.write != 'undefined') {
- this.tags.lhs.write(value)
- }
- this.update({ 'prompt': value })
- }
- }
- process(e) {
- this.parent.process(this.prompt, this.command.value)
- }
- this.prompt = '$ '
- this.setPrompt(opts.prompt)
- </commandline>
- <history>
- <div each={ hist }>
- <raw content={ content } />
- </div>
- add(output) {
- if (output) {
- output = this.preserveWhiteSpace(output)
- this.hist.push({ 'content': output })
- this.update()
- }
- }
- /**
- * 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, '<br />')
- // 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.hist = []
- this.add(opts.welcome)
- </history>
- <raw>
- <span></span>
- // Initialise contents from tag options, but expose `write()` for updating.
- write(value) {
- this.root.innerHTML = value
- }
- this.write(opts.content)
- </raw>
|