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