card 1.19.4 → 1.19.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/config/initializers/01_core_extensions/module.rb +4 -0
  4. data/lib/card.rb +2 -2
  5. data/lib/card/content/diff/lcs.rb +1 -1
  6. data/lib/card/model/save_helper.rb +100 -25
  7. data/lib/card/name.rb +8 -0
  8. data/lib/card/set/event.rb +22 -42
  9. data/lib/card/set/format.rb +1 -0
  10. data/lib/card/set/inheritance.rb +30 -5
  11. data/lib/card/set/loader.rb +24 -3
  12. data/mod/account/set/self/account_links.rb +45 -21
  13. data/mod/account/set/self/signin.rb +5 -9
  14. data/mod/account/set/type/signup.rb +22 -15
  15. data/mod/account/spec/set/all/account_spec.rb +3 -1
  16. data/mod/admin/set/self/admin.rb +4 -4
  17. data/mod/admin/set/self/admin_info.rb +3 -9
  18. data/mod/admin/set/self/trash.rb +21 -14
  19. data/mod/basic_formats/set/all/base.rb +6 -7
  20. data/mod/bootstrap/set/all/bootstrap/helper.rb +4 -5
  21. data/mod/bootstrap/set/all/bootstrap/tabs.rb +3 -4
  22. data/mod/bootstrap/spec/set/all/bootstrap/form_spec.rb +2 -0
  23. data/mod/carrierwave/set/abstract/attachment/storage_type.rb +3 -0
  24. data/mod/core/chunk/link.rb +3 -3
  25. data/mod/core/chunk/uri.rb +2 -2
  26. data/mod/core/set/all/actify.rb +11 -10
  27. data/mod/core/set/all/permissions.rb +6 -7
  28. data/mod/core/spec/set/all/actify_spec.rb +2 -0
  29. data/mod/developer/set/right/debug.rb +1 -1
  30. data/mod/email/set/all/follow.rb +2 -1
  31. data/mod/email/set/right/follow.rb +19 -22
  32. data/mod/email/set/type_plus_right/user/follow.rb +1 -1
  33. data/mod/email/spec/set/all/notify_spec.rb +17 -14
  34. data/mod/history/set/all/history.rb +35 -38
  35. data/mod/machines/spec/set/abstract/machine_spec.rb +2 -0
  36. data/mod/settings/set/abstract/permission.rb +6 -9
  37. data/mod/settings/set/type/setting.rb +4 -3
  38. data/mod/solid_cache/set/abstract/solid_cache.rb +41 -20
  39. data/mod/solid_cache/spec/set/abstract/solid_cache_spec.rb +3 -1
  40. data/mod/standard/set/all/error.rb +14 -19
  41. data/mod/standard/set/all/links.rb +149 -120
  42. data/mod/standard/set/all/rich_html/content.rb +9 -19
  43. data/mod/standard/set/all/rich_html/header.rb +3 -5
  44. data/mod/standard/set/all/rich_html/menu.rb +25 -23
  45. data/mod/standard/set/all/rich_html/modal.rb +11 -9
  46. data/mod/standard/set/all/rich_html/toolbar.rb +85 -84
  47. data/mod/standard/set/rstar/rules_editor.rb +96 -79
  48. data/mod/standard/set/type/cardtype.rb +2 -1
  49. data/mod/standard/set/type/search_type.rb +2 -1
  50. data/mod/standard/set/type/set.rb +15 -19
  51. data/mod/standard/set/type/uri.rb +2 -2
  52. data/mod/standard/spec/set/all/links_spec.rb +8 -5
  53. data/spec/config/initializers/core_extensions_spec.rb +2 -0
  54. data/spec/lib/card/format/nest_spec.rb +2 -0
  55. data/spec/lib/card/migration/import_spec.rb +2 -0
  56. data/spec/lib/card/set/trait_spec.rb +2 -0
  57. data/spec/lib/card/stage_director_spec.rb +2 -0
  58. data/spec/lib/card/view_cache_spec.rb +2 -0
  59. data/spec/spec_helper.rb +11 -139
  60. data/spec/support/card_spec_helper.rb +61 -0
  61. data/spec/support/card_spec_loader.rb +83 -0
  62. data/spec/support/helper/card_helper.rb +87 -0
  63. data/spec/support/helper/event_helper.rb +80 -0
  64. data/spec/support/helper/render_helper.rb +35 -0
  65. data/spec/support/helper/save_helper.rb +26 -0
  66. data/{config → spec/support}/simplecov_helper.rb +1 -1
  67. metadata +16 -5
  68. data/spec/card_spec_helper.rb +0 -137
