web-console 2.0.0 → 2.1.0

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