card 1.20.0 → 1.20.1

Sign up to get free protection for your applications and to get access to all the features.
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