web-console 1.0.4 → 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +26 -247
  3. data/lib/action_dispatch/debug_exceptions.rb +115 -0
  4. data/lib/action_dispatch/exception_wrapper.rb +15 -0
  5. data/lib/action_dispatch/templates/rescues/_request_and_response.html.erb +34 -0
  6. data/lib/action_dispatch/templates/rescues/_request_and_response.text.erb +23 -0
  7. data/lib/action_dispatch/templates/rescues/_source.erb +27 -0
  8. data/lib/action_dispatch/templates/rescues/_trace.html.erb +76 -0
  9. data/lib/action_dispatch/templates/rescues/_trace.text.erb +15 -0
  10. data/lib/action_dispatch/templates/rescues/_web_console.html.erb +382 -0
  11. data/lib/action_dispatch/templates/rescues/diagnostics.html.erb +18 -0
  12. data/lib/action_dispatch/templates/rescues/diagnostics.text.erb +9 -0
  13. data/lib/action_dispatch/templates/rescues/layout.erb +162 -0
  14. data/lib/action_dispatch/templates/rescues/missing_template.html.erb +7 -0
  15. data/lib/action_dispatch/templates/rescues/missing_template.text.erb +3 -0
  16. data/lib/action_dispatch/templates/rescues/routing_error.html.erb +30 -0
  17. data/lib/action_dispatch/templates/rescues/routing_error.text.erb +11 -0
  18. data/lib/action_dispatch/templates/rescues/template_error.html.erb +22 -0
  19. data/lib/action_dispatch/templates/rescues/template_error.text.erb +8 -0
  20. data/lib/action_dispatch/templates/rescues/unknown_action.html.erb +6 -0
  21. data/lib/action_dispatch/templates/rescues/unknown_action.text.erb +3 -0
  22. data/lib/web_console.rb +17 -7
  23. data/lib/web_console/exception_extension.rb +22 -0
  24. data/lib/web_console/railtie.rb +15 -0
  25. data/lib/web_console/repl.rb +24 -0
  26. data/lib/web_console/repl_session.rb +90 -0
  27. data/lib/web_console/version.rb +1 -1
  28. data/lib/web_console/view_helpers.rb +21 -0
  29. data/test/action_pack/exception_wrapper_test.rb +26 -0
  30. data/test/dummy/app/controllers/exception_test_controller.rb +11 -0
  31. data/test/dummy/app/controllers/helper_test_controller.rb +5 -0
  32. data/test/dummy/app/views/helper_test/index.html.erb +220 -0
  33. data/test/dummy/app/views/layouts/application.html.erb +2 -0
  34. data/test/dummy/config/application.rb +0 -34
  35. data/test/dummy/config/routes.rb +3 -0
  36. data/test/dummy/db/development.sqlite3 +0 -0
  37. data/test/dummy/log/development.log +61270 -0
  38. data/test/dummy/log/test.log +3917 -0
  39. data/test/dummy/tmp/cache/assets/development/sprockets/038461854af2e8bccdb29768efd4768f +0 -0
  40. data/test/dummy/tmp/cache/assets/development/sprockets/0ec396634a5f6808b026257fd107c355 +0 -0
  41. data/test/dummy/tmp/cache/assets/development/sprockets/127a54171eea8d294e4673599861787d +0 -0
  42. data/{app/assets/stylesheets/web_console/application.css → test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705} +0 -0
  43. data/test/dummy/tmp/cache/assets/development/sprockets/17c571144b4e44da39bddb2d2c412414 +0 -0
  44. data/test/dummy/tmp/cache/assets/development/sprockets/1cb77d8cf661ccbc9de08f347c89b9f1 +0 -0
  45. data/test/dummy/tmp/cache/assets/development/sprockets/204edd12a29660722d4e0d8de9bd6652 +0 -0
  46. data/test/dummy/tmp/cache/assets/development/sprockets/2b96b037f3dfeccfe27113eb95b06ea1 +0 -0
  47. data/test/dummy/tmp/cache/assets/development/sprockets/2c853768baf811357d81d41bdfd05dcf +0 -0
  48. data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  49. data/test/dummy/tmp/cache/assets/development/sprockets/314d48e543146f617c4d3439a4d8d40d +0 -0
  50. data/test/dummy/tmp/cache/assets/development/sprockets/34f21019a876722b8c24a6da4f0ef50b +0 -0
  51. data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  52. data/{vendor/assets/javascripts/term.js → test/dummy/tmp/cache/assets/development/sprockets/36341e42f23669574fa1027d0958ff3e} +0 -0
  53. data/test/dummy/tmp/cache/assets/development/sprockets/44117154e909436e7eeaf10cdb18d2b4 +0 -0
  54. data/test/dummy/tmp/cache/assets/development/sprockets/496864a905d53afd8e176f29500f96a8 +0 -0
  55. data/test/dummy/tmp/cache/assets/development/sprockets/55b7b76605fdffe31d737d4ac1f1ef7b +0 -0
  56. data/test/dummy/tmp/cache/assets/development/sprockets/5ac98782fe3dfd0a766f75ce1801f0a0 +0 -0
  57. data/test/dummy/tmp/cache/assets/development/sprockets/6088d6f344b38303cc8028057d69e0f9 +0 -0
  58. data/test/dummy/tmp/cache/assets/development/sprockets/676dcf9b2d01b9dc7bd3183d8da88463 +0 -0
  59. data/test/dummy/tmp/cache/assets/development/sprockets/680381170dc160e358fc28076ea6886c +0 -0
  60. data/test/dummy/tmp/cache/assets/development/sprockets/6ad7acc9a22fe2a67ec24a1fc866c20e +0 -0
  61. data/test/dummy/tmp/cache/assets/development/sprockets/6bdb0d0c602e0e1bc304dc697e2cc6de +0 -0
  62. data/test/dummy/tmp/cache/assets/development/sprockets/6dc8d7aa69668fce85683aaad6615432 +0 -0
  63. data/test/dummy/tmp/cache/assets/development/sprockets/6e4d5b32cc444226f6597198994ccd5e +0 -0
  64. data/test/dummy/tmp/cache/assets/development/sprockets/74db0ca5cb8c8c347c9131a3ff516748 +0 -0
  65. data/test/dummy/tmp/cache/assets/development/sprockets/7999e525c88173c1beb785f002effc1d +0 -0
  66. data/{lib/assets/javascripts/web_console.js → test/dummy/tmp/cache/assets/development/sprockets/7a50a9e605754e99783de95715b976b0} +0 -0
  67. data/test/dummy/tmp/cache/assets/development/sprockets/806b0e33a2fe8e1245534345fa27c30a +0 -0
  68. data/{app/assets/javascripts/web_console/console_sessions.js → test/dummy/tmp/cache/assets/development/sprockets/8aa4c7aabff23c8089d41e9e54193483} +0 -0
  69. data/test/dummy/tmp/cache/assets/development/sprockets/90396626cba6cbec37e32038e6c54e76 +0 -0
  70. data/test/dummy/tmp/cache/assets/development/sprockets/976b28910aa72c90a3b30c6e940f51df +0 -0
  71. data/test/dummy/tmp/cache/assets/development/sprockets/99e1bd7cbc437505bc8f07bc528c721c +0 -0
  72. data/test/dummy/tmp/cache/assets/development/sprockets/aaccf2c9ae2add0863c9a49e0042a097 +0 -0
  73. data/test/dummy/tmp/cache/assets/development/sprockets/ae4677d24a79d9411f2fced5011d5807 +0 -0
  74. data/test/dummy/tmp/cache/assets/development/sprockets/b2401118729720034b6f3eda0b4c5025 +0 -0
  75. data/test/dummy/tmp/cache/assets/development/sprockets/c649837df826fc310cb80f1adafd6b8d +0 -0
  76. data/test/dummy/tmp/cache/assets/development/sprockets/cac185d59612fae451a12df3fc21bb51 +0 -0
  77. data/test/dummy/tmp/cache/assets/development/sprockets/cb0065359d3b5b296f71d673f4b276e9 +0 -0
  78. data/test/dummy/tmp/cache/assets/development/sprockets/cee8c6b09c33d2b276753e959712724e +0 -0
  79. data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  80. data/test/dummy/tmp/cache/assets/development/sprockets/d1f6e06bc2f112c4ec3a4c3f68351878 +0 -0
  81. data/test/dummy/tmp/cache/assets/development/sprockets/d20d83fd7ffa378b1b2b901786d640f3 +0 -0
  82. data/test/dummy/tmp/cache/assets/development/sprockets/d38c7c3aa1e72b55769ccb3607641ef4 +0 -0
  83. data/test/dummy/tmp/cache/assets/development/sprockets/d6b85d8b0b5c569388b89e56e9f6fed7 +0 -0
  84. data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  85. data/test/dummy/tmp/cache/assets/development/sprockets/d982412def520c434e2240eae6d29cf2 +0 -0
  86. data/test/dummy/tmp/cache/assets/development/sprockets/df048a8b0897b9c04acdf59c8f95b18f +0 -0
  87. data/test/dummy/tmp/cache/assets/development/sprockets/df600f50f002512c95d93bcfbab891ed +0 -0
  88. data/test/dummy/tmp/cache/assets/development/sprockets/e6d6b8bde546349764be7b44ffcf5807 +0 -0
  89. data/test/dummy/tmp/cache/assets/development/sprockets/eb25265794d2f7afd1684779d84efdac +0 -0
  90. data/test/dummy/tmp/cache/assets/development/sprockets/ee8826b12b7d9bfd717df950b58f82ab +0 -0
  91. data/test/dummy/tmp/cache/assets/development/sprockets/ef9824789c6ed3483590e0564a12e1d1 +0 -0
  92. data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  93. data/test/dummy/tmp/cache/assets/development/sprockets/fc7201c6cbef32453aa4175c520c8eae +0 -0
  94. data/test/dummy/tmp/cache/assets/test/sprockets/17c571144b4e44da39bddb2d2c412414 +0 -0
  95. data/test/dummy/tmp/cache/assets/test/sprockets/36341e42f23669574fa1027d0958ff3e +0 -0
  96. data/test/dummy/tmp/cache/assets/test/sprockets/55b7b76605fdffe31d737d4ac1f1ef7b +0 -0
  97. data/test/dummy/tmp/cache/assets/test/sprockets/5ac98782fe3dfd0a766f75ce1801f0a0 +0 -0
  98. data/test/dummy/tmp/cache/assets/test/sprockets/680381170dc160e358fc28076ea6886c +0 -0
  99. data/test/dummy/tmp/cache/assets/test/sprockets/6ad7acc9a22fe2a67ec24a1fc866c20e +0 -0
  100. data/test/dummy/tmp/cache/assets/test/sprockets/6e4d5b32cc444226f6597198994ccd5e +0 -0
  101. data/test/dummy/tmp/cache/assets/test/sprockets/7a50a9e605754e99783de95715b976b0 +0 -0
  102. data/test/dummy/tmp/cache/assets/test/sprockets/8aa4c7aabff23c8089d41e9e54193483 +0 -0
  103. data/test/dummy/tmp/cache/assets/test/sprockets/b2401118729720034b6f3eda0b4c5025 +0 -0
  104. data/test/dummy/tmp/cache/assets/test/sprockets/cb0065359d3b5b296f71d673f4b276e9 +0 -0
  105. data/test/dummy/tmp/cache/assets/test/sprockets/d1f6e06bc2f112c4ec3a4c3f68351878 +0 -0
  106. data/test/dummy/tmp/cache/assets/test/sprockets/d6b85d8b0b5c569388b89e56e9f6fed7 +0 -0
  107. data/test/dummy/tmp/cache/assets/test/sprockets/d982412def520c434e2240eae6d29cf2 +0 -0
  108. data/test/dummy/tmp/cache/assets/test/sprockets/df048a8b0897b9c04acdf59c8f95b18f +0 -0
  109. data/test/dummy/tmp/cache/assets/test/sprockets/e6d6b8bde546349764be7b44ffcf5807 +0 -0
  110. data/test/web_console/exception_extention_test.rb +16 -0
  111. data/test/web_console/repl_session_test.rb +32 -0
  112. data/test/web_console/repl_test.rb +26 -0
  113. metadata +191 -58
  114. data/app/assets/javascripts/web_console/application.js +0 -1
  115. data/app/assets/stylesheets/web_console/console_sessions.css.erb +0 -6
  116. data/app/controllers/web_console/application_controller.rb +0 -13
  117. data/app/controllers/web_console/console_sessions_controller.rb +0 -43
  118. data/app/helpers/web_console/application_helper.rb +0 -4
  119. data/app/helpers/web_console/console_session_helper.rb +0 -4
  120. data/app/models/web_console/console_session.rb +0 -96
  121. data/app/views/layouts/web_console/application.html.erb +0 -14
  122. data/app/views/web_console/console_sessions/index.html.erb +0 -15
  123. data/config/routes.rb +0 -11
  124. data/lib/assets/javascripts/web-console.js +0 -1
  125. data/lib/web_console/colors.rb +0 -87
  126. data/lib/web_console/colors/light.rb +0 -24
  127. data/lib/web_console/colors/monokai.rb +0 -24
  128. data/lib/web_console/colors/solarized.rb +0 -47
  129. data/lib/web_console/colors/tango.rb +0 -24
  130. data/lib/web_console/colors/xterm.rb +0 -24
  131. data/lib/web_console/engine.rb +0 -77
  132. data/lib/web_console/slave.rb +0 -139
  133. data/test/controllers/web_console/console_sessions_controller_test.rb +0 -95
  134. data/test/helpers/web_console/console_session_helper_test.rb +0 -6
  135. data/test/models/console_session_test.rb +0 -58
  136. data/test/web_console/colors_test.rb +0 -58
  137. data/test/web_console/engine_test.rb +0 -136
  138. data/test/web_console/slave_test.rb +0 -71
