terminal.tag 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /**
  2. * # Terminal Riot Tag
  3. *
  4. * Provides a pretend commandline interface, capable of displaying output from a
  5. * "shell" javascript class. Multiple terminal tags can be used on a page, using
  6. * the same shell class or different ones - each terminal will work
  7. * independently.
  8. *
  9. * ## Defaults
  10. *
  11. * The default prompt is `'$'`. There is no default the welcome message. The
  12. * default shell performs no actions: pressing enter will simply move the cursor
  13. * to the next prompt line.
  14. *
  15. * ## Usage
  16. *
  17. * <terminal shell='jsclass' welcome='text' prompt='text'></terminal>
  18. *
  19. * <script src='riot+compiler.min.js'></script>
  20. * <script src='he.js'></script>
  21. * <script src='shell.js'></script>
  22. * <script src='terminal.tag' type='riot/tag'></script>
  23. * <script>riot.mount('terminal')</script>
  24. *
  25. * ## Dependencies
  26. *
  27. * - riot.js (http://riotjs.com/)
  28. * - he.js (https://github.com/mathiasbynens/he) for HTML Entity conversion.
  29. *
  30. * ## Making A Shell Class
  31. *
  32. * The shell class must be defined before the tag is mounted. Shells can keep
  33. * prompt and welcome message settings, and must define a `process()` function.
  34. * Here is the structure of a minimal shell that does nothing:
  35. *
  36. * // Contents of myshell.js
  37. * function() myshell() {
  38. * this.prompt = ''
  39. * this.welcome = ''
  40. * this.process = function(input) {
  41. * return ''
  42. * }
  43. * }
  44. *
  45. * // shell class used by Terminal Riot Tag in index.html
  46. * <terminal shell='myshell'></terminal>
  47. *
  48. * ## Special Shell Return Values
  49. *
  50. * The terminal tag recognises some predefined shell return values and upon
  51. * receiving them, can perform actions other than printing output:
  52. *
  53. * - `'clear'`. Terminal will clear its output history.
  54. *
  55. */
  56. <terminal>
  57. <history welcome={ welcome } />
  58. <commandline prompt={ prompt } />
  59. // Take defaults from shell, otherwise take from terminal tag options.
  60. var shell = { 'process': function() { return ''; } }
  61. this.shell = window[opts.shell] ? new window[opts.shell] : shell
  62. this.welcome = this.shell.welcome ? this.shell.welcome : opts.welcome
  63. this.prompt = this.shell.prompt ? this.shell.prompt : opts.prompt
  64. /**
  65. * How to process a command:
  66. * - Make the input safe by transforming html entities.
  67. * - Keep the last command in history as we go to the next line.
  68. * - Based on shell response, perform special actions or append the output.
  69. * - Set the prompt as required.
  70. * - Make sure all tags update their display content.
  71. */
  72. process(prompt, input) {
  73. input = he.encode(input)
  74. var output = prompt + input + '\n'
  75. var response = this.shell.process(input)
  76. switch(response) {
  77. case 'clear':
  78. this.clear()
  79. break
  80. default:
  81. this.tags.history.add(output + response)
  82. }
  83. this.tags.commandline.setPrompt(this.shell.prompt)
  84. this.tags.commandline.command.value = ''
  85. this.update()
  86. }
  87. clear() {
  88. this.tags.history.hist = []
  89. }
  90. </terminal>
  91. <commandline>
  92. <form autocomplete='off' onsubmit={ process }>
  93. <raw name='lhs' content={ prompt } /><input type='text' name='command' />
  94. </form>
  95. <style>
  96. input[name='command'] {
  97. background: transparent;
  98. border: none; outline: none;
  99. padding: 0; margin: 0;
  100. width: 90%;
  101. }
  102. </style>
  103. this.on('mount', function() {
  104. document.getElementsByName('command')[0].focus()
  105. })
  106. /**
  107. * Set the prompt to any truthy value.
  108. *
  109. * Note about `lhs.write()` check:
  110. * Terminal tag setup fires `setPrompt()` before `lhs.write()` exists.
  111. * At that point, `lhs` is initialised with just its `content` attribute.
  112. * After that, `lhs.write()` is called to make all subsequent prompt changes.
  113. */
  114. setPrompt(value) {
  115. if (value) {
  116. if (typeof this.tags.lhs.write != 'undefined') {
  117. this.tags.lhs.write(value)
  118. }
  119. this.prompt = value
  120. }
  121. }
  122. process() {
  123. this.parent.process(this.prompt, this.command.value)
  124. }
  125. this.prompt = '$ '
  126. this.setPrompt(opts.prompt)
  127. </commandline>
  128. <history>
  129. <div each={ hist }>
  130. <raw content={ content } />
  131. </div>
  132. add(output) {
  133. if (output) {
  134. output = this.preserveWhiteSpace(output)
  135. this.hist.push({ 'content': output })
  136. }
  137. }
  138. /**
  139. * Make sure whitespace displays correctly.
  140. *
  141. * Keep the following:
  142. *
  143. * - Newlines.
  144. * - Whitespace for display. This means all spaces that are not within tags.
  145. */
  146. preserveWhiteSpace(text) {
  147. text = text.replace(/(?:\r\n|\r|\n)/g, '<br />')
  148. // Search for tags or whitespace. Replace whitespace, leave the tags.
  149. text = text.replace(/<[^<]+>|( )/g, function(match, group1) {
  150. if (group1 == " ") { return '&nbsp;' }
  151. return match
  152. })
  153. return text
  154. }
  155. this.hist = []
  156. this.add(opts.welcome)
  157. </history>
  158. <raw>
  159. <span></span>
  160. // Initialise contents from tag options, but expose `write()` for updating.
  161. write(value) {
  162. this.root.innerHTML = value
  163. }
  164. this.write(opts.content)
  165. </raw>