card-mod-follow 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0f81b70d3b7b7b739342e05f5bbc648ba4b7ae85c6f388f4f1cfe2065b1ef6be
4
+ data.tar.gz: 793b238cbd0c52ade5746a36ea734f4421de793d328ab8699aa84b25c29d2c58
5
+ SHA512:
6
+ metadata.gz: 27bdb26be87cf868403b91cc332514ca043a21af76f9dd129a1fe2300f40f54a881a56dcb081693e92ca99ed939ba408a819e11b1589a85a5a35e1a7be2176b9
7
+ data.tar.gz: 66299e2fe971aedf324afc968b278c31845e82b95c2c3ab0fa0e0600571fac5a18d16426f7551988460bb37bf1a61dea8e3d1f84866bce664ca83e8d211bff8d
@@ -0,0 +1,134 @@
1
+ <!--
2
+ # @title README - mod: follow
3
+ -->
4
+ # Follow
5
+
6
+ Follow sets cards, get notified when any card in the set changes.
7
+
8
+ ## Sets
9
+
10
+ Cards ruling notifications include:
11
+
12
+ ### Follow preferences (or follow rules)
13
+
14
+ | name | type | content |
15
+ |:----:|:----:|:-------:|
16
+ | [Set]+[User]+:follow | Pointer | list of follow options |
17
+
18
+ Each rule determines cases in which the _User_ should be notified about changes to the _Set_.
19
+
20
+ These cards have special permission handling, granting full permission to the _User_.
21
+
22
+ Special views include:
23
+
24
+ - *follow_item*
25
+ - *follow_status*
26
+
27
+ ### Follow options
28
+
29
+ | name | type | content |
30
+ |:----:|:----:|:-------:|
31
+ | \*_optionname_| Basic | (blank) |
32
+
33
+ Each follow preference can point to one or more of the following follow options.
34
+
35
+ - _always:_ notify user after every create, update, and delete
36
+ - _never:_ do not notify user
37
+ - _created:_ notify user when a card is created
38
+ - _edited:_ notify user when a card is created or updated
39
+
40
+ ### Follow dashboard
41
+
42
+ | name | type | content |
43
+ |:----:|:----:|:-------:|
44
+ | [User]+:follow | Pointer(virtual) | list of sets |
45
+
46
+ Core view shows Follow and Ignore tabs. Each tab has a list of the
47
+ user's preferences in `follow_item` view. Navigate to this card via the
48
+ card submenu (`account > follow`).
49
+
50
+ Special views include:
51
+ - *follow_tab*
52
+ - *ignore_tab*
53
+
54
+ ### Follow suggestions
55
+
56
+ | name | type | content |
57
+ |:----:|:----:|:-------:|
58
+ | :follow_suggestions | Pointer | list of follow settings |
59
+
60
+ This advanced global setting lets sharks determine what suggestions will appear on the
61
+ follow dashboard. Any item not currently followed will be suggested.
62
+
63
+ Each suggestion can take the form of either `[Set]` or `[Set]+[Follow Option]`.
64
+
65
+ ### Following status
66
+
67
+ | name | type | content |
68
+ |:----:|:----:|:-------:|
69
+ | +:following | Pointer(virtual) | card's follow status for current user |
70
+
71
+ With this card, users can see and/or edit whether they are following the
72
+ card in question.
73
+
74
+ This view is seen via the "follow" item in the main card menu or via
75
+ `activity > follow` in the submenu.
76
+
77
+ Special views include:
78
+ - _core_ a follow edit interface if signed in
79
+ - _status_
80
+
81
+ ### Followers
82
+
83
+ | name | type | content |
84
+ |:----:|:----:|:-------:|
85
+ | +:followers | Pointer(virtual) | list of users |
86
+
87
+ User who are following the card in question.
88
+
89
+ (not integrated into interface)
90
+
91
+ ### Email template
92
+
93
+ | name | type | content |
94
+ |:----:|:----:|:-------:|
95
+ |follower notification email|Email Template|(nested email config)|
96
+
97
+ The _to_ field is handled automatically. All other fields can be sharked.
98
+
99
+ Useful views added by this mod include:
100
+
101
+ - _list_of_changes_ (updates coming soon)
102
+ - _subedits:_ (updates coming soon)
103
+ - _followed:_ label of set followed
104
+ - _unfollow_url:_ link to remove the following rule that led to this email
105
+
106
+ ### Follow fields
107
+
108
+ | name | type | content |
109
+ |:----:|:----:|:-------:|
110
+ | [Set]+:follow_fields | Pointer (rule) | list of fields |
111
+
112
+ These rules rules are advanced configuration that let sharks determine which of a card’s fields
113
+ are automatically followed when a user follow that card.
114
+
115
+ The default value is `:includes`, which means that nested cards are followed by default.
116
+
117
+ ## Lib
118
+
119
+ ### Card::FollowerStash
120
+
121
+ A class for stashing followers of a given card.
122
+
123
+ ### Card::FollowOption
124
+
125
+ A module for tracking and grouping follow options and their processing code.
126
+
127
+
128
+ ## TODO
129
+ - review / address follow references in:
130
+ - rich_html/
131
+ - menu
132
+ - toolbar
133
+ - all/rules
134
+
@@ -0,0 +1,33 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ class Card
4
+ # module to be included in cards used as options for follow rules
5
+ module FollowOption
6
+ # Hash containing an applicability test for each option (block)
7
+ @test = {}
8
+ # Hash containing an id-list-generating block for each option
9
+ @follower_candidate_ids = {}
10
+ # Hash that registers / groups options
11
+ @options = { all: [], main: [], restrictive: [] }
12
+
13
+ class << self
14
+ attr_reader :test, :follower_candidate_ids, :options
15
+
16
+ def codenames type=:all
17
+ options[type]
18
+ end
19
+
20
+ def cards
21
+ codenames.map { |codename| Card[codename] }
22
+ end
23
+
24
+ def restrictive_options
25
+ codenames :restrictive
26
+ end
27
+
28
+ def main_options
29
+ codenames :main
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,88 @@
1
+ class Card
2
+ # stash followers of a given card
3
+ class FollowerStash
4
+ def initialize card=nil
5
+ @stash = Hash.new { |h, v| h[v] = [] }
6
+ @checked = ::Set.new
7
+ check_card(card) if card
8
+ end
9
+
10
+ def check_card card
11
+ return if @checked.include? card.key
12
+
13
+ Auth.as_bot do
14
+ @checked.add card.key
15
+ stash_direct_followers card
16
+ stash_field_followers card.left
17
+ end
18
+ end
19
+
20
+ def followers
21
+ @stash.keys
22
+ end
23
+
24
+ def each_follower_with_reason
25
+ # "follower"(=user) is a card object, "followed"(=reasons) a card name
26
+ @stash.each do |follower_card, reasons|
27
+ yield(follower_card, reasons.first)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def stash_direct_followers card
34
+ card.each_direct_follower_id_with_reason do |user_id, reason|
35
+ stash Card.fetch(user_id), reason
36
+ end
37
+ end
38
+
39
+ def stash_field_followers card
40
+ return unless (fields = follow_fields card)
41
+
42
+ fields.each do |field|
43
+ break if stash_field_follower card, field
44
+ end
45
+ end
46
+
47
+ def stash_field_follower card, field
48
+ return false unless checked?(field.to_name) || nested?(card, field)
49
+
50
+ check_card card
51
+ true
52
+ end
53
+
54
+ def nested? card, field
55
+ return unless field.to_name.key == includes_card_key
56
+
57
+ @checked.intersection(nestee_set(card)).any?
58
+ end
59
+
60
+ def includes_card_key
61
+ @includes_card_key ||= :nests.cardname.key
62
+ end
63
+
64
+ def nestee_set card
65
+ @nestee_set ||= {}
66
+ @nestee_set[card.key] ||= nestee_search card
67
+ end
68
+
69
+ def nestee_search card
70
+ Card.search({ return: "key", included_by: card.name },
71
+ "follow cards included by #{card.name}")
72
+ end
73
+
74
+ def checked? name
75
+ @checked.include? name.key
76
+ end
77
+
78
+ def follow_fields card
79
+ return unless card && !checked?(card.name)
80
+
81
+ card.rule_card(:follow_fields)&.item_names(context: card.name)
82
+ end
83
+
84
+ def stash follower, reason
85
+ @stash[follower] << reason
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,56 @@
1
+ def restrictive_option?
2
+ Card::FollowOption.restrictive_options.include? codename
3
+ end
4
+
5
+ def description set_card
6
+ set_card.follow_label
7
+ end
8
+
9
+ # follow option methods on the Card class
10
+ # FIXME: there's not a great reason to have these on the Card class
11
+ module ClassMethods
12
+ # args:
13
+ # position: <Fixnum> (starting at 1, default: add to end)
14
+ def restrictive_follow_opts args
15
+ add_option args, :restrictive
16
+ end
17
+
18
+ # args:
19
+ # position: <Fixnum> (starting at 1, default: add to end)
20
+ def follow_opts args
21
+ add_option args, :main
22
+ end
23
+
24
+ def follow_test opts={}, &block
25
+ Card::FollowOption.test[get_codename(opts)] = block
26
+ end
27
+
28
+ def follower_candidate_ids opts={}, &block
29
+ Card::FollowOption.follower_candidate_ids[get_codename(opts)] = block
30
+ end
31
+
32
+ private
33
+
34
+ def insert_option pos, item, type
35
+ list = Card::FollowOption.codenames(type)
36
+ list[pos] ? list.insert(pos, item) : (list[pos] = item)
37
+ # If pos > codenames.size in a previous insert then we have a bunch
38
+ # of preceding nils in the array.
39
+ # Hence, we have to overwrite a nil value if we encounter one and
40
+ # can't use insert.
41
+ end
42
+
43
+ def add_option opts, type, &_block
44
+ codename = get_codename opts
45
+ if opts[:position]
46
+ insert_option opts[:position] - 1, codename, type
47
+ else
48
+ Card::FollowOption.codenames(type) << codename
49
+ end
50
+ Card::FollowOption.codenames(:all) << codename
51
+ end
52
+
53
+ def get_codename opts
54
+ opts[:codename] || name.match(/::(\w+)$/)[1].underscore.to_sym
55
+ end
56
+ end
@@ -0,0 +1,33 @@
1
+ # for override
2
+ def followable?
3
+ true
4
+ end
5
+
6
+ def follow_label
7
+ name
8
+ end
9
+
10
+ def list_direct_followers?
11
+ false
12
+ end
13
+
14
+ def follow_option?
15
+ codename && FollowOption.codenames.include?(codename)
16
+ end
17
+
18
+ # the set card to be followed if you want to follow changes of card
19
+ def follow_set_card
20
+ Card.fetch name, :self
21
+ end
22
+
23
+ def follow_rule_name user=nil
24
+ follow_set_card&.follow_rule_name user
25
+ end
26
+
27
+ def follow_rule_card user=nil, args={}
28
+ Card.fetch follow_rule_name(user), args
29
+ end
30
+
31
+ def follow_rule? user=nil
32
+ Card.exists? follow_rule_name(user)
33
+ end
@@ -0,0 +1,61 @@
1
+ #! no set module
2
+ class FollowLink
3
+ attr_reader :format, :rule_content, :link_text, :action, :css_class, :hover_text
4
+
5
+ delegate :link_to, to: :format
6
+
7
+ def initialize format
8
+ @format = format
9
+ @card = format.card
10
+ end
11
+
12
+ def modal_link icon=false
13
+ opts = link_opts.merge(
14
+ "data-path": link_opts[:path],
15
+ "data-toggle": "modal",
16
+ "data-target": "#modal-#{card.name.safe_key}",
17
+ class: css_classes("follow-link", link_opts[:class])
18
+ )
19
+ link_to render_link_text(icon), opts
20
+ end
21
+
22
+ def button
23
+ opts = link_opts(:follow_section).merge(
24
+ remote: true,
25
+ class: @format.css_classes("follow-link", link_opts[:class],
26
+ "slotter btn btn-sm btn-primary")
27
+ )
28
+ opts["data-update-foreign-slot"] = ".d0-card-body > .card-slot.RIGHT-Xfollower.content-view"
29
+ opts["data-hover-text"] = hover_text if hover_text
30
+ link_to render_link_text, opts
31
+ end
32
+
33
+ def link_opts success_view=:follow_status
34
+ { title: title,
35
+ path: path(success_view),
36
+ class: css_class }
37
+ end
38
+
39
+ def render_link_text icon=false
40
+ verb = %(<span class="follow-verb">#{link_text}</span>)
41
+ icon = icon ? icon_tag(:flag) : ""
42
+ [icon, verb].compact.join.html_safe
43
+ end
44
+
45
+ private
46
+
47
+ def mark
48
+ @card.follow_set_card.follow_rule_name Auth.current.name
49
+ end
50
+
51
+ def path view=:follow_status
52
+ @format.path mark: mark,
53
+ action: :update,
54
+ success: { id: @card.name, view: view },
55
+ card: { content: "[[#{rule_content}]]" }
56
+ end
57
+
58
+ def title
59
+ "#{action} emails about changes to #{@card.follow_label}"
60
+ end
61
+ end
@@ -0,0 +1,29 @@
1
+ format do
2
+ def follow_link_class
3
+ card.followed? ? StopFollowLink : StartFollowLink
4
+ end
5
+
6
+ def show_follow?
7
+ Auth.signed_in? && !card.new_card? && card.followable?
8
+ end
9
+ end
10
+
11
+ format :json do
12
+ view :follow_status do
13
+ follow_link_class.link_opts
14
+ end
15
+ end
16
+
17
+ format :html do
18
+ def follow_button
19
+ follow_link_class.new(self).button
20
+ end
21
+
22
+ def follow_modal_link
23
+ follow_link_class.new(self).modal_link
24
+ end
25
+
26
+ view :follow_button, cache: :never do
27
+ follow_button
28
+ end
29
+ end