web-console 2.0.0 → 2.1.0

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +132 -85
  3. data/lib/web_console.rb +14 -13
  4. data/lib/web_console/errors.rb +7 -0
  5. data/lib/web_console/{repl.rb → evaluator.rb} +7 -10
  6. data/lib/web_console/helper.rb +22 -0
  7. data/lib/web_console/integration.rb +8 -0
  8. data/lib/web_console/{core_ext/exception → integration}/cruby.rb +0 -0
  9. data/lib/web_console/integration/jruby.rb +111 -0
  10. data/lib/web_console/integration/rubinius.rb +66 -0
  11. data/lib/web_console/middleware.rb +117 -0
  12. data/lib/web_console/railtie.rb +61 -0
  13. data/lib/web_console/request.rb +30 -0
  14. data/lib/web_console/session.rb +65 -0
  15. data/lib/web_console/template.rb +49 -0
  16. data/lib/web_console/templates/_inner_console_markup.html +3 -0
  17. data/lib/web_console/templates/_markup.html +4 -0
  18. data/lib/web_console/templates/_prompt_box_markup.html +2 -0
  19. data/lib/web_console/templates/console.js +373 -0
  20. data/lib/web_console/templates/error_page.js +83 -0
  21. data/lib/web_console/templates/index.html +8 -0
  22. data/lib/web_console/templates/layouts/inlined_string.erb +1 -0
  23. data/lib/web_console/templates/layouts/javascript.erb +5 -0
  24. data/lib/web_console/templates/main.js +24 -0
  25. data/lib/web_console/templates/style.css +9 -0
  26. data/lib/web_console/version.rb +1 -1
  27. data/lib/web_console/whiny_request.rb +38 -0
  28. data/lib/web_console/whitelist.rb +42 -0
  29. data/test/dummy/config/environments/test.rb +0 -4
  30. data/test/dummy/log/development.log +7075 -0
  31. data/test/dummy/log/test.log +66006 -0
  32. data/test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  33. data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  34. data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  35. data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  36. data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  37. data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  38. data/test/support/scenarios/bad_custom_error_scenario.rb +17 -0
  39. data/test/support/scenarios/basic_nested_scenario.rb +15 -0
  40. data/test/support/scenarios/custom_error_scenario.rb +11 -0
  41. data/test/support/scenarios/eval_nested_scenario.rb +15 -0
  42. data/test/support/scenarios/flat_scenario.rb +9 -0
  43. data/test/support/scenarios/reraised_scenario.rb +21 -0
  44. data/test/test_helper.rb +50 -3
  45. data/test/web_console/evaluator_test.rb +73 -0
  46. data/test/web_console/helper_test.rb +76 -0
  47. data/test/web_console/integration_test.rb +47 -0
  48. data/test/web_console/middleware_test.rb +116 -0
  49. data/test/web_console/railtie_test.rb +99 -0
  50. data/test/web_console/request_test.rb +52 -0
  51. data/test/web_console/session_test.rb +59 -0
  52. data/test/web_console/whiny_request_test.rb +33 -0
  53. data/test/web_console/whitelist_test.rb +43 -0
  54. metadata +66 -56
  55. data/lib/action_dispatch/debug_exceptions.rb +0 -105
  56. data/lib/action_dispatch/exception_wrapper.rb +0 -38
  57. data/lib/action_dispatch/templates/rescues/_request_and_response.html.erb +0 -34
  58. data/lib/action_dispatch/templates/rescues/_request_and_response.text.erb +0 -23
  59. data/lib/action_dispatch/templates/rescues/_source.erb +0 -29
  60. data/lib/action_dispatch/templates/rescues/_trace.html.erb +0 -72
  61. data/lib/action_dispatch/templates/rescues/_trace.text.erb +0 -9
  62. data/lib/action_dispatch/templates/rescues/_web_console.html.erb +0 -420
  63. data/lib/action_dispatch/templates/rescues/diagnostics.html.erb +0 -18
  64. data/lib/action_dispatch/templates/rescues/diagnostics.text.erb +0 -9
  65. data/lib/action_dispatch/templates/rescues/layout.erb +0 -160
  66. data/lib/action_dispatch/templates/rescues/missing_template.html.erb +0 -13
  67. data/lib/action_dispatch/templates/rescues/missing_template.text.erb +0 -3
  68. data/lib/action_dispatch/templates/rescues/routing_error.html.erb +0 -34
  69. data/lib/action_dispatch/templates/rescues/routing_error.text.erb +0 -11
  70. data/lib/action_dispatch/templates/rescues/template_error.html.erb +0 -22
  71. data/lib/action_dispatch/templates/rescues/template_error.text.erb +0 -7
  72. data/lib/action_dispatch/templates/rescues/unknown_action.html.erb +0 -6
  73. data/lib/action_dispatch/templates/rescues/unknown_action.text.erb +0 -3
  74. data/lib/action_dispatch/templates/routes/_route.html.erb +0 -16
  75. data/lib/action_dispatch/templates/routes/_table.html.erb +0 -200
  76. data/lib/assets/javascripts/web-console.js +0 -1
  77. data/lib/assets/javascripts/web_console.js +0 -41
  78. data/lib/web_console/controller_helpers.rb +0 -46
  79. data/lib/web_console/core_ext/exception.rb +0 -7
  80. data/lib/web_console/core_ext/exception/jruby.rb +0 -25
  81. data/lib/web_console/core_ext/exception/rubinius.rb +0 -32
  82. data/lib/web_console/engine.rb +0 -47
  83. data/lib/web_console/repl_session.rb +0 -89
  84. data/lib/web_console/unsupported_platforms.rb +0 -28
  85. data/lib/web_console/view_helpers.rb +0 -16
  86. data/test/action_pack/exception_wrapper_test.rb +0 -26
  87. data/test/controllers/tests_controller_test.rb +0 -41
  88. data/test/web_console/core_ext/exception_test.rb +0 -46
  89. data/test/web_console/engine_test.rb +0 -108
  90. data/test/web_console/repl_session_test.rb +0 -32
  91. data/test/web_console/repl_test.rb +0 -75
