card-mod-follow 0.11.0

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