:css /* Basic reset */ * { margin: 0; padding: 0; } table { width: 100%; border-collapse: collapse; } th, td { vertical-align: top; text-align: left; } textarea { resize: none; } body { font-size: 10pt; } body, td, input, textarea { font-family: helvetica neue, lucida grande, sans-serif; line-height: 1.5; color: #333; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6); } html { background: #f0f0f5; } .clearfix::after{ clear: both; content: "."; display: block; height: 0; visibility: hidden; } /* --------------------------------------------------------------------- * Basic layout * --------------------------------------------------------------------- */ /* Small */ @media screen and (max-width: 1100px) { html { overflow-y: scroll; } body { margin: 0 20px; } header.exception { margin: 0 -20px; } nav.sidebar { padding: 0; margin: 20px 0; } ul.frames { max-height: 200px; overflow: auto; } } /* Wide */ @media screen and (min-width: 1100px) { header.exception { position: fixed; top: 0; left: 0; right: 0; } nav.sidebar, .frame_info { position: fixed; top: 95px; bottom: 0; box-sizing: border-box; overflow-y: auto; overflow-x: hidden; } nav.sidebar { width: 40%; left: 20px; top: 115px; bottom: 20px; } .frame_info { right: 0; left: 40%; padding: 20px; padding-left: 10px; margin-left: 30px; } } nav.sidebar { background: #d3d3da; border-top: solid 3px #a33; border-bottom: solid 3px #a33; border-radius: 4px; box-shadow: 0 0 6px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1); } /* --------------------------------------------------------------------- * Header * --------------------------------------------------------------------- */ header.exception { padding: 18px 20px; height: 59px; min-height: 59px; overflow: hidden; background-color: #20202a; color: #aaa; text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3); font-weight: 200; box-shadow: inset 0 -5px 3px -3px rgba(0, 0, 0, 0.05), inset 0 -1px 0 rgba(0, 0, 0, 0.05); -webkit-text-smoothing: antialiased; } /* Heading */ header.exception h2 { font-weight: 200; font-size: 11pt; } header.exception h2, header.exception p { line-height: 1.4em; overflow: hidden; white-space: pre; text-overflow: ellipsis; } header.exception h2 strong { font-weight: 700; color: #d55; } header.exception p { font-weight: 200; font-size: 20pt; color: white; } header.exception:hover { height: auto; z-index: 2; } header.exception:hover h2, header.exception:hover p { padding-right: 20px; overflow-y: auto; word-wrap: break-word; white-space: pre-wrap; height: auto; max-height: 7.5em; } @media screen and (max-width: 1100px) { header.exception { height: auto; } header.exception h2, header.exception p { padding-right: 20px; overflow-y: auto; word-wrap: break-word; height: auto; max-height: 7em; } } /* --------------------------------------------------------------------- * Navigation * --------------------------------------------------------------------- */ nav.tabs { border-bottom: solid 1px #ddd; background-color: #eee; text-align: center; padding: 6px; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); } nav.tabs a { display: inline-block; height: 22px; line-height: 22px; padding: 0 10px; text-decoration: none; font-size: 8pt; font-weight: bold; color: #999; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6); } nav.tabs a.selected { color: white; background: rgba(0, 0, 0, 0.5); border-radius: 16px; box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.1); text-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 1px 0 rgba(0, 0, 0, 0.4); } nav.tabs a.disabled { text-decoration: line-through; text-shadow: none; cursor: default; } /* --------------------------------------------------------------------- * Sidebar * --------------------------------------------------------------------- */ ul.frames { box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } /* Each item */ ul.frames li { background-color: #f8f8f8; background: -webkit-linear-gradient(top, #f8f8f8 80%, #f0f0f0); background: -moz-linear-gradient(top, #f8f8f8 80%, #f0f0f0); background: linear-gradient(top, #f8f8f8 80%, #f0f0f0); box-shadow: inset 0 -1px 0 #e2e2e2; padding: 7px 20px; cursor: pointer; overflow: hidden; } ul.frames .name, ul.frames .location { overflow: hidden; height: 1.5em; white-space: nowrap; word-wrap: none; text-overflow: ellipsis; } ul.frames .method { color: #966; } ul.frames .location { font-size: 0.85em; font-weight: 400; color: #999; } ul.frames .line { font-weight: bold; } /* Selected frame */ ul.frames li.selected { background: #38a; box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), inset 0 2px 0 rgba(255, 255, 255, 0.01), inset 0 -1px 0 rgba(0, 0, 0, 0.1); } ul.frames li.selected .name, ul.frames li.selected .method, ul.frames li.selected .location { color: white; text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); } ul.frames li.selected .location { opacity: 0.6; } /* Iconography */ ul.frames li { padding-left: 60px; position: relative; } ul.frames li .icon { display: block; width: 20px; height: 20px; line-height: 20px; border-radius: 15px; text-align: center; background: white; border: solid 2px #ccc; font-size: 9pt; font-weight: 200; font-style: normal; position: absolute; top: 14px; left: 20px; } ul.frames .icon.application { background: #808090; border-color: #555; } ul.frames .icon.application:before { content: 'A'; color: white; text-shadow: 0 0 3px rgba(0, 0, 0, 0.2); } /* Responsiveness -- flow to single-line mode */ @media screen and (max-width: 1100px) { ul.frames li { padding-top: 6px; padding-bottom: 6px; padding-left: 36px; line-height: 1.3; } ul.frames li .icon { width: 11px; height: 11px; line-height: 11px; top: 7px; left: 10px; font-size: 5pt; } ul.frames .name, ul.frames .location { display: inline-block; line-height: 1.3; height: 1.3em; } ul.frames .name { margin-right: 10px; } } /* --------------------------------------------------------------------- * Monospace * --------------------------------------------------------------------- */ pre, code, .be-repl input, .be-repl .command-line span, textarea, .code_linenums { font-family: menlo, lucida console, monospace; font-size: 8pt; } /* --------------------------------------------------------------------- * Display area * --------------------------------------------------------------------- */ .trace_info { background: #fff; padding: 6px; border-radius: 3px; margin-bottom: 2px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.03), 1px 1px 0 rgba(0, 0, 0, 0.05), -1px 1px 0 rgba(0, 0, 0, 0.05), 0 0 0 4px rgba(0, 0, 0, 0.04); } .code_block{ background: #f1f1f1; border-left: 1px solid #ccc; } /* Titlebar */ .trace_info .title { background: #f1f1f1; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3); overflow: hidden; padding: 6px 10px; border: solid 1px #ccc; border-bottom: 0; border-top-left-radius: 2px; border-top-right-radius: 2px; } .trace_info .title .name, .trace_info .title .location { font-size: 9pt; line-height: 26px; height: 26px; overflow: hidden; } .trace_info .title .location { float: left; font-weight: bold; font-size: 10pt; } .trace_info .title .location a { color:inherit; text-decoration:none; border-bottom:1px solid #aaaaaa; } .trace_info .title .location a:hover { border-color:#666666; } .trace_info .title .name { float: right; font-weight: 200; } .code, .be-console, .unavailable { background: #fff; padding: 5px; box-shadow: inset 3px 3px 3px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1); } .code_linenums{ background:#f1f1f1; padding-top:10px; padding-bottom:9px; float:left; } .code_linenums span{ display:block; padding:0 12px; } .code { margin-bottom: -1px; border-top-left-radius:2px; padding: 10px 0; overflow: auto; } .code pre{ padding-left:12px; min-height:16px; } /* Source unavailable */ p.unavailable { padding: 20px 0 40px 0; text-align: center; color: #b99; font-weight: bold; } p.unavailable:before { content: '\00d7'; display: block; color: #daa; text-align: center; font-size: 40pt; font-weight: normal; margin-bottom: -10px; } @-webkit-keyframes highlight { 0% { background: rgba(220, 30, 30, 0.3); } 100% { background: rgba(220, 30, 30, 0.1); } } @-moz-keyframes highlight { 0% { background: rgba(220, 30, 30, 0.3); } 100% { background: rgba(220, 30, 30, 0.1); } } @keyframes highlight { 0% { background: rgba(220, 30, 30, 0.3); } 100% { background: rgba(220, 30, 30, 0.1); } } .code .highlight, .code_linenums .highlight { background: rgba(220, 30, 30, 0.1); -webkit-animation: highlight 400ms linear 1; -moz-animation: highlight 400ms linear 1; animation: highlight 400ms linear 1; } /* REPL shell */ .be-console { padding: 0 1px 10px 1px; border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } .be-console pre { padding: 10px 10px 0 10px; max-height: 400px; overflow-x: none; overflow-y: auto; margin-bottom: -3px; word-wrap: break-word; white-space: pre-wrap; } /* .command-line > span + input */ .be-console .command-line { display: table; width: 100%; } .be-console .command-line span, .be-console .command-line input { display: table-cell; } .be-console .command-line span { width: 1%; padding-right: 5px; padding-left: 10px; white-space: pre; } .be-console .command-line input { width: 99%; } /* Input box */ .be-console input, .be-console input:focus { outline: 0; border: 0; padding: 0; background: transparent; margin: 0; } /* Hint text */ .hint { margin: 15px 0 20px 0; font-size: 8pt; color: #8080a0; padding-left: 20px; } .hint:before { content: '\25b2'; margin-right: 5px; opacity: 0.5; } /* --------------------------------------------------------------------- * Variable infos * --------------------------------------------------------------------- */ .sub { padding: 10px 0; margin: 10px 0; } .sub:before { content: ''; display: block; width: 100%; height: 4px; border-radius: 2px; background: rgba(0, 150, 200, 0.05); box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.7), inset 0 0 0 1px rgba(0, 0, 0, 0.04), inset 2px 2px 2px rgba(0, 0, 0, 0.07); } .sub h3 { color: #39a; font-size: 1.1em; margin: 10px 0; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6); -webkit-font-smoothing: antialiased; } .sub .inset { overflow-y: auto; } .sub table { table-layout: fixed; } .sub table td { border-top: dotted 1px #ddd; padding: 7px 1px; } .sub table td.name { width: 150px; font-weight: bold; font-size: 0.8em; padding-right: 20px; word-wrap: break-word; } .sub table td pre { max-height: 15em; overflow-y: auto; } .sub table td pre { width: 100%; word-wrap: break-word; white-space: normal; } /* "(object doesn't support inspect)" */ .sub .unsupported { font-family: sans-serif; color: #777; } /* --------------------------------------------------------------------- * Scrollbar * --------------------------------------------------------------------- */ nav.sidebar::-webkit-scrollbar, .inset pre::-webkit-scrollbar, .be-console pre::-webkit-scrollbar, .code::-webkit-scrollbar { width: 10px; height: 10px; } .inset pre::-webkit-scrollbar-thumb, .be-console pre::-webkit-scrollbar-thumb, .code::-webkit-scrollbar-thumb { background: #ccc; border-radius: 5px; } nav.sidebar::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.0); border-radius: 5px; } nav.sidebar:hover::-webkit-scrollbar-thumb { background-color: #999; background: -webkit-linear-gradient(left, #aaa, #999); } .be-console pre:hover::-webkit-scrollbar-thumb, .inset pre:hover::-webkit-scrollbar-thumb, .code:hover::-webkit-scrollbar-thumb { background: #888; } :javascript (function() { var elements = ["section", "nav", "header", "footer", "audio"]; for (var i = 0; i < elements.length; i++) { document.createElement(elements[i]); } })(); :javascript if (window.Turbolinks) { for(var i=0; i < document.styleSheets.length; i++) { if(document.styleSheets[i].href) document.styleSheets[i].disabled = true; } if (window.Turbolinks.controller) { // Turbolinks > 5 (see https://github.com/turbolinks/turbolinks/issues/6) document.addEventListener("turbolinks:load", function restoreCSS(e) { for(var i=0; i < document.styleSheets.length; i++) { document.styleSheets[i].disabled = false; } document.removeEventListener("turbolinks:load", restoreCSS, false); }); } else { document.addEventListener("page:restore", function restoreCSS(e) { for(var i=0; i < document.styleSheets.length; i++) { document.styleSheets[i].disabled = false; } document.removeEventListener("page:restore", restoreCSS, false); }); } } .top %header.exception %h2 %strong= exception_type %span at #{request_path} %p= exception_message %section.backtrace %nav.sidebar %nav.tabs %a#application_frames{:href => "#"} Application Frames %a#all_frames{:href => "#"} All Frames %ul.frames - backtrace_frames.each_with_index do |frame, index| %li{:class => "#{frame.context}", "data-context" => "#{frame.context}", "data-index" => index} %span.stroke %i{:class => "icon #{frame.context}"} .info .name %strong= frame.class_name %span.method= frame.method_name .location %span.filename>= frame.pretty_path , line %span.line= frame.line - backtrace_frames.each_with_index do |frame, index| .frame_info{:id => "frame_info_#{index}", :style => "display:none;"} :javascript (function() { var OID = "#{id}"; var previousFrame = null; var previousFrameInfo = null; var allFrames = document.querySelectorAll("ul.frames li"); var allFrameInfos = document.querySelectorAll(".frame_info"); function apiCall(method, opts, cb) { var req = new XMLHttpRequest(); req.open("POST", "//" + window.location.host + "/__better_errors/" + OID + "/" + method, true); req.setRequestHeader("Content-Type", "application/json"); req.send(JSON.stringify(opts)); req.onreadystatechange = function() { if(req.readyState == 4) { var res = JSON.parse(req.responseText); cb(res); } }; } function escapeHTML(html) { return html.replace(/&/, "&").replace(/>"); REPL.all[this.index] = this; } REPL.prototype.focus = function() { this.inputElement.focus(); }; REPL.prototype.setPrompt = function(prompt) { this._prompt = prompt; this.promptElement.innerHTML = escapeHTML(prompt); }; REPL.prototype.getInput = function() { return this.inputElement.value; }; REPL.prototype.setInput = function(text) { this.inputElement.value = text; if(this.inputElement.setSelectionRange) { // set cursor to end of input this.inputElement.setSelectionRange(text.length, text.length); } }; REPL.prototype.writeRawOutput = function(output) { this.outputElement.innerHTML += output; this.outputElement.scrollTop = this.outputElement.scrollHeight; }; REPL.prototype.writeOutput = function(output) { this.writeRawOutput(escapeHTML(output)); }; REPL.prototype.sendInput = function(line) { var self = this; apiCall("eval", { "index": this.index, source: line }, function(response) { if(response.error) { self.writeOutput(response.error + "\n"); } self.writeOutput(self._prompt + " "); self.writeRawOutput(response.highlighted_input + "\n"); self.writeOutput(response.result); self.setPrompt(response.prompt); self.setInput(response.prefilled_input); }); }; REPL.prototype.onEnterKey = function() { var text = this.getInput(); if(text != "" && text !== undefined) { var previousCommands = JSON.parse(localStorage.getItem("better_errors_previous_commands")); this.previousCommandOffset = previousCommands.push(text); if(previousCommands.length > 100) { previousCommands.splice(0, 1); this.previousCommandOffset -= 1; } localStorage.setItem("better_errors_previous_commands", JSON.stringify(previousCommands)); } this.setInput(""); this.sendInput(text); }; REPL.prototype.onNavigateHistory = function(direction) { this.previousCommandOffset += direction; var previousCommands = JSON.parse(localStorage.getItem("better_errors_previous_commands")); if(this.previousCommandOffset < 0) { this.previousCommandOffset = -1; this.setInput(""); return; } if(this.previousCommandOffset >= previousCommands.length) { this.previousCommandOffset = previousCommands.length; this.setInput(""); return; } this.setInput(previousCommands[this.previousCommandOffset]); }; REPL.prototype.onKeyDown = function(ev) { if(ev.keyCode == 13) { this.onEnterKey(); } else if(ev.keyCode == 38 || (ev.ctrlKey && ev.keyCode == 80)) { // the user pressed the up arrow or Ctrl-P this.onNavigateHistory(-1); ev.preventDefault(); return false; } else if(ev.keyCode == 40 || (ev.ctrlKey && ev.keyCode == 78)) { // the user pressed the down arrow or Ctrl-N this.onNavigateHistory(1); ev.preventDefault(); return false; } }; function switchTo(el) { if(previousFrameInfo) previousFrameInfo.style.display = "none"; previousFrameInfo = el; el.style.display = "block"; var replInput = el.querySelector('.be-console input'); if (replInput) replInput.focus(); } function selectFrameInfo(index) { var el = allFrameInfos[index]; if(el) { if (el.loaded) { return switchTo(el); } apiCall("variables", { "index": index }, function(response) { el.loaded = true; if(response.error) { el.innerHTML = "

" + escapeHTML(response.error) + "

"; if(response.explanation) { el.innerHTML += "

" + escapeHTML(response.explanation) + "

"; } el.innerHTML += "

More about Better Errors

"; } else { el.innerHTML = response.html; var repl = el.querySelector(".be-repl .be-console"); if(repl) { new REPL(index).install(repl); } } switchTo(el); }); } } for(var i = 0; i < allFrames.length; i++) { (function(i, el) { var el = allFrames[i]; el.onclick = function() { if(previousFrame) { previousFrame.className = ""; } el.className = "selected"; previousFrame = el; selectFrameInfo(el.attributes["data-index"].value); }; })(i); } // Click the first application frame ( document.querySelector(".frames li.application") || document.querySelector(".frames li") ).onclick(); // This is the second query performed for frames; maybe the 'allFrames' list // currently used and this list can be better used to avoid the repetition: var applicationFramesCount = document.querySelectorAll( "ul.frames li[data-context=application]" ).length; var applicationFramesButtonIsInstalled = false; var applicationFramesButton = document.getElementById("application_frames"); var allFramesButton = document.getElementById("all_frames"); // The application frames button only needs to be bound if // there are actually any application frames to look at. var installApplicationFramesButton = function() { applicationFramesButton.onclick = function() { allFramesButton.className = ""; applicationFramesButton.className = "selected"; for(var i = 0; i < allFrames.length; i++) { if(allFrames[i].attributes["data-context"].value == "application") { allFrames[i].style.display = "block"; } else { allFrames[i].style.display = "none"; } } return false; }; applicationFramesButtonIsInstalled = true; } allFramesButton.onclick = function() { if(applicationFramesButtonIsInstalled) { applicationFramesButton.className = ""; } allFramesButton.className = "selected"; for(var i = 0; i < allFrames.length; i++) { allFrames[i].style.display = "block"; } return false; }; // If there are no application frames, select the 'All Frames' // tab by default. if(applicationFramesCount > 0) { installApplicationFramesButton(); applicationFramesButton.onclick(); } else { applicationFramesButton.className = "disabled"; applicationFramesButton.title = "No application frames available"; allFramesButton.onclick(); } })();