@@ -1,105 +0,0 @@
1
- module ActionDispatch
2
- class DebugExceptions
3
- RESCUES_TEMPLATE_PATH.replace(File.expand_path('../templates', __FILE__))
4
-
5
- def call(env)
6
- request = Request.new(env)
7
-
8
- if request.put? && request.xhr? && allowed?(request) && m = env["PATH_INFO"].match(%r{/repl_sessions/(?<id>.+?)\z})
9
- update_repl_session(m[:id], request.params[:input])
10
- elsif request.post? && request.xhr? && allowed?(request) && m = env["PATH_INFO"].match(%r{/repl_sessions/(?<id>.+?)/trace\z})
11
- change_stack_trace(m[:id], request.params[:frame_id])
12
- else
13
- middleware_call(env)
14
- end
15
- end
16
-
17
- def middleware_call(env)
18
- _, headers, body = response = @app.call(env)
19
-
20
- if headers['X-Cascade'] == 'pass'
21
- body.close if body.respond_to?(:close)
22
- raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
23
- end
24
-
25
- response
26
- rescue Exception => exception
27
- raise exception if env['action_dispatch.show_exceptions'] == false
28
- render_exception(env, exception)
29
- end
30
-
31
- private
32
-
33
- def allowed?(request)
34
- request.remote_ip.in?(WebConsole.config.whitelisted_ips)
35
- end
36
-
37
- def update_repl_session(id, input)
38
- console_session = WebConsole::REPLSession.find(id)
39
- response = console_session.save(input: input)
40
- [ 200, { "Content-Type" => "text/plain; charset=utf-8" }, [ response.to_json ] ]
41
- end
42
-
43
- def change_stack_trace(id, frame_id)
44
- console_session = WebConsole::REPLSession.find(id)
45
- binding = console_session.binding_stack[frame_id.to_i]
46
- console_session.binding = binding
47
- [ 200, { "Content-Type" => "text/plain; charset=utf-8" }, [ JSON.dump("success") ] ]
48
- end
49
-
50
- def render_exception(env, exception)
51
- wrapper = ExceptionWrapper.new(env, exception)
52
- log_error(env, wrapper)
53
-
54
- if env['action_dispatch.show_detailed_exceptions']
55
- request = Request.new(env)
56
- if allowed?(request)
57
- console_session = WebConsole::REPLSession.create(
58
- binding: wrapper.exception.bindings.first,
59
- binding_stack: exception.bindings
60
- )
61
- end
62
-
63
- traces = wrapper.traces
64
- extract_sources = wrapper.extract_sources
65
- console_session = WebConsole::REPLSession.create(
66
- binding: exception.bindings.first,
67
- binding_stack: exception.bindings
68
- )
69
-
70
- trace_to_show = 'Application Trace'
71
- if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
72
- trace_to_show = 'Full Trace'
73
- end
74
-
75
- if source_to_show = traces[trace_to_show].first
76
- source_to_show_id = source_to_show[:id]
77
- end
78
-
79
- template = ActionView::Base.new([ RESCUES_TEMPLATE_PATH ],
80
- request: request,
81
- exception: wrapper.exception,
82
- show_source_idx: source_to_show_id,
83
- trace_to_show: trace_to_show,
84
- traces: traces,
85
- routes_inspector: routes_inspector(exception),
86
- source_extract: wrapper.extract_sources,
87
- console_session: console_session
88
- )
89
- file = "rescues/#{wrapper.rescue_template}"
90
-
91
- if request.xhr?
92
- body = template.render(template: file, layout: false, formats: [ :text ])
93
- format = "text/plain"
94
- else
95
- body = template.render(template: file, layout: 'rescues/layout')
96
- format = "text/html"
97
- end
98
-
99
- [ wrapper.status_code, { 'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s }, [ body ] ]
100
- else
101
- raise exception
102
- end
103
- end
104
- end
105
- end
@@ -1,38 +0,0 @@
1
- module ActionDispatch
2
- class ExceptionWrapper
3
- def traces
4
- appplication_trace_with_ids = []
5
- framework_trace_with_ids = []
6
- full_trace_with_ids = []
7
-
8
- if full_trace
9
- full_trace.each_with_index do |trace, idx|
10
- trace_with_id = { id: idx, trace: trace }
11
-
12
- appplication_trace_with_ids << trace_with_id if application_trace.include?(trace)
13
- framework_trace_with_ids << trace_with_id if framework_trace.include?(trace)
14
- full_trace_with_ids << trace_with_id
15
- end
16
- end
17
-
18
- {
19
- "Application Trace" => appplication_trace_with_ids,
20
- "Framework Trace" => framework_trace_with_ids,
21
- "Full Trace" => full_trace_with_ids
22
- }
23
- end
24
-
25
- def extract_sources
26
- exception.backtrace.map do |trace|
27
- file, line = trace.split(":")
28
- line_number = line.to_i
29
-
30
- {
31
- code: source_fragment(file, line_number) || {},
32
- file: file,
33
- line_number: line_number
34
- }
35
- end if exception.backtrace
36
- end
37
- end
38
- end
@@ -1,34 +0,0 @@
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>
@@ -1,23 +0,0 @@
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' %>
@@ -1,29 +0,0 @@
1
- <% if @source_extract %>
2
- <% @source_extract.each_with_index do |extract_source, index| %>
3
- <% if extract_source[:code] %>
4
- <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
5
- <div class="info">
6
- Extracted source (around line <strong>#<%= extract_source[:line_number] %></strong>):
7
- </div>
8
- <div class="data">
9
- <table cellpadding="0" cellspacing="0" class="lines">
10
- <tr>
11
- <td>
12
- <pre class="line_numbers">
13
- <% extract_source[:code].each_key do |line_number| %>
14
- <span><%= line_number -%></span>
15
- <% end %>
16
- </pre>
17
- </td>
18
- <td width="100%">
19
- <pre>
20
- <% extract_source[:code].each do |line, source| -%><div class="line<%= " active" if line == extract_source[:line_number] -%>"><%= source -%></div><% end -%>
21
- </pre>
22
- </td>
23
- </tr>
24
- </table>
25
- </div>
26
- </div>
27
- <% end %>
28
- <% end %>
29
- <% end %>
@@ -1,72 +0,0 @@
1
- <% names = @traces.keys %>
2
-
3
- <p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
4
-
5
- <div id="traces">
6
- <% names.each do |name| %>
7
- <%
8
- show = "show('#{name.gsub(/\s/, '-')}');"
9
- hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
10
- %>
11
- <a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
12
- <% end %>
13
-
14
- <% @traces.each do |name, trace| %>
15
- <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;">
16
- <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
17
- </div>
18
- <% end %>
19
-
20
- <script type="text/javascript">
21
- var traceFrames = document.getElementsByClassName('trace-frames');
22
- var selectedFrame, currentSource = document.getElementById('frame-source-0');
23
-
24
- // Add click listeners for all stack frames
25
- for (var i = 0; i < traceFrames.length; i++) {
26
- traceFrames[i].addEventListener('click', function(e) {
27
- e.preventDefault();
28
- var target = e.target;
29
- var frameId = target.dataset.frameId;
30
-
31
- // Change the binding of the console.
32
- changeBinding(frameId, function() {
33
- if (selectedFrame) {
34
- selectedFrame.className = selectedFrame.className.replace("selected", "");
35
- }
36
-
37
- target.className += " selected";
38
- selectedFrame = target;
39
- });
40
-
41
- // Change the extracted source code
42
- changeSourceExtract(frameId);
43
- });
44
- }
45
-
46
- function changeBinding(frameId, callback) {
47
- var consoleEl = document.getElementById('console');
48
- if (! consoleEl) { return; }
49
- var url = consoleEl.dataset.remotePath + "/trace";
50
- var params = "frame_id=" + encodeURIComponent(frameId);
51
- var xhr = new XMLHttpRequest();
52
- xhr.open("POST", url, true);
53
- xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
54
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
55
- xhr.send(params);
56
- xhr.onreadystatechange = function() {
57
- if (xhr.readyState == 4 && xhr.status == 200) {
58
- callback();
59
- }
60
- }
61
- }
62
-
63
- function changeSourceExtract(frameId) {
64
- var el = document.getElementById('frame-source-' + frameId);
65
- if (currentSource && el) {
66
- currentSource.className += " hidden";
67
- el.className = el.className.replace(" hidden", "");
68
- currentSource = el;
69
- }
70
- }
71
- </script>
72
- </div>
@@ -1,9 +0,0 @@
1
- Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>
2
-
3
- <% @traces.each do |name, trace| %>
4
- <% if trace.any? %>
5
- <%= name %>
6
- <%= trace.map { |t| t[:trace] }.join("\n") %>
7
-
8
- <% end %>
9
- <% end %>
@@ -1,420 +0,0 @@
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>
6
-
7
- <script type="text/javascript">
8
- (function() {
9
- // DOM helpers
10
- function hasClass(el, className) {
11
- var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
12
- return el.className.match(regex);
13
- }
14
-
15
- function addClass(el, className) {
16
- el.className += " " + className;
17
- }
18
-
19
- function removeClass(el, className) {
20
- var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
21
- el.className = el.className.replace(regex, '');
22
- }
23
-
24
- function removeAllChildren(el) {
25
- while (el.firstChild) {
26
- el.removeChild(el.firstChild);
27
- }
28
- }
29
-
30
- function escapeHTML(html) {
31
- return html
32
- .replace(/&/g, '&amp;')
33
- .replace(/</g, '&lt;')
34
- .replace(/>/g, '&gt;')
35
- .replace(/"/g, '&quot;')
36
- .replace(/'/g, '&#x27;')
37
- .replace(/`/g, '&#x60;');
38
- }
39
-
40
- // Add CSS styles dynamically. This probably doesnt work for IE <8.
41
- var style = document.createElement('style');
42
- style.type = 'text/css';
43
- style.innerHTML =
44
- "#console { position: fixed; left: 0; bottom: 0; width: 100%; height: 150px; border: 1px solid; padding: 0 0 20px 0; overflow: none; margin: 0; background: none repeat scroll 0% 0% #333; } " +
45
- "#console div.console-inner { font-family: monospace; font-size: 11px; height: 100%; overflow: auto; background: #333; border: 0; padding: 0 margin: 0; } " +
46
- "#console div.console-prompt-box { color: #FFF; } " +
47
- "#console pre.console-message { color: #1AD027; margin: 0; border: 0; white-space: pre-wrap; background-color: #333; padding: 0; } " +
48
- "#console div.console-focus span.console-cursor { background: #FEFEFE; color: #333; font-weight: bold; }";
49
- document.getElementsByTagName('head')[0].appendChild(style);
50
-
51
- /**
52
- * Constructor for command storage.
53
- * It uses localStorage if available. Otherwise fallback to normal JS array.
54
- */
55
- function CommandStorage() {
56
- this.previousCommands = [];
57
- var previousCommandOffset = 0;
58
- var hasLocalStorage = typeof window.localStorage !== 'undefined';
59
- var STORAGE_KEY = "web_console_previous_commands";
60
- var MAX_STORAGE = 100;
61
-
62
- if (hasLocalStorage) {
63
- this.previousCommands = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
64
- previousCommandOffset = this.previousCommands.length;
65
- }
66
-
67
- this.addCommand = function(command) {
68
- previousCommandOffset = this.previousCommands.push(command);
69
-
70
- if (previousCommandOffset > MAX_STORAGE) {
71
- this.previousCommands.splice(0, 1);
72
- previousCommandOffset = MAX_STORAGE;
73
- }
74
-
75
- if (hasLocalStorage) {
76
- localStorage.setItem(STORAGE_KEY, JSON.stringify(this.previousCommands));
77
- }
78
- };
79
-
80
- this.navigate = function(offset) {
81
- previousCommandOffset += offset;
82
-
83
- if (previousCommandOffset < 0) {
84
- previousCommandOffset = -1;
85
- return null;
86
- }
87
-
88
- if (previousCommandOffset >= this.previousCommands.length) {
89
- previousCommandOffset = this.previousCommands.length;
90
- return null;
91
- }
92
-
93
- return this.previousCommands[previousCommandOffset];
94
- }
95
- }
96
-
97
- // HTML strings for dynamic elements.
98
- var consoleInnerHtml = [
99
- "<div id='resizer' style='width: 100%; height: 3px; cursor: ns-resize;'></div>",
100
- "<div class='console-inner'></div>",
101
- "<input id='clipboard' type='text' style='visibility: hidden;'>"
102
- ].join("");
103
-
104
- var promptBoxHtml = [
105
- "<span class='console-prompt-label' style='display: inline; color: #FFF; background: none repeat scroll 0% 0% #333; border: 0; padding: 0;'></span>",
106
- "<pre class='console-prompt-display' style='display: inline; color: #FFF; background: none repeat scroll 0% 0% #333; border: 0; padding: 0;'></pre>"
107
- ].join("");
108
-
109
- // REPLConsole Constructor
110
- function REPLConsole(config) {
111
- this.commandStorage = new CommandStorage();
112
- this.prompt = config && config.promptLabel ? config.promptLabel : ' >>';
113
- this.commandHandle = config && config.commandHandle ? config.commandHandle : function() { return this; }
114
- }
115
-
116
-
117
- REPLConsole.prototype.install = function(container) {
118
- var _this = this;
119
-
120
- document.onkeydown = function(ev) {
121
- if (_this.focused) { _this.onKeyDown(ev); }
122
- };
123
-
124
- document.onkeypress = function(ev) {
125
- if (_this.focused) { _this.onKeyPress(ev); }
126
- };
127
-
128
- document.addEventListener('mousedown', function(ev) {
129
- var el = ev.target || ev.srcElement;
130
-
131
- if (el) {
132
- do {
133
- if (el === container) {
134
- _this.focus();
135
- return;
136
- }
137
- } while (el = el.parentNode);
138
-
139
- _this.blur();
140
- }
141
- });
142
-
143
- // Render the console.
144
- container.innerHTML = consoleInnerHtml;
145
- // Make the console resizable.
146
- document.getElementById('resizer').addEventListener('mousedown', function(ev) {
147
- var startY = ev.clientY;
148
- var startHeight = parseInt(document.defaultView.getComputedStyle(consoleDiv).height, 10);
149
-
150
- var doDrag = function(e) {
151
- consoleDiv.style.height = (startHeight + startY - e.clientY) + 'px';
152
- };
153
-
154
- var stopDrag = function(e) {
155
- document.documentElement.removeEventListener('mousemove', doDrag, false);
156
- document.documentElement.removeEventListener('mouseup', stopDrag, false);
157
- };
158
-
159
- document.documentElement.addEventListener('mousemove', doDrag, false);
160
- document.documentElement.addEventListener('mouseup', stopDrag, false);
161
- });
162
-
163
- // Initialize
164
- this.inner = container.getElementsByClassName('console-inner')[0];
165
- this.clipboard = document.getElementById('clipboard');
166
- this.newPromptBox();
167
- this.focus();
168
- };
169
-
170
- REPLConsole.prototype.focus = function() {
171
- if (! this.focused) {
172
- this.focused = true;
173
- if (! hasClass(this.inner, "console-focus")) {
174
- addClass(this.inner, "console-focus");
175
- }
176
- this.scrollToBottom();
177
- }
178
- };
179
-
180
- REPLConsole.prototype.blur = function() {
181
- this.focused = false;
182
- removeClass(this.inner, "console-focus");
183
- };
184
-
185
- /**
186
- * Add a new empty prompt box to the console.
187
- */
188
- REPLConsole.prototype.newPromptBox = function() {
189
- // Remove the caret from previous prompt display if any.
190
- if (this.promptDisplay) {
191
- this.removeCaretFromPrompt();
192
- }
193
-
194
- var promptBox = document.createElement('div');
195
- promptBox.className = "console-prompt-box";
196
- promptBox.innerHTML = promptBoxHtml;
197
- this.promptLabel = promptBox.getElementsByClassName('console-prompt-label')[0];
198
- this.promptDisplay = promptBox.getElementsByClassName('console-prompt-display')[0];
199
- // Render the prompt box
200
- this.setInput("");
201
- this.promptLabel.innerHTML = this.prompt;
202
- this.inner.appendChild(promptBox);
203
- this.scrollToBottom();
204
- };
205
-
206
- /**
207
- * Remove the caret from the prompt box,
208
- * mainly before adding a new prompt box.
209
- * For simplicity, just re-render the prompt box
210
- * with caret position -1.
211
- */
212
- REPLConsole.prototype.removeCaretFromPrompt = function() {
213
- this.setInput(this._input, -1);
214
- };
215
-
216
- REPLConsole.prototype.setInput = function(input, caretPos) {
217
- this._caretPos = caretPos === undefined ? input.length : caretPos;
218
- this._input = input;
219
- this.renderInput();
220
- };
221
-
222
- /**
223
- * Add some text to the existing input.
224
- */
225
- REPLConsole.prototype.addToInput = function(val, caretPos) {
226
- caretPos = caretPos || this._caretPos;
227
- var before = this._input.substring(0, caretPos);
228
- var after = this._input.substring(caretPos, this._input.length);
229
- var newInput = before + val + after;
230
- this.setInput(newInput, caretPos + val.length);
231
- };
232
-
233
- /**
234
- * Render the input prompt. This is called whenever
235
- * the user input changes, sometimes not very efficient.
236
- */
237
- REPLConsole.prototype.renderInput = function() {
238
- // Clear the current input.
239
- removeAllChildren(this.promptDisplay);
240
-
241
- var promptCursor = document.createElement('span');
242
- promptCursor.className = "console-cursor";
243
- var before, current, after;
244
-
245
- if (this._caretPos < 0) {
246
- before = this._input;
247
- current = after = "";
248
- } else if (this._caretPos === this._input.length) {
249
- before = this._input;
250
- current = "\u00A0";
251
- after = "";
252
- } else {
253
- before = this._input.substring(0, this._caretPos);
254
- current = this._input.charAt(this._caretPos);
255
- after = this._input.substring(this._caretPos + 1, this._input.length);
256
- }
257
-
258
- this.promptDisplay.appendChild(document.createTextNode(before));
259
- promptCursor.appendChild(document.createTextNode(current));
260
- this.promptDisplay.appendChild(promptCursor);
261
- this.promptDisplay.appendChild(document.createTextNode(after));
262
- };
263
-
264
- REPLConsole.prototype.writeOutput = function(output) {
265
- var consoleMessage = document.createElement('pre');
266
- consoleMessage.className = "console-message";
267
- consoleMessage.innerHTML = escapeHTML(output);
268
- this.inner.appendChild(consoleMessage);
269
- this.newPromptBox();
270
- };
271
-
272
- REPLConsole.prototype.onEnterKey = function() {
273
- var input = this._input;
274
-
275
- if(input != "" && input !== undefined) {
276
- this.commandStorage.addCommand(input);
277
- }
278
-
279
- this.commandHandle(input);
280
- };
281
-
282
- REPLConsole.prototype.onNavigateHistory = function(offset) {
283
- var command = this.commandStorage.navigate(offset) || "";
284
- this.setInput(command);
285
- };
286
-
287
- /**
288
- * Handle control keys like up, down, left, right.
289
- */
290
- REPLConsole.prototype.onKeyDown = function(ev) {
291
- switch (ev.keyCode) {
292
- case 13:
293
- // Enter key
294
- this.onEnterKey();
295
- ev.preventDefault();
296
- break;
297
- case 80:
298
- // Ctrl-P
299
- if (! ev.ctrlKey) break;
300
- case 38:
301
- // Up arrow
302
- this.onNavigateHistory(-1);
303
- ev.preventDefault();
304
- break;
305
- case 78:
306
- // Ctrl-N
307
- if (! ev.ctrlKey) break;
308
- case 40:
309
- // Down arrow
310
- this.onNavigateHistory(1);
311
- ev.preventDefault();
312
- break;
313
- case 37:
314
- // Left arrow
315
- var caretPos = this._caretPos > 0 ? this._caretPos - 1 : this._caretPos;
316
- this.setInput(this._input, caretPos);
317
- ev.preventDefault();
318
- break;
319
- case 39:
320
- // Right arrow
321
- var length = this._input.length;
322
- var caretPos = this._caretPos < length ? this._caretPos + 1 : this._caretPos;
323
- this.setInput(this._input, caretPos);
324
- ev.preventDefault();
325
- break;
326
- case 8:
327
- // Delete
328
- this.deleteAtCurrent();
329
- ev.preventDefault();
330
- break;
331
- case 86:
332
- // ctrl/command-v
333
- if (ev.ctrlKey || ev.metaKey) {
334
- // Let the pasted text go to our clipboard.
335
- this.clipboard.focus();
336
-
337
- // Pasting to clipboard doesn't happen immediately,
338
- // so we have to wait for a while to get the pasted text.
339
- var _this = this;
340
- setTimeout(function() {
341
- _this.addToInput(_this.clipboard.value);
342
- _this.clipboard.value = "";
343
- _this.clipboard.blur();
344
- }, 10);
345
- }
346
- break;
347
- default:
348
- break;
349
- }
350
-
351
- ev.stopPropagation();
352
- };
353
-
354
- /**
355
- * Handle input key press.
356
- */
357
- REPLConsole.prototype.onKeyPress = function(ev) {
358
- // Only write to the console if it's a single key press.
359
- if (ev.ctrlKey || ev.metaKey) { return; }
360
- var keyCode = ev.keyCode || ev.which;
361
- this.insertAtCurrent(String.fromCharCode(keyCode));
362
- ev.stopPropagation();
363
- ev.preventDefault();
364
- };
365
-
366
- /**
367
- * Delete a character at the current position.
368
- */
369
- REPLConsole.prototype.deleteAtCurrent = function() {
370
- if (this._caretPos > 0) {
371
- var caretPos = this._caretPos - 1;
372
- var before = this._input.substring(0, caretPos);
373
- var after = this._input.substring(this._caretPos, this._input.length);
374
- this.setInput(before + after, caretPos);
375
- }
376
- };
377
-
378
- /**
379
- * Insert a character at the current position.
380
- */
381
- REPLConsole.prototype.insertAtCurrent = function(char) {
382
- var before = this._input.substring(0, this._caretPos);
383
- var after = this._input.substring(this._caretPos, this._input.length);
384
- this.setInput(before + char + after, this._caretPos + 1);
385
- };
386
-
387
- REPLConsole.prototype.scrollToBottom = function() {
388
- this.inner.scrollTop = this.inner.scrollHeight;
389
- };
390
-
391
- window.REPLConsole = REPLConsole;
392
- })();
393
-
394
- // Install the console
395
- var consoleDiv = document.getElementById('console');
396
- var replConsole = new REPLConsole({
397
- promptLabel: consoleDiv.dataset.initialPrompt,
398
- commandHandle: function(line) {
399
- var _this = this;
400
- var xhr = new XMLHttpRequest();
401
- var url = consoleDiv.dataset.remotePath;
402
- var params = "input=" + encodeURIComponent(line);
403
-
404
- xhr.open("PUT", url, true);
405
- xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
406
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
407
- xhr.send(params);
408
-
409
- xhr.onreadystatechange = function() {
410
- if(xhr.readyState == 4 && xhr.status == 200) {
411
- var response = JSON.parse(xhr.responseText);
412
- _this.writeOutput(response.output);
413
- }
414
- }
415
- }
416
- });
417
-
418
- replConsole.install(consoleDiv);
419
- </script>
420
- <% end %>