card-mod-format 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/lib/card/format/css_format.rb +17 -0
  3. data/lib/card/format/csv_format.rb +19 -0
  4. data/lib/card/format/data_format.rb +6 -0
  5. data/lib/card/format/file_format.rb +8 -0
  6. data/lib/card/format/html_format.rb +45 -0
  7. data/lib/card/format/js_format.rb +17 -0
  8. data/lib/card/format/json_format.rb +24 -0
  9. data/lib/card/format/rss_format.rb +9 -0
  10. data/lib/card/format/text_format.rb +16 -0
  11. data/lib/card/format/xml_format.rb +13 -0
  12. data/lib/card/path.rb +127 -0
  13. data/set/all/active_card.rb +6 -0
  14. data/set/all/base.rb +137 -0
  15. data/set/all/css.rb +37 -0
  16. data/set/all/csv.rb +93 -0
  17. data/set/all/error.rb +79 -0
  18. data/set/all/export.rb +68 -0
  19. data/set/all/file.rb +9 -0
  20. data/set/all/frame.rb +53 -0
  21. data/set/all/haml.rb +75 -0
  22. data/set/all/head.rb +148 -0
  23. data/set/all/header.rb +62 -0
  24. data/set/all/header/header_wrap.haml +4 -0
  25. data/set/all/html_content.rb +168 -0
  26. data/set/all/html_content/labeled.haml +4 -0
  27. data/set/all/html_error.rb +193 -0
  28. data/set/all/html_error/debug_server_error.haml +1015 -0
  29. data/set/all/html_error/not_found.haml +7 -0
  30. data/set/all/html_error/server_error.haml +5 -0
  31. data/set/all/html_show.rb +53 -0
  32. data/set/all/html_title.rb +47 -0
  33. data/set/all/html_wrapper.rb +159 -0
  34. data/set/all/js.rb +10 -0
  35. data/set/all/json.rb +166 -0
  36. data/set/all/links.rb +149 -0
  37. data/set/all/menu.rb +129 -0
  38. data/set/all/meta_tags.haml +4 -0
  39. data/set/all/path.rb +78 -0
  40. data/set/all/rich_html.rb +4 -0
  41. data/set/all/rss.rb +76 -0
  42. data/set/all/text.rb +7 -0
  43. data/set/self/home.rb +9 -0
  44. data/set/type/html.rb +27 -0
  45. data/set/type/json.rb +41 -0
  46. data/set/type/number.rb +22 -0
  47. data/set/type/phrase.rb +5 -0
  48. data/set/type/plain_text.rb +9 -0
  49. data/set/type/toggle.rb +38 -0
  50. data/set/type/uri.rb +15 -0
  51. metadata +123 -0