@@ -0,0 +1,34 @@
1
+ <% unless @exception.blamed_files.blank? %>
2
+ <% if (hide = @exception.blamed_files.length > 8) %>
3
+ <a href="#" onclick="return toggleTrace()">Toggle blamed files</a>
4
+ <% end %>
5
+ <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
6
+ <% end %>
7
+
8
+ <%
9
+ clean_params = @request.filtered_parameters.clone
10
+ clean_params.delete("action")
11
+ clean_params.delete("controller")
12
+
13
+ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
14
+
15
+ def debug_hash(object)
16
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
17
+ end unless self.class.method_defined?(:debug_hash)
18
+ %>
19
+
20
+ <h2 style="margin-top: 30px">Request</h2>
21
+ <p><b>Parameters</b>:</p> <pre><%= request_dump %></pre>
22
+
23
+ <div class="details">
24
+ <div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
25
+ <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
26
+ </div>
27
+
28
+ <div class="details">
29
+ <div class="summary"><a href="#" onclick="return toggleEnvDump()">Toggle env dump</a></div>
30
+ <div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
31
+ </div>
32
+
33
+ <h2 style="margin-top: 30px">Response</h2>
34
+ <p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
@@ -0,0 +1,23 @@
1
+ <%
2
+ clean_params = @request.filtered_parameters.clone
3
+ clean_params.delete("action")
4
+ clean_params.delete("controller")
5
+
6
+ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
7
+
8
+ def debug_hash(object)
9
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
10
+ end unless self.class.method_defined?(:debug_hash)
11
+ %>
12
+
13
+ Request parameters
14
+ <%= request_dump %>
15
+
16
+ Session dump
17
+ <%= debug_hash @request.session %>
18
+
19
+ Env dump
20
+ <%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %>
21
+
22
+ Response headers
23
+ <%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %>
@@ -0,0 +1,27 @@
1
+ <% if @extract_sources %>
2
+ <% @extract_sources.each_with_index do |extract_source, index| %>
3
+ <div class="source <%="hidden" if index != 0%>" id="frame-source-<%=index%>">
4
+ <div class="info">
5
+ Extracted source (around line <strong>#<%= extract_source[:line_number] %></strong>):
6
+ </div>
7
+ <div class="data">
8
+ <table cellpadding="0" cellspacing="0" class="lines">
9
+ <tr>
10
+ <td>
11
+ <pre class="line_numbers">
12
+ <% extract_source[:code].keys.each do |line_number| %>
13
+ <span><%= line_number -%></span>
14
+ <% end %>
15
+ </pre>
16
+ </td>
17
+ <td width="100%">
18
+ <pre>
19
+ <% extract_source[:code].each do |line, source| -%><div class="line<%= " active" if line == extract_source[:line_number] -%>"><%= source -%></div><% end -%>
20
+ </pre>
21
+ </td>
22
+ </tr>
23
+ </table>
24
+ </div>
25
+ </div>
26
+ <% end %>
27
+ <% end %>
@@ -0,0 +1,76 @@
1
+ <%
2
+ traces = { "Application Trace" => @application_trace,
3
+ "Framework Trace" => @framework_trace,
4
+ "Full Trace" => @full_trace }
5
+ names = traces.keys
6
+ %>
7
+
8
+ <p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
9
+
10
+ <div id="traces">
11
+ <% names.each do |name| %>
12
+ <%
13
+ show = "show('#{name.gsub(/\s/, '-')}');"
14
+ hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
15
+ %>
16
+ <a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
17
+ <% end %>
18
+
19
+ <% traces.each do |name, trace| %>
20
+ <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;">
21
+ <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
22
+ </div>
23
+ <% end %>
24
+
25
+ <script type="text/javascript">
26
+ var traceFrames = document.getElementsByClassName('trace-frames');
27
+ var selectedFrame, currentSource = document.getElementById('frame-source-0');
28
+
29
+ // Add click listeners for all stack frames
30
+ for (var i = 0; i < traceFrames.length; i++) {
31
+ traceFrames[i].addEventListener('click', function(e) {
32
+ e.preventDefault();
33
+ var target = e.target;
34
+ var frame_id = target.dataset.frameId;
35
+
36
+ // Change the binding of the console.
37
+ changeBinding(frame_id, function() {
38
+ if (selectedFrame) {
39
+ selectedFrame.className = selectedFrame.className.replace("selected", "");
40
+ }
41
+
42
+ target.className += " selected";
43
+ selectedFrame = target;
44
+ });
45
+
46
+ // Change the extracted source code
47
+ changeSourceExtract(frame_id);
48
+ });
49
+ }
50
+
51
+ function changeBinding(frame_id, callback) {
52
+ var consoleEl = document.getElementById('console');
53
+ if (! consoleEl) { return; }
54
+ var url = consoleEl.dataset.remotePath + "/trace";
55
+ var params = "frame_id=" + frame_id;
56
+ var xhr = new XMLHttpRequest();
57
+ xhr.open("POST", url, true);
58
+ xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
59
+ xhr.send(params);
60
+ xhr.onreadystatechange = function() {
61
+ if(xhr.readyState == 4 && xhr.status == 200) {
62
+ callback();
63
+ }
64
+ }
65
+ }
66
+
67
+ function changeSourceExtract(frame_id) {
68
+ var el = document.getElementById('frame-source-' + frame_id);
69
+ if (currentSource && el) {
70
+ currentSource.className += " hidden";
71
+ el.className = el.className.replace(" hidden", "");
72
+ currentSource = el;
73
+ }
74
+ }
75
+ </script>
76
+ </div>
@@ -0,0 +1,15 @@
1
+ <%
2
+ traces = { "Application Trace" => @application_trace,
3
+ "Framework Trace" => @framework_trace,
4
+ "Full Trace" => @full_trace }
5
+ %>
6
+
7
+ Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>
8
+
9
+ <% traces.each do |name, trace| %>
10
+ <% if trace.any? %>
11
+ <%= name %>
12
+ <%= trace.join("\n") %>
13
+
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,382 @@
1
+ <% if @console_session && @console_session.binding %>
2
+ <div id="console"
3
+ data-remote-path='<%= "console/repl_sessions/#{@console_session.id}" %>'
4
+ data-initial-prompt='<%= @console_session.prompt %>'>
5
+ <div id="resizer" style="width:100%; height: 3px; cursor: ns-resize;"></div>
6
+ </div>
7
+
8
+ <script type="text/javascript">
9
+
10
+ (function() {
11
+ // DOM helpers
12
+ function hasClass(el, className) {
13
+ var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
14
+ return el.className.match(regex);
15
+ }
16
+
17
+ function addClass(el, className) {
18
+ el.className += " " + className;
19
+ }
20
+
21
+ function removeClass(el, className) {
22
+ var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
23
+ el.className = el.className.replace(regex, '');
24
+ }
25
+
26
+ function removeAllChildren(el) {
27
+ while (el.firstChild) {
28
+ el.removeChild(el.firstChild);
29
+ }
30
+ }
31
+
32
+ function escapeHTML(html) {
33
+ return html.replace(/&/, "&amp;").replace(/</g, "&lt;");
34
+ }
35
+
36
+ // Add CSS styles dynamically. This probably doesnt work for IE <8.
37
+ var style = document.createElement('style');
38
+ style.type = 'text/css';
39
+ style.innerHTML = "#console { position: fixed; bottom: 0; width: 100%; height: 150px; border: 1px solid; padding-bottom: 20px; } " +
40
+ "#console div.console-inner { height: 100%; overflow: auto; background: #333; padding: 10px; } " +
41
+ "#console div.console-prompt-box { color: #fff; } " +
42
+ "#console div.console-message { color: #1ad027; } " +
43
+ "#console div.console-focus span.console-cursor { background:#fefefe; color:#333; font-weight:bold; }";
44
+ document.getElementsByTagName('head')[0].appendChild(style);
45
+
46
+ /**
47
+ * Constructor for command storage.
48
+ * It uses localStorage if available. Otherwise fallback to normal JS array.
49
+ */
50
+ function CommandStorage() {
51
+ this.previousCommands = [];
52
+ var previousCommandOffset = 0;
53
+ var hasLocalStorage = typeof window.localStorage !== 'undefined';
54
+ var STORAGE_KEY = "web_console_previous_commands";
55
+ var MAX_STORAGE = 100;
56
+
57
+ if (hasLocalStorage) {
58
+ this.previousCommands = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
59
+ previousCommandOffset = this.previousCommands.length;
60
+ }
61
+
62
+ this.addCommand = function(command) {
63
+ previousCommandOffset = this.previousCommands.push(command);
64
+
65
+ if (previousCommandOffset > MAX_STORAGE) {
66
+ this.previousCommands.splice(0, 1);
67
+ previousCommandOffset = MAX_STORAGE;
68
+ }
69
+
70
+ if (hasLocalStorage) {
71
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(this.previousCommands));
72
+ }
73
+ };
74
+
75
+ this.navigate = function(offset) {
76
+ previousCommandOffset += offset;
77
+
78
+ if (previousCommandOffset < 0) {
79
+ previousCommandOffset = -1;
80
+ return null;
81
+ }
82
+
83
+ if (previousCommandOffset >= this.previousCommands.length) {
84
+ previousCommandOffset = this.previousCommands.length;
85
+ return null;
86
+ }
87
+
88
+ return this.previousCommands[previousCommandOffset];
89
+ }
90
+ }
91
+
92
+ // REPLConsole Constructor
93
+ function REPLConsole(config) {
94
+ this.commandStorage = new CommandStorage();
95
+ this.prompt = config && config.promptLabel ? config.promptLabel : ' >>';
96
+ this.commandHandle = config && config.commandHandle ? config.commandHandle : function() { return this; }
97
+ }
98
+
99
+ REPLConsole.prototype.install = function(container) {
100
+ var _this = this;
101
+
102
+ document.onkeydown = function(ev) {
103
+ if (_this.focused) { _this.onKeyDown(ev); }
104
+ };
105
+
106
+ document.onkeypress = function(ev) {
107
+ if (_this.focused) { _this.onKeyPress(ev); }
108
+ };
109
+
110
+ document.addEventListener('mousedown', function(ev) {
111
+ var el = ev.target || ev.srcElement;
112
+
113
+ if (el) {
114
+ do {
115
+ if (el === container) {
116
+ _this.focus();
117
+ return;
118
+ }
119
+ } while (el = el.parentNode);
120
+
121
+ _this.blur();
122
+ }
123
+ });
124
+
125
+ // Render the console.
126
+ this.inner = document.createElement('div');
127
+ this.inner.className = "console-inner";
128
+ this.newPromptBox();
129
+ container.appendChild(this.inner);
130
+ this.focus();
131
+ };
132
+
133
+ REPLConsole.prototype.focus = function() {
134
+ this.focused = true;
135
+
136
+ if (! hasClass(this.inner, "console-focus")) {
137
+ addClass(this.inner, "console-focus");
138
+ }
139
+ this.scrollToBottom();
140
+ };
141
+
142
+ REPLConsole.prototype.blur = function() {
143
+ this.focused = false;
144
+ removeClass(this.inner, "console-focus");
145
+ };
146
+
147
+ /**
148
+ * Add a new empty prompt box to the console.
149
+ */
150
+ REPLConsole.prototype.newPromptBox = function() {
151
+ // Remove the caret from previous prompt box if any.
152
+ if (this.promptInput) {
153
+ this.removeCaretFromPrompt();
154
+ }
155
+
156
+ var promptBox = document.createElement('div');
157
+ promptBox.className = "console-prompt-box";
158
+ var promptLabel = document.createElement('span');
159
+ promptLabel.className ="console-prompt-label";
160
+ promptLabel.style.display = "inline";
161
+ promptLabel.innerHTML = this.prompt;
162
+ var promptInput = document.createElement('span');
163
+ promptInput.className = "console-prompt";
164
+ promptBox.appendChild(promptLabel);
165
+ promptBox.appendChild(promptInput);
166
+ this.inner.appendChild(promptBox);
167
+
168
+ this.promptLabel = promptLabel;
169
+ this.promptInput = promptInput;
170
+ this.setInput("");
171
+ this.scrollToBottom();
172
+ };
173
+
174
+ /**
175
+ * Remove the caret from the prompt box,
176
+ * mainly before adding a new prompt box.
177
+ * For simplicity, just re-render the prompt box
178
+ * with caret position -1.
179
+ */
180
+ REPLConsole.prototype.removeCaretFromPrompt = function() {
181
+ this.setInput(this._input, -1);
182
+ };
183
+
184
+ REPLConsole.prototype.setInput = function(input, caretPos) {
185
+ this._caretPos = caretPos === undefined ? input.length : caretPos;
186
+ this._input = input;
187
+ this.renderInput();
188
+ }
189
+
190
+ /**
191
+ * Render the input prompt. This is called whenever
192
+ * the user input changes, sometimes not very efficient.
193
+ */
194
+ REPLConsole.prototype.renderInput = function() {
195
+ // Clear the current input.
196
+ removeAllChildren(this.promptInput);
197
+
198
+ var promptCursor = document.createElement('span');
199
+ promptCursor.className = "console-cursor";
200
+ var before, current, master;
201
+
202
+ if (this._caretPos < 0) {
203
+ before = this._input;
204
+ current = after = "";
205
+ } else if (this._caretPos === this._input.length) {
206
+ before = this._input;
207
+ current = "\u00A0";
208
+ after = "";
209
+ } else {
210
+ before = this._input.substring(0, this._caretPos);
211
+ current = this._input.charAt(this._caretPos);
212
+ after = this._input.substring(this._caretPos + 1, this._input.length);
213
+ }
214
+
215
+ this.promptInput.appendChild(document.createTextNode(before));
216
+ promptCursor.appendChild(document.createTextNode(current));
217
+ this.promptInput.appendChild(promptCursor);
218
+ this.promptInput.appendChild(document.createTextNode(after));
219
+ };
220
+
221
+ REPLConsole.prototype.writeOutput = function(output) {
222
+ var consoleMessage = document.createElement('div');
223
+ consoleMessage.className = "console-message";
224
+ consoleMessage.innerHTML = escapeHTML(output);
225
+ this.inner.appendChild(consoleMessage);
226
+ this.newPromptBox();
227
+ };
228
+
229
+ REPLConsole.prototype.onEnterKey = function() {
230
+ var input = this._input;
231
+
232
+ if(input != "" && input !== undefined) {
233
+ this.commandStorage.addCommand(input);
234
+ }
235
+
236
+ this.commandHandle(input);
237
+ };
238
+
239
+ REPLConsole.prototype.onNavigateHistory = function(offset) {
240
+ var command = this.commandStorage.navigate(offset) || "";
241
+ this.setInput(command);
242
+ };
243
+
244
+ /**
245
+ * Handle control keys like up, down, left, right.
246
+ */
247
+ REPLConsole.prototype.onKeyDown = function(ev) {
248
+ switch (ev.keyCode) {
249
+ case 13:
250
+ // Enter key
251
+ this.onEnterKey();
252
+ ev.preventDefault();
253
+ break;
254
+ case 80:
255
+ // Ctrl-P
256
+ if (! ev.ctrlKey) break;
257
+ case 38:
258
+ // Up arrow
259
+ this.onNavigateHistory(-1);
260
+ ev.preventDefault();
261
+ break;
262
+ case 78:
263
+ // Ctrl-N
264
+ if (! ev.ctrlKey) break;
265
+ case 40:
266
+ // Down arrow
267
+ this.onNavigateHistory(1);
268
+ ev.preventDefault();
269
+ break;
270
+ case 37:
271
+ // Left arrow
272
+ var caretPos = this._caretPos > 0 ? this._caretPos - 1 : this._caretPos;
273
+ this.setInput(this._input, caretPos);
274
+ ev.preventDefault();
275
+ break;
276
+ case 39:
277
+ // Right arrow
278
+ var length = this._input.length;
279
+ var caretPos = this._caretPos < length ? this._caretPos + 1 : this._caretPos;
280
+ this.setInput(this._input, caretPos);
281
+ ev.preventDefault();
282
+ break;
283
+ case 8:
284
+ // Delete
285
+ this.deleteAtCurrent();
286
+ ev.preventDefault();
287
+ break;
288
+ default:
289
+ break;
290
+ }
291
+
292
+ ev.stopPropagation();
293
+ };
294
+
295
+ /**
296
+ * Handle input key press.
297
+ */
298
+ REPLConsole.prototype.onKeyPress = function(ev) {
299
+ var keyCode = ev.keyCode || ev.which;
300
+ this.insertAtCurrent(String.fromCharCode(keyCode));
301
+ ev.stopPropagation();
302
+ ev.preventDefault();
303
+ };
304
+
305
+ /**
306
+ * Delete a character at the current position.
307
+ */
308
+ REPLConsole.prototype.deleteAtCurrent = function() {
309
+ if (this._caretPos > 0) {
310
+ var caretPos = this._caretPos - 1;
311
+ var before = this._input.substring(0, caretPos);
312
+ var after = this._input.substring(this._caretPos, this._input.length);
313
+ this.setInput(before + after, caretPos);
314
+ }
315
+ };
316
+
317
+ /**
318
+ * Insert a character at the current position.
319
+ */
320
+ REPLConsole.prototype.insertAtCurrent = function(char) {
321
+ var before = this._input.substring(0, this._caretPos);
322
+ var after = this._input.substring(this._caretPos, this._input.length);
323
+ this.setInput(before + char + after, this._caretPos + 1);
324
+ };
325
+
326
+ REPLConsole.prototype.scrollToBottom = function() {
327
+ this.inner.scrollTop = this.inner.scrollHeight;
328
+ };
329
+
330
+ window.REPLConsole = REPLConsole;
331
+ })();
332
+
333
+ // Make the console resizable (not sure if works in all browser)
334
+ var consoleDiv = document.getElementById('console');
335
+ var resizer = document.getElementById('resizer');
336
+
337
+ var initDrag = function(e) {
338
+ var startY = e.clientY;
339
+ var startHeight = parseInt(document.defaultView.getComputedStyle(consoleDiv).height, 10);
340
+
341
+ var doDrag = function(e) {
342
+ consoleDiv.style.height = (startHeight + startY - e.clientY) + 'px';
343
+ };
344
+
345
+ var stopDrag = function(e) {
346
+ document.documentElement.removeEventListener('mousemove', doDrag, false);
347
+ document.documentElement.removeEventListener('mouseup', stopDrag, false);
348
+ };
349
+
350
+ document.documentElement.addEventListener('mousemove', doDrag, false);
351
+ document.documentElement.addEventListener('mouseup', stopDrag, false);
352
+ }
353
+
354
+ resizer.addEventListener('mousedown', initDrag, false);
355
+
356
+ // Install the console
357
+ var consoleDiv = document.getElementById('console');
358
+ var replConsole = new REPLConsole({
359
+ promptLabel: consoleDiv.dataset.initialPrompt,
360
+ commandHandle: function(line) {
361
+ var _this = this;
362
+ var xhr = new XMLHttpRequest();
363
+ var url = consoleDiv.dataset.remotePath;
364
+ var params = "input=" + line;
365
+
366
+ xhr.open("PUT", url, true);
367
+ xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
368
+ xhr.send(params);
369
+
370
+ xhr.onreadystatechange = function() {
371
+ if(xhr.readyState == 4 && xhr.status == 200) {
372
+ var response = JSON.parse(xhr.responseText);
373
+ console.log(response.output);
374
+ _this.writeOutput(response.output);
375
+ }
376
+ }
377
+ }
378
+ });
379
+
380
+ replConsole.install(consoleDiv);
381
+ </script>
382
+ <% end %>