terminal.tag 4.4 KB

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