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.
- checksums.yaml +7 -0
- data/README.md +134 -0
- data/lib/card/follow_option.rb +33 -0
- data/lib/card/follower_stash.rb +88 -0
- data/set/abstract/follow_option.rb +56 -0
- data/set/all/follow.rb +33 -0
- data/set/all/follow/follow_link.rb +61 -0
- data/set/all/follow/follow_link_views.rb +29 -0
- data/set/all/follow/followed_by.rb +70 -0
- data/set/all/follow/follower_ids.rb +121 -0
- data/set/all/follow/start_follow_link.rb +11 -0
- data/set/all/follow/stop_follow_link.rb +12 -0
- data/set/all/notify.rb +81 -0
- data/set/all/notify/base_views.rb +127 -0
- data/set/all/notify/html_views.rb +17 -0
- data/set/right/account.rb +15 -0
- data/set/right/follow.rb +118 -0
- data/set/right/follow/follow_status.haml +9 -0
- data/set/right/follow_fields.rb +3 -0
- data/set/right/followers.rb +23 -0
- data/set/right/following.rb +50 -0
- data/set/self/always.rb +13 -0
- data/set/self/created.rb +19 -0
- data/set/self/edited.rb +20 -0
- data/set/self/follow.rb +1 -0
- data/set/self/follow_defaults.rb +88 -0
- data/set/self/follow_fields.rb +1 -0
- data/set/self/never.rb +13 -0
- data/set/type/cardtype.rb +21 -0
- data/set/type/notification_template.rb +33 -0
- data/set/type/set.rb +53 -0
- data/set/type/user.rb +7 -0
- data/set/type_plus_right/user/follow.rb +77 -0
- data/set/type_plus_right/user/follow/follow_editor.haml +8 -0
- data/set/type_plus_right/user/follow/follow_editor_helper.rb +121 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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
|
data/set/all/follow.rb
ADDED
@@ -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
|