web-console 1.0.4 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of web-console might be problematic. Click here for more details.

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 %>