terminal.tag 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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='myshell.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. *
  29. * ## Making A Shell Class
  30. *
  31. * A shell class must be defined before the tag is mounted. Shells can keep
  32. * prompt and welcome message settings, and should listen to the `'cmd_entered'`
  33. * event to process input from the `commandline` tag.
  34. *
  35. * Here is the structure of a minimal shell that does nothing:
  36. *
  37. * // Contents of myshell.js
  38. * function myshellclass(events) {
  39. * this.prompt = ''
  40. * this.welcome = ''
  41. * events.on('cmd_entered', function(input) {
  42. * // Do nothing
  43. * })
  44. * }
  45. *
  46. * The shell can trigger events available on the `display` and `commandline`
  47. * tags to make things happen:
  48. *
  49. * events.trigger('disp_add', text) // Append `text` to the display
  50. * events.trigger('disp_set', text) // Display only `text`
  51. * events.trigger('disp_clear') // Clear the display
  52. * events.trigger('disp_hide') // Save the display, then clear it
  53. * events.trigger('disp_restore') // Restore the saved display
  54. * events.trigger('prompt_set', text) // Change the command prompt to `text`
  55. * events.trigger('prompt_hide') // Hide the command prompt
  56. * events.trigger('prompt_show') // Show the command prompt, if hidden
  57. *
  58. */
  59. <terminal>
  60. <display welcome={ welcome } events={ this } />
  61. <commandline prompt={ prompt } events={ this } />
  62. <style>
  63. terminal * { padding: 0; margin: 0; line-height: normal; font-size: 100%; }
  64. </style>
  65. /**
  66. * Create a new shell with the class name given to the terminal tag.
  67. * The terminal tag object passes events between the shell and the other tags.
  68. */
  69. var shell = window[opts.shell] ? new window[opts.shell](this) : {}
  70. this.welcome = shell.welcome || opts.welcome
  71. this.prompt = shell.prompt || opts.prompt
  72. </terminal>
  73. <display>
  74. <div each={ output }>
  75. <raw content={ content } />
  76. </div>
  77. var ev = opts.events
  78. var self = this
  79. this.output = []
  80. this.on('mount', function() {
  81. this.add(opts.welcome)
  82. })
  83. ev.on('disp_add', function(text) {
  84. self.add(text)
  85. })
  86. ev.on('disp_set', function(text) {
  87. if (text) {
  88. self.clear()
  89. self.add(text)
  90. }
  91. })
  92. ev.on('disp_clear', function() {
  93. self.clear()
  94. })
  95. ev.on('disp_hide', function() {
  96. self.saved = self.output.splice(0, self.output.length)
  97. self.clear()
  98. })
  99. ev.on('disp_restore', function() {
  100. if (self.saved.length > 0) {
  101. self.clear()
  102. self.output = self.saved.splice(0, self.saved.length)
  103. self.update()
  104. }
  105. })
  106. add(text) {
  107. if (text) {
  108. text = text.replace(/\r\n|\r|\n/g, '<br />')
  109. this.output.push({ 'content': text })
  110. this.update()
  111. }
  112. }
  113. clear() {
  114. this.output.length = 0
  115. this.update()
  116. }
  117. </display>
  118. <commandline>
  119. <form autocomplete='off' onsubmit={ process }>
  120. <raw name='lhs' content={ prompt } show={ visible } /><input type='text' name='command' />
  121. </form>
  122. <style>
  123. commandline input[name='command'],
  124. commandline input[name='command']:hover,
  125. commandline input[name='command']:focus {
  126. padding: 0; margin: 0; line-height: normal; font-size: 100%;
  127. background: transparent;
  128. border: none; outline: none;
  129. height: auto; width: 70%;
  130. }
  131. </style>
  132. var ev = opts.events
  133. var self = this
  134. this.prompt = opts.prompt || '$ '
  135. this.visible = true
  136. this.on('mount', function() {
  137. document.getElementsByName('command')[0].focus()
  138. })
  139. ev.on('prompt_set', function(value) {
  140. self.prompt = value
  141. self.tags.lhs.write(value)
  142. })
  143. ev.on('prompt_hide', function() {
  144. self.update({ visible: false })
  145. })
  146. ev.on('prompt_show', function() {
  147. self.update({ visible: true })
  148. })
  149. process() {
  150. var prompt = this.visible ? this.prompt : ''
  151. var command = this.encode(this.command.value)
  152. this.command.value = ''
  153. ev.trigger('disp_add', prompt + command + '\n')
  154. ev.trigger('cmd_entered', command)
  155. // Refocus on the command input to scroll the display and keep it in view.
  156. this.command.blur()
  157. this.command.focus()
  158. }
  159. encode(text) {
  160. return text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
  161. }
  162. </commandline>
  163. <raw>
  164. <span></span>
  165. <style>
  166. raw { white-space: pre-wrap }
  167. </style>
  168. // Set the initial html using the `content` option.
  169. this.on('mount', function() {
  170. this.write(opts.content)
  171. })
  172. // Call `write()` manually to update the html.
  173. write(text) {
  174. this.root.innerHTML = text
  175. }
  176. </raw>