card 1.20.0 → 1.20.1

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/card.gemspec +1 -1
  4. data/lib/card.rb +7 -3
  5. data/lib/card/act_manager/stage_director.rb +22 -10
  6. data/lib/card/cache/persistent.rb +15 -9
  7. data/lib/card/format/nest.rb +12 -5
  8. data/lib/card/format/nest/fetch.rb +2 -1
  9. data/lib/card/format/render.rb +5 -2
  10. data/lib/card/set/event.rb +7 -1
  11. data/lib/card/subcards.rb +3 -3
  12. data/lib/card/view/cache.rb +1 -1
  13. data/lib/card/view/fetch.rb +17 -5
  14. data/lib/card/view/options.rb +52 -26
  15. data/lib/cardio.rb +2 -1
  16. data/mod/account/spec/set/right/account_spec.rb +17 -3
  17. data/mod/admin/set/self/admin.rb +0 -2
  18. data/mod/admin/spec/set/self/admin_spec.rb +14 -11
  19. data/mod/bootstrap/set/all/bootstrap/helper.rb +27 -29
  20. data/mod/carrierwave/set/type/file.rb +1 -1
  21. data/mod/carrierwave/set/type/image.rb +18 -6
  22. data/mod/core/set/all/collection.rb +33 -7
  23. data/mod/core/set/all/fetch.rb +16 -3
  24. data/mod/core/set/all/permissions.rb +6 -12
  25. data/mod/core/set/all/trash.rb +3 -1
  26. data/mod/core/spec/set/all/collection_spec.rb +9 -8
  27. data/mod/email/set/all/notify.rb +27 -17
  28. data/mod/email/set/right/follow.rb +49 -36
  29. data/mod/email/set/type/email_template.rb +25 -69
  30. data/mod/email/set/type/email_template/email_config.rb +63 -0
  31. data/mod/email/set/type_plus_right/user/follow.rb +3 -3
  32. data/mod/machines/lib/stylesheets/style_cards.scss +292 -0
  33. data/mod/pointer/set/abstract/01_pointer.rb +8 -8
  34. data/mod/pointer/set/abstract/01_pointer/edit.rb +6 -2
  35. data/mod/prosemirror_editor/lib/javascript/script_prosemirror.js +12283 -11605
  36. data/mod/prosemirror_editor/lib/javascript/script_prosemirror_config.js.coffee +1 -1
  37. data/mod/standard/set/abstract/01_search_params.rb +1 -1
  38. data/mod/standard/set/abstract/search/paging.rb +4 -4
  39. data/mod/standard/set/all/comment.rb +67 -47
  40. data/mod/standard/set/all/links.rb +2 -2
  41. data/mod/standard/set/all/rich_html/content.rb +1 -1
  42. data/mod/standard/set/all/rich_html/editing.rb +3 -2
  43. data/mod/standard/set/all/rich_html/form.rb +21 -12
  44. data/mod/standard/set/all/rich_html/header.rb +9 -0
  45. data/mod/standard/set/all/rich_html/menu.rb +16 -12
  46. data/mod/standard/set/all/rich_html/toolbar.rb +140 -130
  47. data/mod/standard/set/all/rich_html/wrapper.rb +11 -1
  48. data/mod/standard/set/rstar/rules_editor.rb +2 -34
  49. data/mod/standard/set/self/search.rb +1 -1
  50. data/mod/standard/set/type/set.rb +4 -4
  51. data/mod/standard/spec/set/type/email_template/email_config_spec.rb +218 -0
  52. data/mod/standard/spec/set/type/email_template_spec.rb +3 -185
  53. data/spec/lib/card/cache_spec.rb +0 -1
  54. data/spec/lib/card/format/render_spec.rb +19 -0
  55. data/spec/lib/card/stage_director_spec.rb +1 -1
  56. data/tmpsets/set/mod001-core/all/actify.rb +5 -6
  57. data/tmpsets/set/mod001-core/all/fetch.rb +14 -12
  58. data/tmpsets/set/mod001-core/all/name.rb +1 -1
  59. data/tmpsets/set/mod001-core/all/permissions.rb +12 -22
  60. data/tmpsets/set/mod001-core/all/tracked_attributes.rb +76 -0
  61. data/tmpsets/set/mod001-core/all/utils.rb +40 -3
  62. data/tmpsets/set/mod002-history/all/history.rb +1 -2
  63. data/tmpsets/set/mod008-solid_cache/abstract/solid_cache.rb +1 -1
  64. data/tmpsets/set/mod013-carrierwave/abstract/attachment.rb +282 -0
  65. data/tmpsets/set/mod013-carrierwave/type/file.rb +155 -0
  66. data/tmpsets/set/mod013-carrierwave/type/image.rb +96 -0
  67. data/tmpsets/set/mod014-admin/self/admin.rb +113 -0
  68. data/tmpsets/set/mod014-admin/self/admin_info.rb +110 -0
  69. data/tmpsets/set/mod014-admin/self/version.rb +15 -0
  70. data/tmpsets/set/mod015-developer/all/event_viz.rb +59 -0
  71. data/tmpsets/set/mod015-developer/all/view_viz.rb +30 -0
  72. data/tmpsets/set/mod015-developer/right/debug.rb +96 -0
  73. 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
