terminal.tag 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /**
  2. * # Terminal Riot Tag
  3. *
  4. * A pretend commandline interface capable of displaying output from a "shell"
  5. * Javascript class. Multiple tags can be used on a page, each will work
  6. * completely independently.
  7. *
  8. * ## Dependencies
  9. *
  10. * - riot.js (http://riotjs.com/)
  11. *
  12. * ## Usage
  13. *
  14. * <terminal shell='myshellclass' welcome='text' prompt='text'></terminal>
  15. *
  16. * <script src='riot+compiler.min.js'></script>
  17. * <script src='myshell.js'></script>
  18. * <script src='terminal.tag' type='riot/tag'></script>
  19. * <script>riot.mount('terminal')</script>
  20. *
  21. * ## Configuring
  22. *
  23. * The terminal tag accepts 3 optional parameters:
  24. *
  25. * - `shell` - Text specifying the shell class to interact with.
  26. * - `welcome` - Text/HTML displayed when a terminal is first mounted.
  27. * - `prompt` - Text/HTML before the commandline input. Defaults to `'$ '`.
  28. *
  29. * With no shell logic, the terminal tag will simply print commands entered.
  30. *
  31. * Note: `welcome` and `prompt` can also be specified in the shell. Shell values
  32. * take priority over parameter values.
  33. *
  34. * ## Making A Shell Class
  35. *
  36. * A shell class should be defined before the tag is mounted by riot. It should
  37. * expect a single parameter (the terminal tag itself) which will be an
  38. * observable object.
  39. *
  40. * This observable will emit `'cmd_entered'` events containing input for
  41. * processing. Here is a minimal shell:
  42. *
  43. * // Contents of myshell.js
  44. * function myshellclass(events) {
  45. * this.prompt = ''
  46. * this.welcome = ''
  47. * events.on('cmd_entered', function(input) {
  48. * // Do processing
  49. * })
  50. * }
  51. *
  52. * The observable also provides events to make things happen:
  53. *
  54. * events.trigger('disp_add', text) // Append `text` to the display
  55. * events.trigger('disp_set', text) // Display only `text`
  56. * events.trigger('disp_clear') // Clear the display
  57. * events.trigger('disp_hide') // Save the display, then clear it
  58. * events.trigger('disp_restore') // Restore the saved display
  59. * events.trigger('prompt_set', text) // Change the command prompt to `text`
  60. * events.trigger('prompt_hide') // Hide the command prompt
  61. * events.trigger('prompt_show') // Show the command prompt
  62. */
  63. <terminal>
  64. <display welcome={ welcome } events={ this } />
  65. <commandline prompt={ prompt } events={ this } />
  66. <style>
  67. terminal * { padding: 0; margin: 0; line-height: normal; font-size: 100%; }
  68. </style>
  69. /**
  70. * Create a new shell with the class name given to the terminal tag.
  71. * The terminal tag object passes events between the shell and the other tags.
  72. */
  73. var shell = window[opts.shell] ? new window[opts.shell](this) : {}
  74. this.welcome = shell.welcome || opts.welcome
  75. this.prompt = shell.prompt || opts.prompt
  76. </terminal>
  77. <display>
  78. <div each={ output }>
  79. <raw content={ content } />
  80. </div>
  81. var ev = opts.events
  82. var self = this
  83. this.output = []
  84. this.on('mount', function() {
  85. this.add(opts.welcome)
  86. })
  87. ev.on('disp_add', function(text) {
  88. self.add(text)
  89. })
  90. ev.on('disp_set', function(text) {
  91. if (text) {
  92. self.clear()
  93. self.add(text)
  94. }
  95. })
  96. ev.on('disp_clear', function() {
  97. self.clear()
  98. })
  99. ev.on('disp_hide', function() {
  100. self.saved = self.output.splice(0, self.output.length)
  101. self.clear()
  102. })
  103. ev.on('disp_restore', function() {
  104. if (self.saved.length > 0) {
  105. self.clear()
  106. self.output = self.saved.splice(0, self.saved.length)
  107. self.update()
  108. }
  109. })
  110. add(text) {
  111. if (text) {
  112. text = text.replace(/\r\n|\r|\n/g, '<br />')
  113. this.output.push({ 'content': text })
  114. this.update()
  115. }
  116. }
  117. clear() {
  118. this.output.length = 0
  119. this.update()
  120. }
  121. </display>
  122. <commandline>
  123. <form autocomplete='off' onsubmit={ process }>
  124. <raw name='lhs' content={ prompt } show={ visible } /><input type='text' name='command' />
  125. </form>
  126. <style>
  127. commandline input[name='command'],
  128. commandline input[name='command']:hover,
  129. commandline input[name='command']:focus {
  130. padding: 0; margin: 0; line-height: normal; font-size: 100%;
  131. background: transparent;
  132. border: none; outline: none;
  133. height: auto; width: 70%;
  134. display: inline;
  135. }
  136. </style>
  137. var ev = opts.events
  138. var self = this
  139. this.prompt = opts.prompt || '$ '
  140. this.visible = true
  141. this.on('mount', function() {
  142. document.getElementsByName('command')[0].focus()
  143. })
  144. ev.on('prompt_set', function(value) {
  145. self.prompt = value
  146. self.tags.lhs.write(value)
  147. })
  148. ev.on('prompt_hide', function() {
  149. self.update({ visible: false })
  150. })
  151. ev.on('prompt_show', function() {
  152. self.update({ visible: true })
  153. })
  154. process() {
  155. var prompt = this.visible ? this.prompt : ''
  156. var command = this.encode(this.command.value)
  157. this.command.value = ''
  158. ev.trigger('disp_add', prompt + command + '\n')
  159. ev.trigger('cmd_entered', command)
  160. // Refocus on the command input to scroll the display and keep it in view.
  161. this.command.blur()
  162. this.command.focus()
  163. }
  164. encode(text) {
  165. return text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
  166. }
  167. </commandline>
  168. <raw>
  169. <span></span>
  170. <style>
  171. raw { white-space: pre-wrap }
  172. </style>
  173. // Set the initial html using the `content` option.
  174. this.on('mount', function() {
  175. this.write(opts.content)
  176. })
  177. // Call `write()` manually to update the html.
  178. write(text) {
  179. this.root.innerHTML = text
  180. }
  181. </raw>