@@ -123,8 +123,8 @@ describe Card::Set::All::Notify do
123
123
  context "and missing permissions" do
124
124
  context "for subcard" do
125
125
  before do
126
- Card.create_or_update! "#{name}+s1+*self+*read",
127
- type: "Pointer", content: "[[Administrator]]"
126
+ create_or_update! "#{name}+s1+*self+*read",
127
+ type: "Pointer", content: "[[Administrator]]"
128
128
  end
129
129
  it "excludes subcard content" do
130
130
  is_expected.not_to include sub1_content
@@ -142,10 +142,10 @@ describe Card::Set::All::Notify do
142
142
  ).text_part.body.raw_source
143
143
  end
144
144
  before do
145
- Card.create_or_update! "#{name}+*self+*read",
146
- type: "Pointer", content: "[[Administrator]]"
147
- Card.create_or_update! "#{name}+s1+*self+*read",
148
- type: "Pointer", content: "[[Anyone]]"
145
+ create_or_update! "#{name}+*self+*read",
146
+ type: "Pointer", content: "[[Administrator]]"
147
+ create_or_update! "#{name}+s1+*self+*read",
148
+ type: "Pointer", content: "[[Anyone]]"
149
149
  end
150
150
  it "includes subcard content" do
151
151
  is_expected.to include sub1_content
@@ -157,18 +157,18 @@ describe Card::Set::All::Notify do
157
157
  end
158
158
  context "for all parts" do
159
159
  before do
160
- Card.create_or_update! "#{name}+s1+*self+*read",
161
- type: "Pointer", content: "[[Administrator]]"
162
- Card.create_or_update! "#{name}+s2+*self+*read",
163
- type: "Pointer", content: "[[Administrator]]"
164
- Card.create_or_update! "#{name}+*self+*read",
165
- type: "Pointer", content: "[[Administrator]]"
160
+ create_or_update! "#{name}+s1+*self+*read",
161
+ type: "Pointer", content: "[[Administrator]]"
162
+ create_or_update! "#{name}+s2+*self+*read",
163
+ type: "Pointer", content: "[[Administrator]]"
164
+ create_or_update! "#{name}+*self+*read",
165
+ type: "Pointer", content: "[[Administrator]]"
166
166
  end
167
167
  it { is_expected.not_to include content }
168
168
  it { is_expected.not_to include sub1_content }
169
169
  it { is_expected.not_to include sub2_content }
170
170
  it "will not be send" do
171
- expect(Card["Joe User"].account.changes_visible? @card.acts.last)
171
+ expect(Card["Joe User"].account.changes_visible?(@card.acts.last))
172
172
  .to be_falsey
173
173
  end
174
174
  end
@@ -204,6 +204,9 @@ describe Card::Set::All::Notify do
204
204
  followed_set: "#{@card.name}+*self",
205
205
  follow_option: "*always"
206
206
  ).text_part.body.raw_source
207
+ unfollow_link =
208
+ "/update/Joe_User+*follow?card%5Bsubcards%5D%5Banother+"\
209
+ "card+with+subcards%2B%2Aself%2BJoe+User%2B%2Afollow%5D=%2Anever"
207
210
  expect(result)
208
211
  .to eq(%("another card with subcards" was just created by Joe User.
209
212
 
@@ -230,7 +233,7 @@ See the card: /another_card_with_subcards
230
233
 
231
234
  You received this email because you're following "another card with subcards".
232
235
 
233
- Use this link to unfollow /update/Joe_User+*follow?card%5Bsubcards%5D%5Banother+card+with+subcards%2B%2Aself%2BJoe+User%2B%2Afollow%5D=%2Anever
236
+ Use this link to unfollow #{unfollow_link}
234
237
  ))
235
238
  end
236
239
  end
@@ -233,7 +233,7 @@ format :html do
233
233
  end
234
234
 
235
235
  view :act_header do |_args|
