card 1.20.0 → 1.20.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/card.gemspec +1 -1
- data/lib/card.rb +7 -3
- data/lib/card/act_manager/stage_director.rb +22 -10
- data/lib/card/cache/persistent.rb +15 -9
- data/lib/card/format/nest.rb +12 -5
- data/lib/card/format/nest/fetch.rb +2 -1
- data/lib/card/format/render.rb +5 -2
- data/lib/card/set/event.rb +7 -1
- data/lib/card/subcards.rb +3 -3
- data/lib/card/view/cache.rb +1 -1
- data/lib/card/view/fetch.rb +17 -5
- data/lib/card/view/options.rb +52 -26
- data/lib/cardio.rb +2 -1
- data/mod/account/spec/set/right/account_spec.rb +17 -3
- data/mod/admin/set/self/admin.rb +0 -2
- data/mod/admin/spec/set/self/admin_spec.rb +14 -11
- data/mod/bootstrap/set/all/bootstrap/helper.rb +27 -29
- data/mod/carrierwave/set/type/file.rb +1 -1
- data/mod/carrierwave/set/type/image.rb +18 -6
- data/mod/core/set/all/collection.rb +33 -7
- data/mod/core/set/all/fetch.rb +16 -3
- data/mod/core/set/all/permissions.rb +6 -12
- data/mod/core/set/all/trash.rb +3 -1
- data/mod/core/spec/set/all/collection_spec.rb +9 -8
- data/mod/email/set/all/notify.rb +27 -17
- data/mod/email/set/right/follow.rb +49 -36
- data/mod/email/set/type/email_template.rb +25 -69
- data/mod/email/set/type/email_template/email_config.rb +63 -0
- data/mod/email/set/type_plus_right/user/follow.rb +3 -3
- data/mod/machines/lib/stylesheets/style_cards.scss +292 -0
- data/mod/pointer/set/abstract/01_pointer.rb +8 -8
- data/mod/pointer/set/abstract/01_pointer/edit.rb +6 -2
- data/mod/prosemirror_editor/lib/javascript/script_prosemirror.js +12283 -11605
- data/mod/prosemirror_editor/lib/javascript/script_prosemirror_config.js.coffee +1 -1
- data/mod/standard/set/abstract/01_search_params.rb +1 -1
- data/mod/standard/set/abstract/search/paging.rb +4 -4
- data/mod/standard/set/all/comment.rb +67 -47
- data/mod/standard/set/all/links.rb +2 -2
- data/mod/standard/set/all/rich_html/content.rb +1 -1
- data/mod/standard/set/all/rich_html/editing.rb +3 -2
- data/mod/standard/set/all/rich_html/form.rb +21 -12
- data/mod/standard/set/all/rich_html/header.rb +9 -0
- data/mod/standard/set/all/rich_html/menu.rb +16 -12
- data/mod/standard/set/all/rich_html/toolbar.rb +140 -130
- data/mod/standard/set/all/rich_html/wrapper.rb +11 -1
- data/mod/standard/set/rstar/rules_editor.rb +2 -34
- data/mod/standard/set/self/search.rb +1 -1
- data/mod/standard/set/type/set.rb +4 -4
- data/mod/standard/spec/set/type/email_template/email_config_spec.rb +218 -0
- data/mod/standard/spec/set/type/email_template_spec.rb +3 -185
- data/spec/lib/card/cache_spec.rb +0 -1
- data/spec/lib/card/format/render_spec.rb +19 -0
- data/spec/lib/card/stage_director_spec.rb +1 -1
- data/tmpsets/set/mod001-core/all/actify.rb +5 -6
- data/tmpsets/set/mod001-core/all/fetch.rb +14 -12
- data/tmpsets/set/mod001-core/all/name.rb +1 -1
- data/tmpsets/set/mod001-core/all/permissions.rb +12 -22
- data/tmpsets/set/mod001-core/all/tracked_attributes.rb +76 -0
- data/tmpsets/set/mod001-core/all/utils.rb +40 -3
- data/tmpsets/set/mod002-history/all/history.rb +1 -2
- data/tmpsets/set/mod008-solid_cache/abstract/solid_cache.rb +1 -1
- data/tmpsets/set/mod013-carrierwave/abstract/attachment.rb +282 -0
- data/tmpsets/set/mod013-carrierwave/type/file.rb +155 -0
- data/tmpsets/set/mod013-carrierwave/type/image.rb +96 -0
- data/tmpsets/set/mod014-admin/self/admin.rb +113 -0
- data/tmpsets/set/mod014-admin/self/admin_info.rb +110 -0
- data/tmpsets/set/mod014-admin/self/version.rb +15 -0
- data/tmpsets/set/mod015-developer/all/event_viz.rb +59 -0
- data/tmpsets/set/mod015-developer/all/view_viz.rb +30 -0
- data/tmpsets/set/mod015-developer/right/debug.rb +96 -0
- metadata +15 -2
@@ -170,9 +170,23 @@ describe Card::Set::Right::Account do
|
|
170
170
|
end
|
171
171
|
|
172
172
|
describe "#send_change_notice" do
|
173
|
-
|
174
|
-
|
175
|
-
|
173
|
+
subject(:mail) do
|
174
|
+
Card[:follower_notification_email].format.render_mail(
|
175
|
+
context: Card.fetch("A", look_in_trash: true),
|
176
|
+
to: "joe@user.com",
|
177
|
+
follower: Card["Joe User"],
|
178
|
+
followed_set: Card[:all],
|
179
|
+
follow_option: Card[:always]
|
180
|
+
)
|
181
|
+
end
|
182
|
+
|
183
|
+
it "works for deleted card" do
|
184
|
+
delete "A"
|
185
|
+
expect(mail.subject).to eq 'Joe User deleted "A"'
|
186
|
+
end
|
187
|
+
|
188
|
+
it "sends multipart email" do
|
189
|
+
expect(mail.content_type).to include("multipart/alternative")
|
176
190
|
end
|
177
191
|
|
178
192
|
context "denied access" do
|
data/mod/admin/set/self/admin.rb
CHANGED
@@ -8,8 +8,6 @@ event :admin_tasks, :initialize, on: :update do
|
|
8
8
|
case task.to_sym
|
9
9
|
when :clear_cache then Card::Cache.reset_all
|
10
10
|
when :repair_references then Card::Reference.repair_all
|
11
|
-
# when :clear_view_cache then Card::View.reset
|
12
|
-
when :delete_old_revisions then Card::Action.delete_old
|
13
11
|
when :repair_permissions then Card.repair_all_permissions
|
14
12
|
when :clear_solid_cache then Card.clear_solid_cache
|
15
13
|
when :clear_machine_cache then Card.reset_all_machines
|
@@ -23,17 +23,20 @@ describe Card::Set::Self::Admin do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
# NOTE: I removed this functionality for now, because I don't think we
|
27
|
+
# should have web access to admin functions that can incur actual data loss.
|
28
|
+
|
29
|
+
# it "triggers deleting old revisions (with right params)" do
|
30
|
+
# Card::Auth.as_bot do
|
31
|
+
# a = Card["A"]
|
32
|
+
# a.update_attributes! content: "a new day"
|
33
|
+
# a.update_attributes! content: "another day"
|
34
|
+
# expect(a.actions.count).to eq(3)
|
35
|
+
# Card::Env.params[:task] = :delete_old_revisions
|
36
|
+
# @admin.update_attributes({})
|
37
|
+
# expect(a.actions.count).to eq(1)
|
38
|
+
# end
|
39
|
+
# end
|
37
40
|
|
38
41
|
# it 'is trigger reference repair' do
|
39
42
|
# Card::Auth.as_bot do
|
@@ -1,6 +1,7 @@
|
|
1
1
|
format :html do
|
2
2
|
def glyphicon icon_type, extra_class=""
|
3
|
-
wrap_with :span, "",
|
3
|
+
wrap_with :span, "",
|
4
|
+
"aria-hidden" => true,
|
4
5
|
class: "glyphicon glyphicon-#{icon_type} #{extra_class}"
|
5
6
|
end
|
6
7
|
|
@@ -25,50 +26,47 @@ format :html do
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def dropdown_list items, extra_css_class=nil, active=nil
|
28
|
-
|
29
|
+
wrap_with :ul, class: "dropdown-menu #{extra_css_class}", role: "menu" do
|
29
30
|
case items
|
30
31
|
when Array
|
31
|
-
items.map.with_index
|
32
|
-
"<li #{'class=\'active\'' if index == active}>#{item}</li>" if item
|
33
|
-
end.compact.join "\n"
|
32
|
+
items.map.with_index { |item, i| dropdown_list_item item, i, active }
|
34
33
|
when Hash
|
35
|
-
items.map
|
36
|
-
"<li #{'class=\'active\'' if key == active}>#{item}</li>" if item
|
37
|
-
end.compact.join "\n"
|
34
|
+
items.map { |key, item| dropdown_list_item item, key, active }
|
38
35
|
else
|
39
|
-
items
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
[items]
|
37
|
+
end.compact.join "\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def dropdown_list_item item, active_test, active
|
42
|
+
return unless item
|
43
|
+
"<li #{'class=\'active\'' if active_test == active}>#{item}</li>"
|
46
44
|
end
|
47
45
|
|
48
46
|
def separator
|
49
47
|
'<li role="separator" class="divider"></li>'
|
50
48
|
end
|
51
49
|
|
52
|
-
def split_button
|
53
|
-
items = yield
|
54
|
-
args[:situation] ||= "primary"
|
55
|
-
|
50
|
+
def split_button main_button, active_item
|
56
51
|
wrap_with :div, class: "btn-group" do
|
57
52
|
[
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
"aria-haspopup" => "true", "aria-expanded" => "false") do
|
62
|
-
<<-HTML
|
63
|
-
<span class="caret"></span>
|
64
|
-
<span class="sr-only">Toggle Dropdown</span>
|
65
|
-
HTML
|
66
|
-
end,
|
67
|
-
dropdown_list(items, nil, args[:active_item])
|
53
|
+
main_button,
|
54
|
+
split_button_toggle,
|
55
|
+
dropdown_list(yield, nil, active_item)
|
68
56
|
]
|
69
57
|
end
|
70
58
|
end
|
71
59
|
|
60
|
+
def split_button_toggle
|
61
|
+
button_tag(situation: "primary",
|
62
|
+
class: "dropdown-toggle",
|
63
|
+
"data-toggle" => "dropdown",
|
64
|
+
"aria-haspopup" => "true",
|
65
|
+
"aria-expanded" => "false") do
|
66
|
+
'<span class="caret"></span><span class="sr-only">Toggle Dropdown</span>'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
72
70
|
def list_group content_or_options=nil, options={}
|
73
71
|
options = content_or_options if block_given?
|
74
72
|
content = block_given? ? yield : content_or_options
|
@@ -93,7 +93,7 @@ format :html do
|
|
93
93
|
""
|
94
94
|
end
|
95
95
|
|
96
|
-
view :preview_editor, tags: :unknown_ok do |args|
|
96
|
+
view :preview_editor, tags: :unknown_ok, cache: :never do |args|
|
97
97
|
cached_upload_card_name = Card::Env.params[:attachment_upload]
|
98
98
|
cached_upload_card_name.gsub!(/\[\w+\]$/, "[action_id_of_cached_upload]")
|
99
99
|
<<-HTML
|
@@ -16,10 +16,14 @@ format do
|
|
16
16
|
|
17
17
|
def source_url
|
18
18
|
return card.raw_content if card.web?
|
19
|
+
selected_version.url
|
20
|
+
end
|
21
|
+
|
22
|
+
def selected_version
|
19
23
|
if voo.size == :original
|
20
|
-
card.image
|
24
|
+
card.image
|
21
25
|
else
|
22
|
-
card.image.versions[voo.size.to_sym]
|
26
|
+
card.image.versions[voo.size.to_sym]
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
@@ -34,12 +38,11 @@ format do
|
|
34
38
|
when voo.size then voo.size.to_sym
|
35
39
|
when main? then :large
|
36
40
|
else :medium
|
37
|
-
|
41
|
+
end
|
38
42
|
voo.size = :original if voo.size == :full
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
42
|
-
|
43
46
|
format :html do
|
44
47
|
include File::HtmlFormat
|
45
48
|
|
@@ -54,7 +57,7 @@ format :html do
|
|
54
57
|
end
|
55
58
|
|
56
59
|
def preview
|
57
|
-
return
|
60
|
+
return if card.new_card? && !card.preliminary_upload?
|
58
61
|
voo.size = :medium
|
59
62
|
wrap_with :div, class: "attachment-preview",
|
60
63
|
id: "#{card.attachment.filename}-preview" do
|
@@ -62,7 +65,7 @@ format :html do
|
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
|
-
def show_action_content_toggle?
|
68
|
+
def show_action_content_toggle? _action, _view_type
|
66
69
|
true
|
67
70
|
end
|
68
71
|
|
@@ -87,6 +90,15 @@ format :html do
|
|
87
90
|
end
|
88
91
|
end
|
89
92
|
|
93
|
+
format :email_html do
|
94
|
+
view :core do
|
95
|
+
url_generator = voo.closest_live_option(:inline_attachment_url)
|
96
|
+
path = selected_version.path
|
97
|
+
return _render_source unless url_generator && ::File.exist?(path)
|
98
|
+
image_tag url_generator.call(path)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
90
102
|
format :css do
|
91
103
|
view :core do
|
92
104
|
render_source
|
@@ -79,6 +79,18 @@ def insert_item index, name
|
|
79
79
|
self.content = new_names.join "\n"
|
80
80
|
end
|
81
81
|
|
82
|
+
def add_id id
|
83
|
+
add_item "~#{id}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def drop_id id
|
87
|
+
drop_item "~#{id}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def insert_id index, id
|
91
|
+
insert_item index, "~#{id}"
|
92
|
+
end
|
93
|
+
|
82
94
|
def extended_item_cards context=nil
|
83
95
|
context = (context ? context.cardname : cardname)
|
84
96
|
args = { limit: "" }
|
@@ -118,10 +130,23 @@ def extended_list context=nil
|
|
118
130
|
# a collection
|
119
131
|
end
|
120
132
|
|
133
|
+
def context_card
|
134
|
+
@context_card || self
|
135
|
+
end
|
136
|
+
|
137
|
+
def with_context card
|
138
|
+
old_context = @context_card
|
139
|
+
@context_card = card
|
140
|
+
result = yield
|
141
|
+
@context_card = old_context
|
142
|
+
result
|
143
|
+
end
|
144
|
+
|
121
145
|
def contextual_content context_card, format_args={}, view_args={}
|
122
|
-
|
123
|
-
|
124
|
-
|
146
|
+
view = view_args.delete(:view) || :core
|
147
|
+
with_context context_card do
|
148
|
+
format(format_args).render view, view_args
|
149
|
+
end
|
125
150
|
end
|
126
151
|
|
127
152
|
def each_chunk opts={}
|
@@ -171,7 +196,7 @@ format do
|
|
171
196
|
end
|
172
197
|
|
173
198
|
def voo_items_view
|
174
|
-
return unless
|
199
|
+
return unless voo && (items = voo.items)
|
175
200
|
items[:view]
|
176
201
|
end
|
177
202
|
|
@@ -215,10 +240,11 @@ format do
|
|
215
240
|
end
|
216
241
|
|
217
242
|
def normalized_edit_fields
|
218
|
-
edit_fields.map do |
|
219
|
-
options
|
243
|
+
edit_fields.map do |name_or_card, options|
|
244
|
+
next [name_or_card, options || {}] if name_or_card.is_a?(Card)
|
245
|
+
options ||= Card.fetch_name name_or_card
|
220
246
|
options = { title: options } if options.is_a?(String)
|
221
|
-
[card.cardname.field(
|
247
|
+
[card.cardname.field(name_or_card), options]
|
222
248
|
end
|
223
249
|
end
|
224
250
|
|
data/mod/core/set/all/fetch.rb
CHANGED
@@ -329,19 +329,32 @@ def cache_class_from_type cache_type
|
|
329
329
|
end
|
330
330
|
|
331
331
|
def register_view_cache_key cache_key
|
332
|
+
view_cache_keys cache_key
|
333
|
+
hard_write_view_cache_keys
|
334
|
+
end
|
335
|
+
|
336
|
+
def view_cache_keys new_key=nil
|
332
337
|
@view_cache_keys ||= []
|
333
|
-
@view_cache_keys <<
|
338
|
+
@view_cache_keys << new_key if new_key
|
339
|
+
append_missing_view_cache_keys
|
334
340
|
@view_cache_keys.uniq!
|
335
|
-
|
341
|
+
end
|
342
|
+
|
343
|
+
def append_missing_view_cache_keys
|
344
|
+
return unless Card.cache.hard
|
345
|
+
@view_cache_keys +=
|
346
|
+
(Card.cache.hard.read_attribute(key, :view_cache_keys) || [])
|
336
347
|
end
|
337
348
|
|
338
349
|
def hard_write_view_cache_keys
|
350
|
+
# puts "WRITE VIEW CACHE KEYS (#{name}): #{view_cache_keys}"
|
339
351
|
return unless Card.cache.hard
|
340
352
|
Card.cache.hard.write_attribute key, :view_cache_keys, @view_cache_keys
|
341
353
|
end
|
342
354
|
|
343
355
|
def expire_views
|
344
|
-
|
356
|
+
# puts "EXPIRE VIEW CACHE (#{name}): #{view_cache_keys}"
|
357
|
+
return unless view_cache_keys.present?
|
345
358
|
Array.wrap(@view_cache_keys).each do |view_cache_key|
|
346
359
|
Card::View.cache.delete view_cache_key
|
347
360
|
end
|
@@ -224,26 +224,20 @@ def add_to_read_rule_update_queue updates
|
|
224
224
|
end
|
225
225
|
|
226
226
|
event :check_permissions, :validate do
|
227
|
-
task =
|
228
|
-
if @action != :delete && comment # will be obviated by new comment handling
|
229
|
-
:comment
|
230
|
-
else
|
231
|
-
@action
|
232
|
-
end
|
233
227
|
track_permission_errors do
|
234
|
-
ok?
|
228
|
+
ok? action_for_permission_check
|
235
229
|
end
|
236
230
|
end
|
237
231
|
|
232
|
+
def action_for_permission_check
|
233
|
+
commenting? ? :comment : @action
|
234
|
+
end
|
235
|
+
|
238
236
|
def track_permission_errors
|
239
237
|
@permission_errors = []
|
240
238
|
result = yield
|
241
|
-
|
242
|
-
@permission_errors.each do |message|
|
243
|
-
errors.add :permission_denied, message
|
244
|
-
end
|
239
|
+
@permission_errors.each { |msg| errors.add :permission_denied, msg }
|
245
240
|
@permission_errors = nil
|
246
|
-
|
247
241
|
result
|
248
242
|
end
|
249
243
|
|
data/mod/core/set/all/trash.rb
CHANGED
@@ -15,6 +15,7 @@ module ClassMethods
|
|
15
15
|
Card.delete_trashed_files
|
16
16
|
Card.where(trash: true).delete_all
|
17
17
|
Card::Action.delete_cardless
|
18
|
+
Card::Change.delete_actionless
|
18
19
|
Card::Reference.unmap_if_referee_missing
|
19
20
|
Card::Reference.delete_if_referer_missing
|
20
21
|
end
|
@@ -94,7 +95,8 @@ event :validate_delete, :validate, on: :delete do
|
|
94
95
|
end
|
95
96
|
end
|
96
97
|
|
97
|
-
event :validate_delete_children, :
|
98
|
+
event :validate_delete_children, after: :validate_delete, on: :delete do
|
99
|
+
return if errors.any?
|
98
100
|
children.each do |child|
|
99
101
|
child.trash = true
|
100
102
|
add_subcard child
|
@@ -54,22 +54,23 @@ describe Card::Set::All::Collection do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
describe "#contextual_content" do
|
57
|
+
let(:context_card) { Card["A"] } # refers to 'Z'
|
57
58
|
it "processes nests relative to context card" do
|
58
|
-
|
59
|
-
c = Card.new(name: "foo", content: "{{_self+B|core}}")
|
59
|
+
c = create "foo", content: "{{_self+B|core}}"
|
60
60
|
expect(c.contextual_content(context_card)).to eq("AlphaBeta")
|
61
61
|
end
|
62
62
|
|
63
63
|
# why the heck is this good? -efm
|
64
64
|
it "returns content even when context card is hard templated" do
|
65
|
-
|
66
|
-
|
67
|
-
Card::Auth.as_bot do
|
68
|
-
Card.create! name: "A+*self+*structure", content: "Banana"
|
69
|
-
end
|
70
|
-
c = Card.new name: "foo", content: "{{_self+B|core}}"
|
65
|
+
create "A+*self+*structure", content: "Banana"
|
66
|
+
c = create "foo", content: "{{_self+B|core}}"
|
71
67
|
expect(c.contextual_content(context_card)).to eq("AlphaBeta")
|
72
68
|
end
|
69
|
+
|
70
|
+
it "it doesn't use chunk list of context card" do
|
71
|
+
c = create "foo", content: "test@email.com", type: "HTML"
|
72
|
+
expect(c.contextual_content(context_card)).not_to have_tag "a"
|
73
|
+
end
|
73
74
|
end
|
74
75
|
|
75
76
|
describe "tabs view" do
|
data/mod/email/set/all/notify.rb
CHANGED
@@ -170,31 +170,41 @@ format do
|
|
170
170
|
#{render_list_of_changes(args)})
|
171
171
|
end
|
172
172
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
173
|
+
def followed_set_card
|
174
|
+
(set_name = voo.closest_live_option(:followed_set)) && Card.fetch(set_name)
|
175
|
+
end
|
176
|
+
|
177
|
+
def follow_option_card
|
178
|
+
(option_name = voo.closest_live_option(:follow_option)) &&
|
179
|
+
Card.fetch(option_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
view :followed, perms: :none, closed: true do
|
183
|
+
if (set_card = followed_set_card) &&
|
184
|
+
(option_card = follow_option_card)
|
178
185
|
option_card.description set_card
|
179
186
|
else
|
180
187
|
"followed card"
|
181
188
|
end
|
182
189
|
end
|
183
190
|
|
184
|
-
view :follower, perms: :none, closed: true do
|
185
|
-
|
191
|
+
view :follower, perms: :none, closed: true do
|
192
|
+
voo.closest_live_option(:follower) || "follower"
|
193
|
+
end
|
194
|
+
|
195
|
+
def live_follow_rule_name
|
196
|
+
return unless (set_card = followed_set_card) &&
|
197
|
+
voo.closest_live_option(:follower)
|
198
|
+
set_card.follow_rule_name voo.closest_live_option(:follower)
|
186
199
|
end
|
187
200
|
|
188
|
-
view :unfollow_url, perms: :none, closed: true do |
|
189
|
-
if
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
card: { subcards: {
|
196
|
-
rule_name => Card[:never].name
|
197
|
-
} }
|
201
|
+
view :unfollow_url, perms: :none, closed: true do |_args|
|
202
|
+
if (rule_name = live_follow_rule_name)
|
203
|
+
target_name = "#{voo.closest_live_option :follower}+#{Card[:follow].name}"
|
204
|
+
update_path = page_path target_name, action: :update,
|
205
|
+
card: { subcards: {
|
206
|
+
rule_name => Card[:never].name
|
207
|
+
} }
|
198
208
|
card_url update_path # absolutize path
|
199
209
|
end
|
200
210
|
end
|