@@ -0,0 +1,62 @@
1
+ # LOCALIZE first item
2
+ TOGGLE_MAP = { close: %w[open open], open: %w[close closed] }.freeze
3
+
4
+ format :html do
5
+ view :header, perms: :none do
6
+ main_header
7
+ end
8
+
9
+ def main_header
10
+ header_wrap _render_header_title
11
+ end
12
+
13
+ def header_wrap content=nil
14
+ haml :header_wrap, content: (block_given? ? yield : output(content))
15
+ end
16
+
17
+ view :header_title, perms: :none do
18
+ header_title_elements
19
+ end
20
+
21
+ def header_title_elements
22
+ voo.hide :title_toggle if show_view?(:icon_toggle, :hide)
23
+ title_view = show_view?(:title_toggle, :hide) ? :title_toggle : :title
24
+ [_render_icon_toggle(optional: :hide), _render(title_view)]
25
+ end
26
+
27
+ def show_draft_link?
28
+ card.drafts.present? && @slot_view == :edit
29
+ end
30
+
31
+ view :title_toggle, perms: :none do
32
+ content_toggle(_render_title(hide: :title_link))
33
+ end
34
+
35
+ view :icon_toggle, perms: :none do
36
+ direction = @toggle_mode == :close ? :expand : :collapse_down
37
+ content_toggle icon_tag(direction)
38
+ end
39
+
40
+ view :toggle, :icon_toggle # deprecated; use icon_toggle
41
+
42
+ def content_toggle text=""
43
+ return if text.nil?
44
+
45
+ verb, adjective = toggle_verb_adjective
46
+ link_to_view adjective, text, title: "#{verb} #{card.name}", # LOCALIZE
47
+ class: "toggle-#{adjective} toggler nodblclick"
48
+ end
49
+
50
+ def toggle_view
51
+ toggle_verb_adjective.last
52
+ end
53
+
54
+ def toggle_verb_adjective
55
+ TOGGLE_MAP[@toggle_mode || :open] ||
56
+ raise(Card::Error, "invalid toggle mode: #{@toggle_mode}")
57
+ end
58
+
59
+ def structure_editable?
60
+ card.structure && card.template.ok?(:update)
61
+ end
62
+ end
@@ -0,0 +1,4 @@
1
+ %div{ class: [classy("d0-card-header")], href: path(view: toggle_view) }
2
+ = _render_menu
3
+ %div{ class: classy("d0-card-header-title") }
4
+ = content
@@ -0,0 +1,168 @@
1
+ format :html do
2
+ def prepare_content_slot
3
+ class_up "card-slot", "d0-card-content"
4
+ voo.hide :menu
5
+ end
6
+
7
+ before(:content) { prepare_content_slot }
8
+
9
+ view :content do
10
+ voo.hide :edit_button
11
+ wrap do
12
+ [_render_menu, _render_core, _render_edit_button(edit: :inline)]
13
+ end
14
+ end
15
+
16
+ before(:content_with_edit_button) do
17
+ prepare_content_slot
18
+ end
19
+
20
+ view :content_with_edit_button do
21
+ wrap do
22
+ [_render_menu, _render_core, _render_edit_button(edit: :inline)]
23
+ end
24
+ end
25
+
26
+ view :short_content, wrap: { div: { class: "text-muted" } } do
27
+ short_content
28
+ end
29
+
30
+ view :raw_one_line_content, unknown: :mini_unknown,
31
+ wrap: { div: { class: "text-muted" } } do
32
+ raw_one_line_content
33
+ end
34
+
35
+ view :one_line_content, unknown: :mini_unknown,
36
+ wrap: { div: { class: "text-muted" } } do
37
+ one_line_content
38
+ end
39
+
40
+ before(:content_with_title) { prepare_content_slot }
41
+
42
+ view :content_with_title do
43
+ wrap true, title: card.format(:text).render_core do
44
+ [_render_menu, _render_core]
45
+ end
46
+ end
47
+
48
+ before :content_panel do
49
+ prepare_content_slot
50
+ class_up "card-slot", "card"
51
+ end
52
+
53
+ view :content_panel do
54
+ wrap do
55
+ wrap_with :div, class: "card-body" do
56
+ [_render_menu, _render_core]
57
+ end
58
+ end
59
+ end
60
+
61
+ view :titled do
62
+ @content_body = true
63
+ wrap do
64
+ [
65
+ naming { render_header },
66
+ render_flash,
67
+ wrap_body { render_titled_content },
68
+ render_comment_box(optional: :hide)
69
+ ]
70
+ end
71
+ end
72
+
73
+ view :labeled, unknown: true do
74
+ @content_body = true
75
+ wrap(true, class: "row") do
76
+ labeled(render_title, wrap_body { "#{render_menu}#{render_labeled_content}" })
77
+ end
78
+ end
79
+
80
+ def labeled label, content
81
+ haml :labeled, label: label, content: content
82
+ end
83
+
84
+ def labeled_field field, item_view=:name, opts={}
85
+ opts[:title] ||= Card.fetch_name field
86
+ field_nest field, opts.merge(view: :labeled,
87
+ items: (opts[:items] || {}).merge(view: item_view))
88
+ end
89
+
90
+ view :open do
91
+ toggle_logic
92
+ @toggle_mode = :open
93
+ @content_body = true
94
+ frame do
95
+ [_render_open_content, render_comment_box(optional: :hide)]
96
+ end
97
+ end
98
+
99
+ view :closed do
100
+ with_nest_mode :compact do
101
+ toggle_logic
102
+ class_up "d0-card-body", "closed-content"
103
+ @content_body = false
104
+ @toggle_mode = :close
105
+ frame
106
+ end
107
+ end
108
+
109
+ def toggle_logic
110
+ show_view?(:title_link, :hide) ? voo.show(:icon_toggle) : voo.show(:title_toggle)
111
+ end
112
+
113
+ def current_set_card
114
+ set_name = params[:current_set]
115
+ set_name ||= "#{card.name}+*type" if card.known? && card.type_id == Card::CardtypeID
116
+ set_name ||= "#{card.name}+*self"
117
+ Card.fetch(set_name)
118
+ end
119
+
120
+ def raw_one_line_content
121
+ cleaned = Card::Content.clean! render_raw, {}
122
+ cut_with_ellipsis cleaned
123
+ end
124
+
125
+ def one_line_content
126
+ # TODO: use a version of Card::Content.smart_truncate
127
+ # that counts characters instead of clean!
128
+ cleaned = Card::Content.clean! render_core, {}
129
+ cut_with_ellipsis cleaned
130
+ end
131
+
132
+ # LOCALIZE
133
+ def short_content
134
+ short_content_items || short_content_fields || short_content_from_core
135
+ end
136
+
137
+ def short_content_items
138
+ return unless card.respond_to? :count
139
+ "#{count} #{'item'.pluralize count}"
140
+ end
141
+
142
+ def short_content_fields
143
+ with_short_content_fields do |num_fields|
144
+ "#{num_fields} #{'field'.pluralize num_fields}" if num_fields.positive?
145
+ end
146
+ end
147
+
148
+ def with_short_content_fields
149
+ yield nested_field_names.size if voo.structure || card.structure
150
+ end
151
+
152
+ def short_content_from_core
153
+ content = render_core
154
+ if content.blank?
155
+ "empty"
156
+ elsif content.size <= 5
157
+ content
158
+ elsif content.count("\n") < 2
159
+ "#{content.size} characters"
160
+ else
161
+ "#{content.count("\n") + 1} lines"
162
+ end
163
+ end
164
+
165
+ def count
166
+ @count ||= card.count
167
+ end
168
+ end
@@ -0,0 +1,4 @@
1
+ .label.col-4.text-right
2
+ = label
3
+ .labeled-content.col-8
4
+ = content
@@ -0,0 +1,193 @@
1
+ format :html do
2
+ view :server_error, template: :haml
3
+
4
+ view :debug_server_error, wrap: { modal: { size: :full } } do
5
+ error_page = BetterErrors::ErrorPage.new Card::Error.current,
6
+ "PATH_INFO" => request.env["REQUEST_URI"]
7
+ haml :debug_server_error, {}, error_page
8
+ end
9
+
10
+ view :unknown do
11
+ createable { wrap { unknown_link "#{unknown_icon} #{render_title}" } }
12
+ end
13
+
14
+ # icon only, no wrap
15
+ view :mini_unknown, unknown: true, cache: :never do
16
+ createable { unknown_link unknown_icon }
17
+ end
18
+
19
+ def createable
20
+ card.ok?(:create) ? yield : ""
21
+ end
22
+
23
+ def unknown_link text
24
+ path_opts = voo.type ? { card: { type: voo.type } } : {}
25
+ link_to_view :new_in_modal, text, path: path_opts, class: classy("unknown-link")
26
+ end
27
+
28
+ def unknown_icon
29
+ fa_icon "plus-square"
30
+ end
31
+
32
+ view :compact_missing, perms: :none do
33
+ wrap_with :span, h(title_in_context), class: "text-muted"
34
+ end
35
+
36
+ view :conflict, cache: :never do
37
+ actor_link = link_to_card card.last_action.act.actor.name
38
+ class_up "card-slot", "error-view"
39
+ wrap do # LOCALIZE
40
+ alert "warning" do
41
+ %(
42
+ <strong>Conflict!</strong>
43
+ <span class="new-current-revision-id">#{card.last_action_id}</span>
44
+ <div>#{actor_link} has also been making changes.</div>
45
+ <div>Please examine below, resolve above, and re-submit.</div>
46
+ #{render_act}
47
+ )
48
+ end
49
+ end
50
+ end
51
+
52
+ view :errors, perms: :none do
53
+ return if card.errors.empty?
54
+
55
+ voo.title = card.name.blank? ? "Problems" : tr(:problems_name, cardname: card.name)
56
+ voo.hide! :menu
57
+ class_up "alert", "card-error-msg"
58
+ standard_errors voo.title
59
+ end
60
+
61
+ view :not_found do
62
+ voo.hide! :menu
63
+ voo.title = "Not Found"
64
+ frame do
65
+ [not_found_errors, sign_in_or_up_links("to create it")]
66
+ end
67
+ end
68
+
69
+ view :denial do
70
+ focal? ? loud_denial : quiet_denial
71
+ end
72
+
73
+ def view_for_unknown view
74
+ main? && ok?(:create) ? :new : super
75
+ end
76
+
77
+ def show_all_errors?
78
+ # make configurable by env
79
+ Auth.always_ok? || Rails.env.development?
80
+ end
81
+
82
+ def error_cardname exception
83
+ cardname = super
84
+ show_all_errors? ? backtrace_link(cardname, exception) : cardname
85
+ end
86
+
87
+ def rendering_error exception, view
88
+ wrap_with(:span, class: "render-error alert alert-danger") { super }
89
+ end
90
+
91
+ def error_modal_id
92
+ @error_modal_id ||= unique_id
93
+ end
94
+
95
+ def error_message exception
96
+ %{
97
+ <h3>Error message (visible to admin only)</h3>
98
+ <p><strong>#{CGI.escapeHTML exception.message}</strong></p>
99
+ <div>#{exception.backtrace * "<br>\n"}</div>
100
+ }
101
+ end
102
+
103
+ def backtrace_link cardname, exception
104
+ # TODO: make this a modal link after new modal handling is merged in
105
+ wrap_with(:span, title: error_message(exception)) { cardname }
106
+ end
107
+
108
+ def standard_errors heading=nil
109
+ alert "warning", true do
110
+ [
111
+ (wrap_with(:h4, heading, class: "alert-heading error") if heading),
112
+ error_messages.join("<hr>")
113
+ ]
114
+ end
115
+ end
116
+
117
+ def simple_error_message message
118
+ h message
119
+ end
120
+
121
+ def standard_error_message error
122
+ "<p><strong>#{h error.attribute.to_s.upcase}:</strong> #{h error.message}</p>"
123
+ end
124
+
125
+ def not_found_errors
126
+ if card.errors.any?
127
+ standard_errors
128
+ else
129
+ haml :not_found
130
+ end
131
+ end
132
+
133
+ def sign_in_or_up_links to_task
134
+ return if Auth.signed_in?
135
+
136
+ links = [signin_link, signup_link].compact.join " #{tr :or} "
137
+ wrap_with(:div) do
138
+ [tr(:please), links, to_task].join(" ") + "."
139
+ end
140
+ end
141
+
142
+ def signin_link
143
+ link_to_card :signin, tr(:sign_in),
144
+ class: "signin-link", slotter: true, path: { view: :open }
145
+ end
146
+
147
+ def signup_link
148
+ return unless signup_ok?
149
+
150
+ link_to_card :signup, tr(:sign_up),
151
+ class: "signup-link", slotter: true, path: { action: :new }
152
+ end
153
+
154
+ def signup_ok?
155
+ Card.new(type_id: Card::SignupID).ok? :create
156
+ end
157
+
158
+ def quiet_denial
159
+ wrap_with :span, class: "denied" do
160
+ "<!-- Sorry, you don't have permission (#{@denied_task}) -->"
161
+ end
162
+ end
163
+
164
+ def loud_denial
165
+ voo.hide :menu
166
+ frame do
167
+ [wrap_with(:h1, tr(:sorry)),
168
+ wrap_with(:div, loud_denial_message)]
169
+ end
170
+ end
171
+
172
+ def loud_denial_message
173
+ to_task = @denied_task ? tr(:denied_task, denied_task: @denied_task) : tr(:to_do_that)
174
+
175
+ case
176
+ when not_denied_task_read?
177
+ tr(:read_only)
178
+ when Auth.signed_in?
179
+ tr(:need_permission_task, task: to_task)
180
+ else
181
+ Env.save_interrupted_action request.env["REQUEST_URI"]
182
+ sign_in_or_up_links to_do_unauthorized_task
183
+ end
184
+ end
185
+
186
+ def not_denied_task_read?
187
+ @denied_task != :read && Card.config.read_only
188
+ end
189
+
190
+ def to_do_unauthorized_task
191
+ @denied_task ? tr(:denied_task, denied_task: @denied_task) : tr(:to_do_that)
192
+ end
193
+ end
@@ -0,0 +1,1015 @@
1
+
2
+ :css
3
+ /* Basic reset */
4
+ * {
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ table {
10
+ width: 100%;
11
+ border-collapse: collapse;
12
+ }
13
+
14
+ th, td {
15
+ vertical-align: top;
16
+ text-align: left;
17
+ }
18
+
19
+ textarea {
20
+ resize: none;
21
+ }
22
+
23
+ body {
24
+ font-size: 10pt;
25
+ }
26
+
27
+ body, td, input, textarea {
28
+ font-family: helvetica neue, lucida grande, sans-serif;
29
+ line-height: 1.5;
30
+ color: #333;
31
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
32
+ }
33
+
34
+ html {
35
+ background: #f0f0f5;
36
+ }
37
+
38
+ .clearfix::after{
39
+ clear: both;
40
+ content: ".";
41
+ display: block;
42
+ height: 0;
43
+ visibility: hidden;
44
+ }
45
+
46
+ /* ---------------------------------------------------------------------
47
+ * Basic layout
48
+ * --------------------------------------------------------------------- */
49
+
50
+ /* Small */
51
+ @media screen and (max-width: 1100px) {
52
+ html {
53
+ overflow-y: scroll;
54
+ }
55
+
56
+ body {
57
+ margin: 0 20px;
58
+ }
59
+
60
+ header.exception {
61
+ margin: 0 -20px;
62
+ }
63
+
64
+ nav.sidebar {
65
+ padding: 0;
66
+ margin: 20px 0;
67
+ }
68
+
69
+ ul.frames {
70
+ max-height: 200px;
71
+ overflow: auto;
72
+ }
73
+ }
74
+
75
+ /* Wide */
76
+ @media screen and (min-width: 1100px) {
77
+ header.exception {
78
+ position: fixed;
79
+ top: 0;
80
+ left: 0;
81
+ right: 0;
82
+ }
83
+
84
+ nav.sidebar,
85
+ .frame_info {
86
+ position: fixed;
87
+ top: 95px;
88
+ bottom: 0;
89
+
90
+ box-sizing: border-box;
91
+
92
+ overflow-y: auto;
93
+ overflow-x: hidden;
94
+ }
95
+
96
+ nav.sidebar {
97
+ width: 40%;
98
+ left: 20px;
99
+ top: 115px;
100
+ bottom: 20px;
101
+ }
102
+
103
+ .frame_info {
104
+ right: 0;
105
+ left: 40%;
106
+
107
+ padding: 20px;
108
+ padding-left: 10px;
109
+ margin-left: 30px;
110
+ }
111
+ }
112
+
113
+ nav.sidebar {
114
+ background: #d3d3da;
115
+ border-top: solid 3px #a33;
116
+ border-bottom: solid 3px #a33;
117
+ border-radius: 4px;
118
+ box-shadow: 0 0 6px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
119
+ }
120
+
121
+ /* ---------------------------------------------------------------------
122
+ * Header
123
+ * --------------------------------------------------------------------- */
124
+
125
+ header.exception {
126
+ padding: 18px 20px;
127
+
128
+ height: 59px;
129
+ min-height: 59px;
130
+
131
+ overflow: hidden;
132
+
133
+ background-color: #20202a;
134
+ color: #aaa;
135
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
136
+ font-weight: 200;
137
+ box-shadow: inset 0 -5px 3px -3px rgba(0, 0, 0, 0.05), inset 0 -1px 0 rgba(0, 0, 0, 0.05);
138
+
139
+ -webkit-text-smoothing: antialiased;
140
+ }
141
+
142
+ /* Heading */
143
+ header.exception h2 {
144
+ font-weight: 200;
145
+ font-size: 11pt;
146
+ }
147
+
148
+ header.exception h2,
149
+ header.exception p {
150
+ line-height: 1.4em;
151
+ overflow: hidden;
152
+ white-space: pre;
153
+ text-overflow: ellipsis;
154
+ }
155
+
156
+ header.exception h2 strong {
157
+ font-weight: 700;
158
+ color: #d55;
159
+ }
160
+
161
+ header.exception p {
162
+ font-weight: 200;
163
+ font-size: 20pt;
164
+ color: white;
165
+ }
166
+
167
+ header.exception:hover {
168
+ height: auto;
169
+ z-index: 2;
170
+ }
171
+
172
+ header.exception:hover h2,
173
+ header.exception:hover p {
174
+ padding-right: 20px;
175
+ overflow-y: auto;
176
+ word-wrap: break-word;
177
+ white-space: pre-wrap;
178
+ height: auto;
179
+ max-height: 7.5em;
180
+ }
181
+
182
+ @media screen and (max-width: 1100px) {
183
+ header.exception {
184
+ height: auto;
185
+ }
186
+
187
+ header.exception h2,
188
+ header.exception p {
189
+ padding-right: 20px;
190
+ overflow-y: auto;
191
+ word-wrap: break-word;
192
+ height: auto;
193
+ max-height: 7em;
194
+ }
195
+ }
196
+
197
+ <haml_silent>
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+ </haml_silent>
212
+ /* ---------------------------------------------------------------------
213
+ * Navigation
214
+ * --------------------------------------------------------------------- */
215
+
216
+ nav.tabs {
217
+ border-bottom: solid 1px #ddd;
218
+
219
+ background-color: #eee;
220
+ text-align: center;
221
+
222
+ padding: 6px;
223
+
224
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
225
+ }
226
+
227
+ nav.tabs a {
228
+ display: inline-block;
229
+
230
+ height: 22px;
231
+ line-height: 22px;
232
+ padding: 0 10px;
233
+
234
+ text-decoration: none;
235
+ font-size: 8pt;
236
+ font-weight: bold;
237
+
238
+ color: #999;
239
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
240
+ }
241
+
242
+ nav.tabs a.selected {
243
+ color: white;
244
+ background: rgba(0, 0, 0, 0.5);
245
+ border-radius: 16px;
246
+ box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.1);
247
+ text-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 1px 0 rgba(0, 0, 0, 0.4);
248
+ }
249
+
250
+ nav.tabs a.disabled {
251
+ text-decoration: line-through;
252
+ text-shadow: none;
253
+ cursor: default;
254
+ }
255
+
256
+ /* ---------------------------------------------------------------------
257
+ * Sidebar
258
+ * --------------------------------------------------------------------- */
259
+
260
+ ul.frames {
261
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
262
+ }
263
+
264
+ /* Each item */
265
+ ul.frames li {
266
+ background-color: #f8f8f8;
267
+ background: -webkit-linear-gradient(top, #f8f8f8 80%, #f0f0f0);
268
+ background: -moz-linear-gradient(top, #f8f8f8 80%, #f0f0f0);
269
+ background: linear-gradient(top, #f8f8f8 80%, #f0f0f0);
270
+ box-shadow: inset 0 -1px 0 #e2e2e2;
271
+ padding: 7px 20px;
272
+
273
+ cursor: pointer;
274
+ overflow: hidden;
275
+ }
276
+
277
+ ul.frames .name,
278
+ ul.frames .location {
279
+ overflow: hidden;
280
+ height: 1.5em;
281
+
282
+ white-space: nowrap;
283
+ word-wrap: none;
284
+ text-overflow: ellipsis;
285
+ }
286
+
287
+ ul.frames .method {
288
+ color: #966;
289
+ }
290
+
291
+ ul.frames .location {
292
+ font-size: 0.85em;
293
+ font-weight: 400;
294
+ color: #999;
295
+ }
296
+
297
+ ul.frames .line {
298
+ font-weight: bold;
299
+ }
300
+
301
+ /* Selected frame */
302
+ ul.frames li.selected {
303
+ background: #38a;
304
+ 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);
305
+ }
306
+
307
+ ul.frames li.selected .name,
308
+ ul.frames li.selected .method,
309
+ ul.frames li.selected .location {
310
+ color: white;
311
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
312
+ }
313
+
314
+ ul.frames li.selected .location {
315
+ opacity: 0.6;
316
+ }
317
+
318
+ /* Iconography */
319
+ ul.frames li {
320
+ padding-left: 60px;
321
+ position: relative;
322
+ }
323
+
324
+ ul.frames li .icon {
325
+ display: block;
326
+ width: 20px;
327
+ height: 20px;
328
+ line-height: 20px;
329
+ border-radius: 15px;
330
+
331
+ text-align: center;
332
+
333
+ background: white;
334
+ border: solid 2px #ccc;
335
+
336
+ font-size: 9pt;
337
+ font-weight: 200;
338
+ font-style: normal;
339
+
340
+ position: absolute;
341
+ top: 14px;
342
+ left: 20px;
343
+ }
344
+
345
+ ul.frames .icon.application {
346
+ background: #808090;
347
+ border-color: #555;
348
+ }
349
+
350
+ ul.frames .icon.application:before {
351
+ content: 'A';
352
+ color: white;
353
+ text-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
354
+ }
355
+
356
+ /* Responsiveness -- flow to single-line mode */
357
+ @media screen and (max-width: 1100px) {
358
+ ul.frames li {
359
+ padding-top: 6px;
360
+ padding-bottom: 6px;
361
+ padding-left: 36px;
362
+ line-height: 1.3;
363
+ }
364
+
365
+ ul.frames li .icon {
366
+ width: 11px;
367
+ height: 11px;
368
+ line-height: 11px;
369
+
370
+ top: 7px;
371
+ left: 10px;
372
+ font-size: 5pt;
373
+ }
374
+
375
+ ul.frames .name,
376
+ ul.frames .location {
377
+ display: inline-block;
378
+ line-height: 1.3;
379
+ height: 1.3em;
380
+ }
381
+
382
+ ul.frames .name {
383
+ margin-right: 10px;
384
+ }
385
+ }
386
+
387
+ /* ---------------------------------------------------------------------
388
+ * Monospace
389
+ * --------------------------------------------------------------------- */
390
+
391
+ pre, code, .be-repl input, .be-repl .command-line span, textarea, .code_linenums {
392
+ font-family: menlo, lucida console, monospace;
393
+ font-size: 8pt;
394
+ }
395
+
396
+ /* ---------------------------------------------------------------------
397
+ * Display area
398
+ * --------------------------------------------------------------------- */
399
+
400
+ .trace_info {
401
+ background: #fff;
402
+ padding: 6px;
403
+ border-radius: 3px;
404
+ margin-bottom: 2px;
405
+ 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);
406
+ }
407
+
408
+ .code_block{
409
+ background: #f1f1f1;
410
+ border-left: 1px solid #ccc;
411
+ }
412
+
413
+ /* Titlebar */
414
+ .trace_info .title {
415
+ background: #f1f1f1;
416
+
417
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
418
+ overflow: hidden;
419
+ padding: 6px 10px;
420
+
421
+ border: solid 1px #ccc;
422
+ border-bottom: 0;
423
+
424
+ border-top-left-radius: 2px;
425
+ border-top-right-radius: 2px;
426
+ }
427
+
428
+ .trace_info .title .name,
429
+ .trace_info .title .location {
430
+ font-size: 9pt;
431
+ line-height: 26px;
432
+ height: 26px;
433
+ overflow: hidden;
434
+ }
435
+
436
+ .trace_info .title .location {
437
+ float: left;
438
+ font-weight: bold;
439
+ font-size: 10pt;
440
+ }
441
+
442
+ .trace_info .title .location a {
443
+ color:inherit;
444
+ text-decoration:none;
445
+ border-bottom:1px solid #aaaaaa;
446
+ }
447
+
448
+ .trace_info .title .location a:hover {
449
+ border-color:#666666;
450
+ }
451
+
452
+ .trace_info .title .name {
453
+ float: right;
454
+ font-weight: 200;
455
+ }
456
+
457
+ .code, .be-console, .unavailable {
458
+ background: #fff;
459
+ padding: 5px;
460
+
461
+ box-shadow: inset 3px 3px 3px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
462
+ }
463
+
464
+ .code_linenums{
465
+ background:#f1f1f1;
466
+ padding-top:10px;
467
+ padding-bottom:9px;
468
+ float:left;
469
+ }
470
+
471
+ .code_linenums span{
472
+ display:block;
473
+ padding:0 12px;
474
+ }
475
+
476
+ .code {
477
+ margin-bottom: -1px;
478
+ border-top-left-radius:2px;
479
+ padding: 10px 0;
480
+ overflow: auto;
481
+ }
482
+
483
+ .code pre{
484
+ padding-left:12px;
485
+ min-height:16px;
486
+ }
487
+
488
+ /* Source unavailable */
489
+ p.unavailable {
490
+ padding: 20px 0 40px 0;
491
+ text-align: center;
492
+ color: #b99;
493
+ font-weight: bold;
494
+ }
495
+
496
+ p.unavailable:before {
497
+ content: '\00d7';
498
+ display: block;
499
+
500
+ color: #daa;
501
+
502
+ text-align: center;
503
+ font-size: 40pt;
504
+ font-weight: normal;
505
+ margin-bottom: -10px;
506
+ }
507
+
508
+ @-webkit-keyframes highlight {
509
+ 0% { background: rgba(220, 30, 30, 0.3); }
510
+ 100% { background: rgba(220, 30, 30, 0.1); }
511
+ }
512
+ @-moz-keyframes highlight {
513
+ 0% { background: rgba(220, 30, 30, 0.3); }
514
+ 100% { background: rgba(220, 30, 30, 0.1); }
515
+ }
516
+ @keyframes highlight {
517
+ 0% { background: rgba(220, 30, 30, 0.3); }
518
+ 100% { background: rgba(220, 30, 30, 0.1); }
519
+ }
520
+
521
+ .code .highlight, .code_linenums .highlight {
522
+ background: rgba(220, 30, 30, 0.1);
523
+ -webkit-animation: highlight 400ms linear 1;
524
+ -moz-animation: highlight 400ms linear 1;
525
+ animation: highlight 400ms linear 1;
526
+ }
527
+
528
+ /* REPL shell */
529
+ .be-console {
530
+ padding: 0 1px 10px 1px;
531
+ border-bottom-left-radius: 2px;
532
+ border-bottom-right-radius: 2px;
533
+ }
534
+
535
+ .be-console pre {
536
+ padding: 10px 10px 0 10px;
537
+ max-height: 400px;
538
+ overflow-x: none;
539
+ overflow-y: auto;
540
+ margin-bottom: -3px;
541
+ word-wrap: break-word;
542
+ white-space: pre-wrap;
543
+ }
544
+
545
+ /* .command-line > span + input */
546
+ .be-console .command-line {
547
+ display: table;
548
+ width: 100%;
549
+ }
550
+
551
+ .be-console .command-line span,
552
+ .be-console .command-line input {
553
+ display: table-cell;
554
+ }
555
+
556
+ .be-console .command-line span {
557
+ width: 1%;
558
+ padding-right: 5px;
559
+ padding-left: 10px;
560
+ white-space: pre;
561
+ }
562
+
563
+ .be-console .command-line input {
564
+ width: 99%;
565
+ }
566
+
567
+ /* Input box */
568
+ .be-console input,
569
+ .be-console input:focus {
570
+ outline: 0;
571
+ border: 0;
572
+ padding: 0;
573
+ background: transparent;
574
+ margin: 0;
575
+ }
576
+
577
+ /* Hint text */
578
+ .hint {
579
+ margin: 15px 0 20px 0;
580
+ font-size: 8pt;
581
+ color: #8080a0;
582
+ padding-left: 20px;
583
+ }
584
+
585
+ .hint:before {
586
+ content: '\25b2';
587
+ margin-right: 5px;
588
+ opacity: 0.5;
589
+ }
590
+
591
+ /* ---------------------------------------------------------------------
592
+ * Variable infos
593
+ * --------------------------------------------------------------------- */
594
+
595
+ .sub {
596
+ padding: 10px 0;
597
+ margin: 10px 0;
598
+ }
599
+
600
+ .sub:before {
601
+ content: '';
602
+ display: block;
603
+ width: 100%;
604
+ height: 4px;
605
+
606
+ border-radius: 2px;
607
+ background: rgba(0, 150, 200, 0.05);
608
+ 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);
609
+ }
610
+
611
+ .sub h3 {
612
+ color: #39a;
613
+ font-size: 1.1em;
614
+ margin: 10px 0;
615
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
616
+
617
+ -webkit-font-smoothing: antialiased;
618
+ }
619
+
620
+ .sub .inset {
621
+ overflow-y: auto;
622
+ }
623
+
624
+ .sub table {
625
+ table-layout: fixed;
626
+ }
627
+
628
+ .sub table td {
629
+ border-top: dotted 1px #ddd;
630
+ padding: 7px 1px;
631
+ }
632
+
633
+ .sub table td.name {
634
+ width: 150px;
635
+
636
+ font-weight: bold;
637
+ font-size: 0.8em;
638
+ padding-right: 20px;
639
+
640
+ word-wrap: break-word;
641
+ }
642
+
643
+ .sub table td pre {
644
+ max-height: 15em;
645
+ overflow-y: auto;
646
+ }
647
+
648
+ .sub table td pre {
649
+ width: 100%;
650
+
651
+ word-wrap: break-word;
652
+ white-space: normal;
653
+ }
654
+
655
+ /* "(object doesn't support inspect)" */
656
+ .sub .unsupported {
657
+ font-family: sans-serif;
658
+ color: #777;
659
+ }
660
+
661
+ /* ---------------------------------------------------------------------
662
+ * Scrollbar
663
+ * --------------------------------------------------------------------- */
664
+
665
+ nav.sidebar::-webkit-scrollbar,
666
+ .inset pre::-webkit-scrollbar,
667
+ .be-console pre::-webkit-scrollbar,
668
+ .code::-webkit-scrollbar {
669
+ width: 10px;
670
+ height: 10px;
671
+ }
672
+
673
+ .inset pre::-webkit-scrollbar-thumb,
674
+ .be-console pre::-webkit-scrollbar-thumb,
675
+ .code::-webkit-scrollbar-thumb {
676
+ background: #ccc;
677
+ border-radius: 5px;
678
+ }
679
+
680
+ nav.sidebar::-webkit-scrollbar-thumb {
681
+ background: rgba(0, 0, 0, 0.0);
682
+ border-radius: 5px;
683
+ }
684
+
685
+ nav.sidebar:hover::-webkit-scrollbar-thumb {
686
+ background-color: #999;
687
+ background: -webkit-linear-gradient(left, #aaa, #999);
688
+ }
689
+
690
+ .be-console pre:hover::-webkit-scrollbar-thumb,
691
+ .inset pre:hover::-webkit-scrollbar-thumb,
692
+ .code:hover::-webkit-scrollbar-thumb {
693
+ background: #888;
694
+ }
695
+ :javascript
696
+ (function() {
697
+ var elements = ["section", "nav", "header", "footer", "audio"];
698
+ for (var i = 0; i < elements.length; i++) {
699
+ document.createElement(elements[i]);
700
+ }
701
+ })();
702
+ :javascript
703
+ if (window.Turbolinks) {
704
+ for(var i=0; i < document.styleSheets.length; i++) {
705
+ if(document.styleSheets[i].href)
706
+ document.styleSheets[i].disabled = true;
707
+ }
708
+ if (window.Turbolinks.controller) {
709
+ // Turbolinks > 5 (see https://github.com/turbolinks/turbolinks/issues/6)
710
+ document.addEventListener("turbolinks:load", function restoreCSS(e) {
711
+ for(var i=0; i < document.styleSheets.length; i++) {
712
+ document.styleSheets[i].disabled = false;
713
+ }
714
+ document.removeEventListener("turbolinks:load", restoreCSS, false);
715
+ });
716
+ } else {
717
+ document.addEventListener("page:restore", function restoreCSS(e) {
718
+ for(var i=0; i < document.styleSheets.length; i++) {
719
+ document.styleSheets[i].disabled = false;
720
+ }
721
+ document.removeEventListener("page:restore", restoreCSS, false);
722
+ });
723
+ }
724
+ }
725
+ .top
726
+ %header.exception
727
+ %h2
728
+ %strong= exception_type
729
+ %span
730
+ at #{request_path}
731
+ %p= exception_message
732
+ %section.backtrace
733
+ %nav.sidebar
734
+ %nav.tabs
735
+ %a#application_frames{:href => "#"} Application Frames
736
+ %a#all_frames{:href => "#"} All Frames
737
+ %ul.frames
738
+ - backtrace_frames.each_with_index do |frame, index|
739
+ %li{:class => "#{frame.context}", "data-context" => "#{frame.context}", "data-index" => index}
740
+ %span.stroke
741
+ %i{:class => "icon #{frame.context}"}
742
+ .info
743
+ .name
744
+ %strong= frame.class_name
745
+ %span.method= frame.method_name
746
+ .location
747
+ %span.filename>= frame.pretty_path
748
+ , line
749
+ %span.line= frame.line
750
+ - backtrace_frames.each_with_index do |frame, index|
751
+ .frame_info{:id => "frame_info_#{index}", :style => "display:none;"}
752
+ :javascript
753
+ (function() {
754
+
755
+ var OID = "#{id}";
756
+
757
+ var previousFrame = null;
758
+ var previousFrameInfo = null;
759
+ var allFrames = document.querySelectorAll("ul.frames li");
760
+ var allFrameInfos = document.querySelectorAll(".frame_info");
761
+
762
+ function apiCall(method, opts, cb) {
763
+ var req = new XMLHttpRequest();
764
+ req.open("POST", "//" + window.location.host + "/__better_errors/" + OID + "/" + method, true);
765
+ req.setRequestHeader("Content-Type", "application/json");
766
+ req.send(JSON.stringify(opts));
767
+ req.onreadystatechange = function() {
768
+ if(req.readyState == 4) {
769
+ var res = JSON.parse(req.responseText);
770
+ cb(res);
771
+ }
772
+ };
773
+ }
774
+
775
+ function escapeHTML(html) {
776
+ return html.replace(/&/, "&amp;").replace(/</g, "&lt;");
777
+ }
778
+
779
+ function REPL(index) {
780
+ this.index = index;
781
+
782
+ var previousCommands = JSON.parse(localStorage.getItem("better_errors_previous_commands"));
783
+ if(previousCommands === null) {
784
+ localStorage.setItem("better_errors_previous_commands", JSON.stringify([]));
785
+ previousCommands = [];
786
+ }
787
+
788
+ this.previousCommandOffset = previousCommands.length;
789
+ }
790
+
791
+ REPL.all = [];
792
+
793
+ REPL.prototype.install = function(containerElement) {
794
+ this.container = containerElement;
795
+
796
+ this.promptElement = this.container.querySelector(".command-line .prompt");
797
+ this.inputElement = this.container.querySelector("input");
798
+ this.outputElement = this.container.querySelector("pre");
799
+
800
+ var self = this;
801
+ this.inputElement.onkeydown = function(ev) {
802
+ self.onKeyDown(ev);
803
+ };
804
+
805
+ this.setPrompt(">>");
806
+
807
+ REPL.all[this.index] = this;
808
+ }
809
+
810
+ REPL.prototype.focus = function() {
811
+ this.inputElement.focus();
812
+ };
813
+
814
+ REPL.prototype.setPrompt = function(prompt) {
815
+ this._prompt = prompt;
816
+ this.promptElement.innerHTML = escapeHTML(prompt);
817
+ };
818
+
819
+ REPL.prototype.getInput = function() {
820
+ return this.inputElement.value;
821
+ };
822
+
823
+ REPL.prototype.setInput = function(text) {
824
+ this.inputElement.value = text;
825
+
826
+ if(this.inputElement.setSelectionRange) {
827
+ // set cursor to end of input
828
+ this.inputElement.setSelectionRange(text.length, text.length);
829
+ }
830
+ };
831
+
832
+ REPL.prototype.writeRawOutput = function(output) {
833
+ this.outputElement.innerHTML += output;
834
+ this.outputElement.scrollTop = this.outputElement.scrollHeight;
835
+ };
836
+
837
+ REPL.prototype.writeOutput = function(output) {
838
+ this.writeRawOutput(escapeHTML(output));
839
+ };
840
+
841
+ REPL.prototype.sendInput = function(line) {
842
+ var self = this;
843
+ apiCall("eval", { "index": this.index, source: line }, function(response) {
844
+ if(response.error) {
845
+ self.writeOutput(response.error + "\n");
846
+ }
847
+ self.writeOutput(self._prompt + " ");
848
+ self.writeRawOutput(response.highlighted_input + "\n");
849
+ self.writeOutput(response.result);
850
+ self.setPrompt(response.prompt);
851
+ self.setInput(response.prefilled_input);
852
+ });
853
+ };
854
+
855
+ REPL.prototype.onEnterKey = function() {
856
+ var text = this.getInput();
857
+ if(text != "" && text !== undefined) {
858
+ var previousCommands = JSON.parse(localStorage.getItem("better_errors_previous_commands"));
859
+ this.previousCommandOffset = previousCommands.push(text);
860
+ if(previousCommands.length > 100) {
861
+ previousCommands.splice(0, 1);
862
+ this.previousCommandOffset -= 1;
863
+ }
864
+ localStorage.setItem("better_errors_previous_commands", JSON.stringify(previousCommands));
865
+ }
866
+ this.setInput("");
867
+ this.sendInput(text);
868
+ };
869
+
870
+ REPL.prototype.onNavigateHistory = function(direction) {
871
+ this.previousCommandOffset += direction;
872
+ var previousCommands = JSON.parse(localStorage.getItem("better_errors_previous_commands"));
873
+
874
+ if(this.previousCommandOffset < 0) {
875
+ this.previousCommandOffset = -1;
876
+ this.setInput("");
877
+ return;
878
+ }
879
+
880
+ if(this.previousCommandOffset >= previousCommands.length) {
881
+ this.previousCommandOffset = previousCommands.length;
882
+ this.setInput("");
883
+ return;
884
+ }
885
+
886
+ this.setInput(previousCommands[this.previousCommandOffset]);
887
+ };
888
+
889
+ REPL.prototype.onKeyDown = function(ev) {
890
+ if(ev.keyCode == 13) {
891
+ this.onEnterKey();
892
+ } else if(ev.keyCode == 38 || (ev.ctrlKey && ev.keyCode == 80)) {
893
+ // the user pressed the up arrow or Ctrl-P
894
+ this.onNavigateHistory(-1);
895
+ ev.preventDefault();
896
+ return false;
897
+ } else if(ev.keyCode == 40 || (ev.ctrlKey && ev.keyCode == 78)) {
898
+ // the user pressed the down arrow or Ctrl-N
899
+ this.onNavigateHistory(1);
900
+ ev.preventDefault();
901
+ return false;
902
+ }
903
+ };
904
+
905
+ function switchTo(el) {
906
+ if(previousFrameInfo) previousFrameInfo.style.display = "none";
907
+ previousFrameInfo = el;
908
+
909
+ el.style.display = "block";
910
+
911
+ var replInput = el.querySelector('.be-console input');
912
+ if (replInput) replInput.focus();
913
+ }
914
+
915
+ function selectFrameInfo(index) {
916
+ var el = allFrameInfos[index];
917
+ if(el) {
918
+ if (el.loaded) {
919
+ return switchTo(el);
920
+ }
921
+
922
+ apiCall("variables", { "index": index }, function(response) {
923
+ el.loaded = true;
924
+ if(response.error) {
925
+ el.innerHTML = "<h2 class='error'>" + escapeHTML(response.error) + "</h2>";
926
+ if(response.explanation) {
927
+ el.innerHTML += "<p class='explanation'>" + escapeHTML(response.explanation) + "</p>";
928
+ }
929
+ el.innerHTML += "<p><a target='_new' href='https://github.com/BetterErrors/better_errors'>More about Better Errors</a></p>";
930
+ } else {
931
+ el.innerHTML = response.html;
932
+
933
+ var repl = el.querySelector(".be-repl .be-console");
934
+ if(repl) {
935
+ new REPL(index).install(repl);
936
+ }
937
+ }
938
+ switchTo(el);
939
+ });
940
+ }
941
+ }
942
+
943
+ for(var i = 0; i < allFrames.length; i++) {
944
+ (function(i, el) {
945
+ var el = allFrames[i];
946
+ el.onclick = function() {
947
+ if(previousFrame) {
948
+ previousFrame.className = "";
949
+ }
950
+ el.className = "selected";
951
+ previousFrame = el;
952
+
953
+ selectFrameInfo(el.attributes["data-index"].value);
954
+ };
955
+ })(i);
956
+ }
957
+
958
+ // Click the first application frame
959
+ (
960
+ document.querySelector(".frames li.application") ||
961
+ document.querySelector(".frames li")
962
+ ).onclick();
963
+
964
+ // This is the second query performed for frames; maybe the 'allFrames' list
965
+ // currently used and this list can be better used to avoid the repetition:
966
+ var applicationFramesCount = document.querySelectorAll(
967
+ "ul.frames li[data-context=application]"
968
+ ).length;
969
+
970
+ var applicationFramesButtonIsInstalled = false;
971
+ var applicationFramesButton = document.getElementById("application_frames");
972
+ var allFramesButton = document.getElementById("all_frames");
973
+
974
+ // The application frames button only needs to be bound if
975
+ // there are actually any application frames to look at.
976
+ var installApplicationFramesButton = function() {
977
+ applicationFramesButton.onclick = function() {
978
+ allFramesButton.className = "";
979
+ applicationFramesButton.className = "selected";
980
+ for(var i = 0; i < allFrames.length; i++) {
981
+ if(allFrames[i].attributes["data-context"].value == "application") {
982
+ allFrames[i].style.display = "block";
983
+ } else {
984
+ allFrames[i].style.display = "none";
985
+ }
986
+ }
987
+ return false;
988
+ };
989
+
990
+ applicationFramesButtonIsInstalled = true;
991
+ }
992
+
993
+ allFramesButton.onclick = function() {
994
+ if(applicationFramesButtonIsInstalled) {
995
+ applicationFramesButton.className = "";
996
+ }
997
+
998
+ allFramesButton.className = "selected";
999
+ for(var i = 0; i < allFrames.length; i++) {
1000
+ allFrames[i].style.display = "block";
1001
+ }
1002
+ return false;
1003
+ };
1004
+
1005
+ // If there are no application frames, select the 'All Frames'
1006
+ // tab by default.
1007
+ if(applicationFramesCount > 0) {
1008
+ installApplicationFramesButton();
1009
+ applicationFramesButton.onclick();
1010
+ } else {
1011
+ applicationFramesButton.className = "disabled";
1012
+ applicationFramesButton.title = "No application frames available";
1013
+ allFramesButton.onclick();
1014
+ }
1015
+ })();