236
- %(<h5 class="act-header">#{card_link card}</h5>)
236
+ %(<h5 class="act-header">#{link_to_card card}</h5>)
237
237
  end
238
238
 
239
239
  view :act_metadata do |args|
@@ -244,7 +244,7 @@ format :html do
244
244
  = '#' + act_seq.to_s
245
245
  .title
246
246
  .actor
247
- = link_to act.actor.name, card_url(act.actor.cardname.url_key)
247
+ = link_to_card act.actor
248
248
  .time.timeago
249
249
  = time_ago_in_words(act.acted_at)
250
250
  ago
@@ -306,17 +306,17 @@ format :html do
306
306
  end
307
307
 
308
308
  def name_diff action, hide_diff
309
+ working_name = name_changes action, hide_diff
309
310
  if action.card == card
310
- name_changes(action, hide_diff)
311
+ working_name
311
312
  else
312
- link_path = path(
313
- view: :related,
314
- related: { view: "history", name: action.card.name }
313
+ link_to_view(
314
+ :related, working_name,
315
+ path: { related: { view: "history", name: action.card.name } },
316
+ remote: true,
317
+ class: "slotter label label-default",
318
+ "data-slot-selector" => ".card-slot.history-view"
315
319
  )
316
- link_to name_changes(action, hide_diff), link_path,
317
- class: "slotter label label-default",
318
- "data-slot-selector" => ".card-slot.history-view",
319
- remote: true
320
320
  end
321
321
  end
322
322
 
@@ -370,45 +370,42 @@ format :html do
370
370
  end
371
371
 
372
372
  def fold_or_unfold_link args
373
- path_opts = {
374
- act_id: args[:act].id,
375
- act_seq: args[:act_seq],
376
- hide_diff: args[:hide_diff],
377
- act_context: args[:act_context],
378
- action_view: (args[:action_view] == :expanded ? :summary : :expanded),
379
- look_in_trash: true
380
- }
373
+ act_id = args[:act].id
374
+ action_view = args[:action_view] == :expanded ? :summary : :expanded
381
375
  arrow_dir = args[:action_view] == :expanded ? "arrow-down" : "arrow-right"
382
- view_link "", :act, path_opts: path_opts,
383
- class: "slotter revision-#{args[:act_id]} #{arrow_dir}"
376
+
377
+ link_to_view :act, "", class: "slotter revision-#{act_id} #{arrow_dir}",
378
+ path: { act_id: act_id,
379
+ act_seq: args[:act_seq],
380
+ hide_diff: args[:hide_diff],
381
+ act_context: args[:act_context],
382
+ action_view: action_view,
383
+ look_in_trash: true }
384
384
  end
385
385
 
386
386
  def rollback_link actions
387
- not_current =
387
+ # @fixme -- doesn't this need to specify which action it wants?
388
+ prior = # @fixme - should be a Card::Action method
388
389
  actions.select { |action| action.card.last_action_id != action.id }
389
- return unless card.ok?(:update) && not_current.present?
390
- link_path = path action: :update, view: :open, action_ids: not_current,
391
- look_in_trash: true
390
+ return unless card.ok?(:update) && prior.present?
392
391
  link = link_to(
393
- "Save as current", link_path,
394
- class: "slotter", "data-slot-selector" => ".card-slot.history-view",
395
- remote: true, method: :post, rel: "nofollow"
392
+ "Save as current", class: "slotter",
393
+ "data-slot-selector" => ".card-slot.history-view",
394
+ remote: true, method: :post, rel: "nofollow",
395
+ path: { action: :update, action_ids: prior,
396
+ view: :open, look_in_trash: true }
396
397
  )
397
398
  %(<div class="act-link">#{link}</div>)
398
399
  end
399
400
 
400
401
  def show_or_hide_changes_link args
401
- toggle = args[:hide_diff] ? "Show" : "Hide"
402
- path_opts = {
403
- act_id: args[:act].id,
404
- act_seq: args[:act_seq],
405
- hide_diff: !args[:hide_diff],
406
- action_view: :expanded,
407
- act_context: args[:act_context],
408
- look_in_trash: true
409
- }
410
- link = view_link("#{toggle} changes", :act,
411
- path_opts: path_opts, class: "slotter", remote: true)
402
+ link = link_to_view(
403
+ :act, "#{args[:hide_diff] ? 'Show' : 'Hide'} changes",
404
+ class: "slotter",
405
+ path: { act_id: args[:act].id, act_seq: args[:act_seq],
406
+ hide_diff: !args[:hide_diff], action_view: :expanded,
407
+ act_context: args[:act_context], look_in_trash: true }
408
+ )
412
409
  %(<div class="act-link">#{link}</div>)
413
410
  end
414
411
  end
@@ -1,3 +1,5 @@
1
+ # -*- encoding : utf-8 -*-
2
+
1
3
  describe Card::Set::Abstract::Machine do
2
4
  describe "#make_machine_output_coded" do
3
5
  it "creates coded file" do
@@ -45,14 +45,11 @@ format :html do
45
45
 
46
46
  def groups item_names
47
47
  group_options.map do |option|
48
- checked = !!item_names.delete(option.name)
49
- %(
50
- <div class="group-option">
51
- #{check_box_tag("#{option.key}-perm-checkbox", option.name, checked,
52
- class: 'perm-checkbox-button')}
53
- <label>#{card_link option.name, target: 'wagn_role'}</label>
54
- </div>
55
- )
48
+ checked = !item_names.delete(option.name).nil?
49
+ option_link = link_to_card option.name, nil, target: "wagn_role"
50
+ box = check_box_tag "#{option.key}-perm-checkbox",
51
+ option.name, checked, class: "perm-checkbox-button"
52
+ %(<div class="group-option">#{box}<label>#{option_link}</label></div>)
56
53
  end * "\n"
57
54
  end
58
55
 
@@ -103,7 +100,7 @@ format :html do
103
100
  task = card.tag.codename
104
101
  ancestor = Card[set_context.trunk_name.trunk_name]
105
102
  links = ancestor.who_can(task.to_sym).map do |card_id|
106
- card_link Card[card_id].name, target: args[:target]
103
+ link_to_card card_id, nil, target: args[:target]
107
104
  end * ", "
108
105
  "Inherit ( #{links} )"
109
106
  rescue
@@ -58,17 +58,18 @@ format do
58
58
  - card.set_classes_with_rules.each do |klass, rules|
59
59
  %tr.klass-row
60
60
  %td{class: ['setting-klass', "anchorless-#{klass.anchorless?}"]}
61
- = klass.anchorless? ? card_link(klass.pattern) : klass.pattern
61
+ - kpat = klass.pattern
62
+ = klass.anchorless? ? link_to_card(kpat) : kpat
62
63
  %td.rule-content-container
63
64
  %span.closed-content.content
64
65
  - if klass.anchorless?
65
66
  = subformat(rules[0])._render_closed_content
66
67
  - if !klass.anchorless?
67
68
  - duplicate_check(rules) do |rule, duplicate, changeover|
69
+ - setname = rule.cardname.trunk_name
68
70
  %tr{class: ('rule-changeover' if changeover)}
69
71
  %td.rule-anchor
70
- = card_link rule.cardname.trunk_name,
71
- text: rule.cardname.trunk_name.trunk_name
72
+ = link_to_card setname, setname.trunk_name
72
73
  - if duplicate
73
74
  %td
74
75
  - else
@@ -10,31 +10,36 @@
10
10
 
11
11
  card_accessor :solid_cache, type: :html
12
12
 
13
- format :html do
14
- def default_core_args args
15
- args[:solid_cache] = true unless args.key?(:solid_cache)
13
+ def self.included host_class
14
+ host_class.format(host_class.try(:cached_format) || :base) do
15
+ view :core do |args|
16
+ return super(args) unless args[:solid_cache]
17
+ card.update_solid_cache if card.solid_cache_card.new?
18
+ subformat(card.solid_cache_card)._render_core args
19
+ end
16
20
  end
21
+ end
17
22
 
18
- view :core do |args|
19
- return super(args) unless args[:solid_cache]
20
-
21
- subformat(card.solid_cache_card)._render_core args
23
+ format do
24
+ def default_core_args args
25
+ args[:solid_cache] = true unless args.key?(:solid_cache)
22
26
  end
23
27
  end
24
28
 
25
29
  module ClassMethods
26
- # If a card of the set given by 'set_of_changed_card' is updated
30
+ # If a card of the set given by 'set_of_changed_card' is changed
27
31
  # the given block is executed. It is supposed to return an array of
28
- # cards whose solid caches are expired because of the update.
32
+ # cards whose solid caches are expired because of the change.
29
33
  # @param set_of_changed_card [set constant] a set of cards that triggers
30
34
  # a cache update
31
35
  # @param args [Hash]
32
- # @option args [Symbol, Array of symbols] :on the action(s)
36
+ # @option args [Symbol, Array<Symbol>] :on the action(s)
33
37
  # (:create, :update, or :delete) on which the cache update
34
38
  # should be triggered. Default is all actions.
35
39
  # @option args [Stage] :in_stage the stage when the update is executed.
36
40
  # Default is :integrate
37
- # @yield return an array of cards with solid cache that need to be updated
41
+ # @yield return a card or an array of cards with solid cache that need to be
42
+ # updated
38
43
  def cache_update_trigger set_of_changed_card, args={}, &block
39
44
  define_event_to_update_expired_cached_cards(
40
45
  set_of_changed_card, args, :update_solid_cache, &block
@@ -56,37 +61,53 @@ module ClassMethods
56
61
  stage = args[:in_stage] || :integrate
57
62
  Card::Set.register_set set_of_changed_card
58
63
  set_of_changed_card.event name, stage, args do
59
- Array(yield(self)).compact.each do |expired_cache_card|
64
+ Array.wrap(yield(self)).compact.each do |expired_cache_card|
60
65
  next unless expired_cache_card.solid_cache?
61
- expired_cache_card.send method_name
66
+ expired_cache_card.send method_name, self
62
67
  end
63
68
  end
64
69
  end
65
70
 
66
71
  def event_name set, args
67
- changed_card_set = set.to_s.tr(":", "_").underscore
68
- solid_cache_set = to_s.tr(":", "_").underscore
72
+ changed_card_set = set.shortname.tr(":", "_").underscore
73
+ solid_cache_set = shortname.tr(":", "_").underscore + "__solid_cache"
69
74
  actions = Array.wrap(args[:on]).join("_")
70
- "update_#{solid_cache_set}_solid_cache_changed_by_" \
71
- "#{changed_card_set}_on_#{actions}".to_sym
75
+ ["update", solid_cache_set,
76
+ "changed_by", changed_card_set,
77
+ "on", actions].join("___").to_sym
72
78
  end
73
79
  end
74
80
 
75
- def expire_solid_cache
81
+ def expire_solid_cache _changed_card=nil
76
82
  return unless solid_cache?
77
83
  Auth.as_bot do
78
84
  solid_cache_card.delete!
79
85
  end
80
86
  end
81
87
 
82
- def update_solid_cache
88
+ def update_solid_cache changed_card=nil
83
89
  return unless solid_cache?
84
- new_content = format(:html)._render_core(solid_cache: false)
90
+ new_content =
91
+ if solid_cache_card.new?
92
+ generate_content_for_cache changed_card
93
+ else
94
+ updated_content_for_cache changed_card
95
+ end
85
96
  return unless new_content
86
97
  write_to_solid_cache new_content
87
98
  new_content
88
99
  end
89
100
 
101
+ def generate_content_for_cache changed_card=nil
102
+ format_type = try(:cached_format) || :base
103
+ format(format_type)._render_core(solid_cache: false,
104
+ changed_card: changed_card)
105
+ end
106
+
107
+ def updated_content_for_cache _changed_card=nil
108
+ generate_content_for_cache
109
+ end
110
+
90
111
  def write_to_solid_cache new_content
91
112
  Auth.as_bot do
92
113
  if solid_cache_card.new_card?
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
 
2
3
  describe Card::Set::Abstract::SolidCache do
3
4
  context "render core view of a card" do
@@ -5,7 +6,8 @@ describe Card::Set::Abstract::SolidCache do
5
6
  @card = Card["A"]
6
7
  end
7
8
 
8
- let(:core_view) { 'Alpha <a class="known-card" href="/Z">Z</a>' }
9
+ # let(:core_view) { 'Alpha <a class="known-card" href="/Z">Z</a>' }
10
+ let(:core_view) { "Alpha Z[/Z]" }
9
11
  context "with solid cache" do
10
12
  it "saves core view in solid cache card" do
11
13
  @card.format_with_set(Card::Set::Abstract::SolidCache, &:render_core)
@@ -71,18 +71,16 @@ format :html do
71
71
  end
72
72
 
73
73
  def backtrace_link exception
74
- warning_options = {
75
- dismissible: true,
76
- alert_class: "render-error-message errors-view admin-error-message"
77
- }
78
- warning = alert("warning", warning_options) do
74
+ alert_class = "render-error-message errors-view admin-error-message"
75
+ warning = alert("warning", dismissible: true, alert_class: alert_class) do
79
76
  %{
80
77
  <h3>Error message (visible to admin only)</h3>
81
78
  <p><strong>#{exception.message}</strong></p>
82
79
  <div>#{exception.backtrace * "<br>\n"}</div>
83
80
  }
84
81
  end
85
- card_link(error_cardname, class: "render-error-link") + warning
82
+ link = link_to_card error_cardname, nil, class: "render-error-link"
83
+ link + warning
86
84
  end
87
85
 
88
86
  view :unsupported_view, perms: :none, tags: :unknown_ok do |args|
@@ -102,15 +100,11 @@ format :html do
102
100
 
103
101
  view :missing do |args|
104
102
  return "" unless card.ok? :create # should this be moved into ok_view?
105
-
106
- link_opts = {
107
- remote: true,
108
- class: "slotter missing-#{args[:denied_view] || args[:home_view]}"
109
- }
110
- link_opts[:path_opts] = { type: args[:type] } if args[:type]
111
-
103
+ missing_view = args[:denied_view] || args[:home_view]
112
104
  wrap args do
113
- view_link "Add #{fancy_title args[:title]}", :new, link_opts
105
+ link_to_view :new, "Add #{fancy_title args[:title]}",
106
+ path: (args[:type] ? { type: args[:type] } : {}),
107
+ class: "slotter missing-#{missing_view}"
114
108
  end
115
109
  end
116
110
 
@@ -119,7 +113,7 @@ format :html do
119
113
  end
120
114
 
121
115
  view :conflict, error_code: 409 do |args|
122
- actor_link = card_link card.last_action.act.actor.cardname
116
+ actor_link = link_to_card card.last_action.act.actor.cardname
123
117
  expanded_act = wrap(args) do
124
118
  _render_act_expanded act: card.last_action.act, current_rev_nr: 0
125
119
  end
@@ -158,8 +152,8 @@ format :html do
158
152
  view :not_found do |args| # ug. bad name.
159
153
  sign_in_or_up_links =
160
154
  unless Auth.signed_in?
161
- signin_link = card_link :signin, text: "Sign in"
162
- signup_link = link_to "Sign up", card_path("new/:signup")
155
+ signin_link = link_to_card :signin, "Sign in"
156
+ signup_link = link_to "Sign up", path: { account: :new, type: :signup }
163
157
  %(<div>#{signin_link} or #{signup_link} to create it.</div>)
164
158
  end
165
159
  frame args.merge(title: "Not Found", optional_menu: :never) do
@@ -186,10 +180,11 @@ format :html do
186
180
  when Auth.signed_in?
187
181
  "You need permission #{to_task}"
188
182
  else
189
- signin_link = link_to "sign in", card_url(":signin")
183
+ signin_link = link_to_card :signin, "sign in"
190
184
  or_signup_link =
191
185
  if Card.new(type_id: Card::SignupID).ok? :create
192
- "or " + link_to("sign up", card_url("new/:signup"))
186
+ "or " +
187
+ link_to("sign up", path: { account: "new", type: :signup })
193
188
  end
194
189
  Env.save_interrupted_action(request.env["REQUEST_URI"])
195
190
  "Please #{signin_link} #{or_signup_link} #{to_task}"
@@ -1,9 +1,30 @@
1
- format do
2
- # link is called by web_link, card_link, and view_link
3
- # (and is overridden in other formats)
4
- def link_to text, href, _opts={}
5
- href = interpret_href href
1
+ RESOURCE_TYPE_REGEXP = /^([a-zA-Z][\-+\.a-zA-Z\d]*):/
2
+
3
+ format :html do
4
+ def link_to text=nil, opts={}
5
+ opts[:href] = interpret_pathish opts.delete(:path)
6
+ text = raw(text || opts[:href])
7
+ interpret_data_opts_to_link_to opts
8
+ content_tag :a, text, opts
9
+ end
10
+
11
+ def interpret_data_opts_to_link_to opts
12
+ [:remote, :method].each do |key|
13
+ next unless (val = opts.delete key)
14
+ opts["data-#{key}"] = val
15
+ end
16
+ end
17
+ end
18
+
19
+ format :css do
20
+ def link_to _text=nil, opts={}
21
+ card_url interpret_pathish(opts.delete(:path))
22
+ end
23
+ end
6
24
 
25
+ format do
26
+ def link_to text=nil, opts={}
27
+ href = interpret_pathish opts.delete(:path)
7
28
  if text && href != text
8
29
  "#{text}[#{href}]"
9
30
  else
@@ -11,148 +32,156 @@ format do
11
32
  end
12
33
  end
13
34
 
14
- # link to url, view, card or related card
15
- def smart_link link_text, target, html_args={}
16
- if (view = target.delete(:view))
17
- view_link link_text, view, html_args.merge(path_opts: target)
18
- elsif (page = target.delete(:card))
19
- card_link page, html_args.merge(path_opts: target, text: link_text)
20
- elsif target[:related]
21
- if target[:related].is_a? String
22
- target[:related] = { name: "+#{target[:related]}" }
23
- end
24
- view_link link_text, :related, html_args.merge(path_opts: target)
25
- elsif target[:web]
35
+ def smart_link_to text, opts={}
36
+ if (linktype = [:view, :related, :card, :resource].find { |key| opts[key] })
37
+ send "link_to_#{linktype}", opts.delete(linktype), text, opts
26
38
  else
27
- link_to link_text, target, html_args
39
+ send :link_to, text, opts
40
+ end
41
+ end
42
+
43
+ def link_to_resource resource, text=nil, opts={}
44
+ case (resource_type = resource_type resource)
45
+ when "external-link" then opts[:target] = "_blank"
46
+ when "internal-link" then resource = internal_url resource[1..-1]
47
+ end
48
+ add_class opts, resource_type
49
+ link_to text, opts.merge(path: resource)
50
+ end
51
+
52
+ def resource_type resource
53
+ case resource
54
+ when /^https?\:/ then "external-link"
55
+ when %r{^/} then "internal-link"
56
+ when /^mailto\:/ then "email-link"
57
+ when RESOURCE_TYPE_REGEXP then Regexp.last_match(1) + "-link"
28
58
  end
29
59
  end
30
60
 
31
- # link to a specific url or path
32
- def web_link href, opts={}
33
- text = opts.delete(:text) || href
34
- new_class =
35
- case href
36
- when /^https?\:/
37
- opts[:target] = "_blank"
38
- "external-link"
39
- when /^mailto\:/
40
- "email-link"
41
- when /^([a-zA-Z][\-+\.a-zA-Z\d]*):/
42
- Regexp.last_match(1) + "-link"
43
- when %r{^/}
44
- href = internal_url href[1..-1]
45
- "internal-link"
46
- else
47
- return card_link href, opts
48
- end
49
- add_class opts, new_class
50
- link_to text, href, opts
51
- end
52
-
53
- # link to a specific card
54
- def card_link name_or_card, opts={}
55
- name =
56
- case name_or_card
57
- when Symbol then Card.fetch(name_or_card, skip_modules: true).cardname
58
- when Card then name_or_card.cardname
59
- else name_or_card
60
- end
61
- text = (opts.delete(:text) || name).to_name.to_show @context_names
62
-
63
- path_opts = opts.delete(:path_opts) || {}
64
- path_opts[:name] = name
65
- path_opts[:known] =
66
- opts[:known].nil? ? Card.known?(name) : opts.delete(:known)
67
- add_class opts, (path_opts[:known] ? "known-card" : "wanted-card")
68
- link_to text, path_opts, opts
61
+ def link_to_card cardish, text=nil, opts={}
62
+ opts[:path] ||= {}
63
+ name = opts[:path][:name] = Card::Name.cardish cardish
64
+ # @fixme - need smarter mark handling
65
+
66
+ text ||= name.to_name.to_show @context_names
67
+ add_known_or_wanted_class opts, name
68
+ link_to text, opts
69
+ end
70
+
71
+ def add_known_or_wanted_class opts, name
72
+ known = opts.delete :known
73
+ known = Card.known?(name) if known.nil?
74
+ add_class opts, (known ? "known-card" : "wanted-card")
69
75
  end
70
76
 
71
77
  # link to a specific view (defaults to current card)
72
78
  # this is generally used for ajax calls
73
- def view_link text, view, opts={}
74
- path_opts = opts.delete(:path_opts) || {}
75
- path_opts[:view] = view unless view == :home
76
- opts[:remote] = true
77
- opts[:rel] = "nofollow"
78
-
79
- link_to text, path_opts, opts
80
- end
81
-
82
- def related_link name_or_card, opts={}
83
- name =
84
- case name_or_card
85
- when Symbol then Card.fetch(name_or_card, skip_modules: true).cardname
86
- when Card then name_or_card.cardname
87
- else name_or_card
88
- end
89
- opts[:path_opts] ||= { view: :related }
90
- opts[:path_opts][:related] = { name: "+#{name}" }
91
- opts[:path_opts][:related].merge! opts[:related_opts] if opts[:related_opts]
92
- view_link(opts[:text] || name, :related, opts)
93
- end
94
-
95
- def path opts={}
96
- if opts[:action] == :new && opts[:type] &&
97
- !(opts[:name] || opts[:card] || opts[:id])
98
- opts.delete(:action)
99
- base = "new/#{opts.delete(:type)}"
100
- else
101
- name = opts.delete(:name) || card.name
102
- base = opts[:action] ? "card/#{opts.delete :action}/" : ""
79
+ def link_to_view view, text, opts={}
80
+ opts.reverse_merge! path: {}, remote: true, rel: "nofollow"
81
+ opts[:path][:view] = view unless view == :home
82
+ link_to text, opts
83
+ end
103
84
 
104
- opts[:no_id] = true if [:new, :create].member? opts[:action]
105
- # generalize. dislike hardcoding views/actions here
85
+ def link_to_related cardish, text=nil, opts={}
86
+ name = Card::Name.cardish cardish
87
+ opts[:path] ||= {}
88
+ opts[:path][:related] ||= {}
89
+ opts[:path][:related][:name] ||= "+#{name}"
90
+ link_to_view :related, (text || name), opts
91
+ end
106
92
 
107
- linkname = name.to_name.url_key
108
- unless name.empty? || opts.delete(:no_id)
109
- base += (opts[:id] ? "~#{opts.delete :id}" : linkname)
110
- end
93
+ # @param opts [Hash]
94
+ # @option opts [Symbol] :action card action (:create, :update, :delete)
95
+ # @option opts [Integer, String] :id
96
+ # @option opts [String, Card::Name] :name
97
+ # @option opts [String] :type
98
+ # @option opts [Hash] :card
99
+ # @param mark_type [Symbol] defaults to :id
100
+ def path opts={}, mark_type=:id
101
+ path = new_cardtype_path(opts) || standard_path(opts, mark_type)
102
+ internal_url path
103
+ end
111
104
 
112
- process_path_card_opts opts, name, linkname
113
- end
105
+ def new_cardtype_path opts
106
+ return unless opts[:action] == :new
107
+ opts.delete :action
108
+ return unless (type_mark = opts.delete(:type))
109
+ "new/#{Card.quick_fetch(type_mark).cardname.url_key}"
110
+ end
114
111
 
115
- query = opts.empty? ? "" : "?#{opts.to_param}"
116
- internal_url(base + query)
112
+ def standard_path opts, mark_type
113
+ standardize_action! opts
114
+ base = path_action(opts[:action]) + path_mark(opts, mark_type)
115
+ base + path_query(opts)
117
116
  end
118
117
 
119
- def internal_url relative_path
120
- card_path relative_path
118
+ def path_action action
119
+ case action
120
+ when :create then "card/#{action}/"
121
+ # sometimes create action has no mark,
122
+ # but /create alone would refer to a card named "create"
123
+ when nil then ""
124
+ else "#{action}/"
125
+ end
121
126
  end
122
127
 
123
- def interpret_href href
124
- href.is_a?(Hash) ? path(href) : href
128
+ def standardize_action! opts
129
+ return if [:create, :update, :delete].member? opts[:action]
130
+ opts.delete :action
125
131
  end
126
132
 
127
- def process_path_card_opts opts, name, linkname
128
- opts[:card] ||= {}
129
- if opts.delete(:known) == false && name.present? && name.to_s != linkname
130
- opts[:card][:name] = name
133
+ def path_mark opts, mark_type
134
+ case mark_type
135
+ when :id && (id = path_id opts) then "~#{id}"
136
+ when :codename && (codename = card.codename) then ":#{codename}"
137
+ else (opts[:name] || card.name).to_name.url_key
131
138
  end
139
+ end
140
+
141
+ def path_id opts
142
+ id = opts.delete :id
143
+ id if id.present?
144
+ end
145
+
146
+ def path_query opts
147
+ finalize_card_opts opts.delete(:card), opts
148
+ opts.delete :action
149
+ opts.empty? ? "" : "?#{opts.to_param}"
150
+ end
132
151
 
133
- if (type = opts.delete(:type)) && Card.known?(type)
134
- opts[:card][:type] = type
152
+ def finalize_card_opts card_opts, opts
153
+ card_opts ||= {}
154
+ [:name, :type].each do |field|
155
+ assign_path_card_opt card_opts, field, opts
135
156
  end
136
- opts.delete(:card) if opts[:card].empty?
157
+ opts[:card] = card_opts unless card_opts.empty?
137
158
  end
138
- end
139
159
 
140
- format :html do
141
- def link_to text, href, opts={}
142
- href = interpret_href href
160
+ def assign_path_card_opt card_opts, field, opts
161
+ optvalue = opts.delete field
162
+ return if card_opts[field] || !optvalue.present?
163
+ new_value = send "new_#{field}_in_path_opts", optvalue.to_s, opts
164
+ return unless new_value
165
+ card_opts[field] = new_value
166
+ end
143
167
 
144
- [:remote, :method].each do |key|
145
- if (val = opts.delete(key))
146
- opts["data-#{key}"] = val
147
- end
168
+ def new_name_in_path_opts name, opts
169
+ if opts[:action] == :update
170
+ name if name != card.name
171
+ elsif !Card.known?(name) && name != name.to_name.url_key
172
+ name
148
173
  end
174
+ end
149
175
 
150
- content_tag :a, raw(text), opts.merge(href: href)
176
+ def new_type_in_path_opts opttype, _opts
177
+ opttype if Card.known?(opttype)
151
178
  end
152
- end
153
179
 
154
- format :css do
155
- def link_to _text, href, _opts={}
156
- card_url interpret_href(href)
180
+ def internal_url relative_path
181
+ card_path relative_path
182
+ end
183
+
184
+ def interpret_pathish pathish
185
+ pathish.is_a?(Hash) ? path(pathish) : pathish
157
186
  end
158
187
  end