card-mod-format 0.11.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 (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
+ })();