- it "send multipart email" do
174
- skip
175
- # pending
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
@@ -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
- it "triggers deleting old revisions (with right params)" do
27
- Card::Auth.as_bot do
28
- a = Card["A"]
29
- a.update_attributes! content: "a new day"
30
- a.update_attributes! content: "another day"
31
- expect(a.actions.count).to eq(3)
32
- Card::Env.params[:task] = :delete_old_revisions
33
- @admin.update_attributes({})
34
- expect(a.actions.count).to eq(1)
35
- end
36
- end
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, "", "aria-hidden" => true,
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
- item_list =
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 do |item, 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 do |key, item|
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
- <ul class="dropdown-menu #{extra_css_class}" role="menu">
43
- #{item_list}
44
- </ul>
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 button, args={}
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
- button,
59
- button_tag(situation: args[:situation],
60
- class: "dropdown-toggle", "data-toggle" => "dropdown",
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.url
24
+ card.image
21
25
  else
22
- card.image.versions[voo.size.to_sym].url
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
- end
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 unless card.new_card? && !card.preliminary_upload?
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? action, view_type
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
- context_card.format(format_args).process_content(
123
- format(format_args)._render_raw(view_args), view_args
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 (voo && items = voo.items)
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 |name, options|
219
- options ||= Card.quick_fetch(name).name
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(name), options]
247
+ [card.cardname.field(name_or_card), options]
222
248
  end
223
249
  end
224
250
 
@@ -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 << cache_key
338
+ @view_cache_keys << new_key if new_key
339
+ append_missing_view_cache_keys
334
340
  @view_cache_keys.uniq!
335
- hard_write_view_cache_keys
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
- return unless @view_cache_keys
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? task
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
 
@@ -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, :prepare_to_validate, on: :delete do
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
- context_card = Card["A"] # refers to 'Z'
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
- context_card = Card["A"] # refers to 'Z'
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
@@ -170,31 +170,41 @@ format do
170
170
  #{render_list_of_changes(args)})
171
171
  end
172
172
 
173
- view :followed, perms: :none, closed: true do |args|
174
- if args[:followed_set] &&
175
- (set_card = Card.fetch(args[:followed_set])) &&
176
- args[:follow_option] &&
177
- (option_card = Card.fetch(args[:follow_option]))
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 |args|
185
- args[:follower] || "follower"
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 |args|
189
- if args[:followed_set] && (set_card = Card.fetch(args[:followed_set])) &&
190
- args[:follow_option] && args[:follower]
191
- rule_name = set_card.follow_rule_name args[:follower]
192
- target_name = "#{args[:follower]}+#{Card[:follow].name}"
193
- update_path = page_path target_name,
194
- action: :update,
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