terminal.tag 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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 `'$ '`.
  12. * The default welcome message is empty.
  13. * The default shell performs no actions: pressing enter will simply move the
  14. * cursor to the next prompt line.
  15. *
  16. * ## Usage
  17. *
  18. * <terminal shell='myshellclass' welcome='text' prompt='text'></terminal>
  19. *
  20. * <script src='riot+compiler.min.js'></script>
  21. * <script src='he.js'></script>
  22. * <script src='myshell.js'></script>
  23. * <script src='terminal.tag' type='riot/tag'></script>
  24. * <script>riot.mount('terminal')</script>
  25. *
  26. * ## Dependencies
  27. *
  28. * - riot.js (http://riotjs.com/)
  29. * - he.js (https://github.com/mathiasbynens/he) for HTML Entity conversion.
  30. *
  31. * ## Making A Shell Class
  32. *
  33. * A shell class must be defined before the tag is mounted. Shells can keep
  34. * prompt and welcome message settings, and should listen to the `'cmd_entered'`
  35. * event to process input from the `commandline` tag.
  36. *
  37. * Here is the structure of a minimal shell that does nothing:
  38. *
  39. * // Contents of myshell.js
  40. * function myshellclass(events) {
  41. * this.prompt = ''
  42. * this.welcome = ''
  43. * events.on('cmd_entered', function(input) {
  44. * // Do nothing
  45. * })
  46. * }
  47. *
  48. * The shell can trigger events available on the `display` and `commandline`
  49. * tags to make things happen:
  50. *
  51. * events.trigger('disp_add', text) // Append `text` to the display
  52. * events.trigger('disp_set', text) // Display only `text`
  53. * events.trigger('disp_clear') // Clear the display
  54. * events.trigger('disp_hide') // Save the display, then clear it
  55. * events.trigger('disp_restore') // Restore the saved display
  56. * events.trigger('prompt_set', text) // Change the command prompt to `text`
  57. * events.trigger('prompt_hide') // Hide the command prompt
  58. * events.trigger('prompt_show') // Show the command prompt, if hidden
  59. *
  60. */
  61. <terminal>
  62. <display welcome={ welcome } events={ this } />
  63. <commandline prompt={ prompt } events={ this } />
  64. /**
  65. * Create a new shell with the class name given to the terminal tag.
  66. * The terminal tag object passes events between the shell and the other tags.
  67. */
  68. var shell = window[opts.shell] ? new window[opts.shell](this) : {}
  69. this.welcome = shell.welcome || opts.welcome
  70. this.prompt = shell.prompt || opts.prompt
  71. </terminal>
  72. <display>
  73. <div each={ output }>
  74. <raw content={ content } />
  75. </div>
  76. var ev = opts.events
  77. var self = this
  78. this.output = []
  79. this.on('mount', function() {
  80. this.add(opts.welcome)
  81. })
  82. ev.on('disp_add', function(text) {
  83. self.add(text)
  84. })
  85. ev.on('disp_set', function(text) {
  86. if (text) {
  87. self.clear()
  88. self.add(text)
  89. }
  90. })
  91. ev.on('disp_clear', function() {
  92. self.clear()
  93. })
  94. ev.on('disp_hide', function() {
  95. self.saved = self.output.splice(0, self.output.length)
  96. self.clear()
  97. })
  98. ev.on('disp_restore', function() {
  99. if (self.saved.length > 0) {
  100. self.clear()
  101. self.output = self.saved.splice(0, self.saved.length)
  102. self.update()
  103. }
  104. })
  105. add(text) {
  106. if (text) {
  107. text = this.preserveWhiteSpace(text)
  108. this.output.push({ 'content': text })
  109. this.update()
  110. }
  111. }
  112. clear() {
  113. this.output.length = 0
  114. this.update()
  115. }
  116. preserveWhiteSpace(text) {
  117. text = text.replace(/\r\n|\r|\n/g, '<br />')
  118. // Search for tags or whitespace. Escape whitespace, leave tags.
  119. text = text.replace(/<[^<]+>|( )/g, function(match, group1) {
  120. if (group1 == " ") { return '&nbsp;' }
  121. return match
  122. })
  123. return text
  124. }
  125. </display>
  126. <commandline>
  127. <form autocomplete='off' onsubmit={ process }>
  128. <raw name='lhs' content={ prompt } show={ visible } /><input type='text' name='command' />
  129. </form>
  130. <style>
  131. input[name='command'] {
  132. background: transparent;
  133. border: none; outline: none;
  134. padding: 0; margin: 0;
  135. width: 90%;
  136. }
  137. </style>
  138. var ev = opts.events
  139. var self = this
  140. this.prompt = opts.prompt || '$ '
  141. this.visible = true
  142. this.on('mount', function() {
  143. document.getElementsByName('command')[0].focus()
  144. })
  145. ev.on('prompt_set', function(value) {
  146. self.prompt = value
  147. // `write()` is called to actually update the `raw` tag's html.
  148. self.tags.lhs.write(value)
  149. })
  150. ev.on('prompt_hide', function() {
  151. self.update({ visible: false })
  152. })
  153. ev.on('prompt_show', function() {
  154. self.update({ visible: true })
  155. })
  156. process() {
  157. var prompt = this.visible ? this.prompt : ''
  158. var command = he.encode(this.command.value)
  159. this.command.value = ''
  160. ev.trigger('disp_add', prompt + command + '\n')
  161. ev.trigger('cmd_entered', command)
  162. }
  163. </commandline>
  164. <raw>
  165. <span></span>
  166. // Initialise HTML from tag options and expose `write()` for updating.
  167. write(text) {
  168. this.root.innerHTML = text
  169. }
  170. this.write(opts.content)
  171. </raw>