thredded 0.15.5 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -7
- data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
- data/app/assets/stylesheets/thredded/components/_messageboard.scss +16 -3
- data/app/assets/stylesheets/thredded/utilities/_flex.scss +3 -0
- data/app/commands/thredded/mark_all_read.rb +3 -7
- data/app/controllers/thredded/messageboards_controller.rb +4 -1
- data/app/controllers/thredded/moderation_controller.rb +7 -2
- data/app/controllers/thredded/private_topics_controller.rb +1 -5
- data/app/controllers/thredded/theme_previews_controller.rb +5 -1
- data/app/controllers/thredded/topics_controller.rb +1 -1
- data/app/forms/thredded/post_form.rb +1 -1
- data/app/forms/thredded/private_post_form.rb +1 -1
- data/app/forms/thredded/private_topic_form.rb +2 -0
- data/app/forms/thredded/topic_form.rb +2 -1
- data/app/forms/thredded/user_preferences_form.rb +5 -1
- data/app/models/concerns/thredded/post_common.rb +10 -6
- data/app/models/concerns/thredded/topic_common.rb +4 -8
- data/app/models/concerns/thredded/user_topic_read_state_common.rb +56 -62
- data/app/models/thredded/messageboard.rb +27 -1
- data/app/models/thredded/post.rb +6 -0
- data/app/models/thredded/private_topic.rb +12 -2
- data/app/models/thredded/topic.rb +8 -3
- data/app/models/thredded/user_extender.rb +0 -7
- data/app/models/thredded/user_private_topic_read_state.rb +35 -0
- data/app/models/thredded/user_topic_read_state.rb +41 -0
- data/app/view_models/thredded/messageboard_group_view.rb +24 -7
- data/app/view_models/thredded/messageboard_view.rb +50 -0
- data/app/views/thredded/messageboards/_messageboard.html.erb +8 -18
- data/app/views/thredded/messageboards/index.html.erb +6 -1
- data/app/views/thredded/messageboards/messageboard/_description.html.erb +1 -0
- data/app/views/thredded/messageboards/messageboard/_header.html.erb +6 -0
- data/app/views/thredded/messageboards/messageboard/_last_updated.html.erb +7 -0
- data/app/views/thredded/messageboards/messageboard/_meta.html.erb +19 -0
- data/app/views/thredded/messageboards/messageboard/_unread_followed_topics_badge.html.erb +6 -0
- data/app/views/thredded/preferences/_messageboards_nav_item.html.erb +1 -1
- data/app/views/thredded/private_posts/_private_post.html.erb +1 -1
- data/app/views/thredded/theme_previews/show.html.erb +10 -4
- data/app/views/thredded/topics/_topic.html.erb +1 -1
- data/app/views/thredded/topics/unread.html.erb +1 -1
- data/config/locales/de.yml +1 -0
- data/config/locales/en.yml +2 -1
- data/config/locales/es.yml +1 -0
- data/config/locales/fr.yml +1 -0
- data/config/locales/it.yml +1 -0
- data/config/locales/pl.yml +1 -0
- data/config/locales/pt-BR.yml +1 -0
- data/config/locales/ru.yml +1 -0
- data/config/locales/zh-CN.yml +1 -0
- data/db/migrate/20160329231848_create_thredded.rb +5 -0
- data/db/upgrade_migrations/20180930063614_upgrade_thredded_v0_15_to_v0_16.rb +40 -0
- data/lib/generators/thredded/install/templates/initializer.rb +4 -4
- data/lib/thredded/arel_compat.rb +18 -24
- data/lib/thredded/content_formatter.rb +1 -1
- data/lib/thredded/database_seeder.rb +7 -6
- data/lib/thredded/version.rb +1 -1
- metadata +24 -18
- data/app/views/thredded/messageboards/_messageboard_meta.html.erb +0 -13
- data/lib/tasks/thredded_tasks.rake +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a642798592ef42f04c27440bdeb2b9930273fca1bbb2cb41da3138dd10af429c
|
4
|
+
data.tar.gz: 78e334145b31fc55950554b3909864fbe6617a262fbfa07006eec2262ab526bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b134c56f3c79b38ed2b13b42e45c6c5fa7dbb54258ab941980fa7791077a8f1fdc1e76ba24d0c5658478ce762beddaf080133fe16fbd8684c467f2d1efbbe80
|
7
|
+
data.tar.gz: 560bfe2c2922595f5a93728bffb7becb25a32a39fa4ddc209ff8f5778c28ccc87cd46b0a08d106383cf2bb440c83f1a7a532bd0247683ff2e63d30028ca99e3f
|
data/README.md
CHANGED
@@ -95,7 +95,7 @@ Then, see the rest of this Readme for more information about using and customizi
|
|
95
95
|
Add the gem to your Gemfile:
|
96
96
|
|
97
97
|
```ruby
|
98
|
-
gem 'thredded', '~> 0.
|
98
|
+
gem 'thredded', '~> 0.16.0'
|
99
99
|
```
|
100
100
|
|
101
101
|
Add the Thredded [initializer] to your parent app by running the install generator.
|
@@ -104,12 +104,6 @@ Add the Thredded [initializer] to your parent app by running the install generat
|
|
104
104
|
rails generate thredded:install
|
105
105
|
```
|
106
106
|
|
107
|
-
Copy emoji images to your `public/emoji` directory.
|
108
|
-
|
109
|
-
```console
|
110
|
-
rake thredded:install:emoji
|
111
|
-
```
|
112
|
-
|
113
107
|
Thredded needs to know the base application User model name and certain columns on it. Configure
|
114
108
|
these in the initializer installed with the command above.
|
115
109
|
|
@@ -33,7 +33,6 @@
|
|
33
33
|
float: left;
|
34
34
|
font-size: 1.125rem; // 18px
|
35
35
|
line-height: 1.2;
|
36
|
-
margin-right: $thredded-small-spacing;
|
37
36
|
vertical-align: baseline;
|
38
37
|
}
|
39
38
|
|
@@ -66,6 +65,19 @@
|
|
66
65
|
vertical-align: baseline;
|
67
66
|
}
|
68
67
|
|
68
|
+
.thredded--messageboard--unread-followed-topics-count {
|
69
|
+
@extend %thredded--nav-tabs--item--badge;
|
70
|
+
align-self: baseline;
|
71
|
+
line-height: inherit;
|
72
|
+
display: flex;
|
73
|
+
}
|
74
|
+
|
75
|
+
.thredded--messageboard--unread-followed-icon {
|
76
|
+
fill: currentColor;
|
77
|
+
width: 1rem;
|
78
|
+
height: 1rem;
|
79
|
+
}
|
80
|
+
|
69
81
|
.thredded--messageboard--description {
|
70
82
|
@extend %thredded--paragraph;
|
71
83
|
clear: both;
|
@@ -96,7 +108,9 @@
|
|
96
108
|
&--header {
|
97
109
|
display: flex;
|
98
110
|
flex-wrap: wrap;
|
99
|
-
|
111
|
+
> .thredded--flex-spacer {
|
112
|
+
margin-right: $thredded-small-spacing;
|
113
|
+
}
|
100
114
|
}
|
101
115
|
&--meta {
|
102
116
|
text-align: right;
|
@@ -149,7 +163,6 @@
|
|
149
163
|
margin-bottom: $margin-y;
|
150
164
|
padding: $thredded-messageboards-grid-item-padding-y $thredded-messageboards-grid-item-padding-x;
|
151
165
|
}
|
152
|
-
|
153
166
|
}
|
154
167
|
}
|
155
168
|
}
|
@@ -1,15 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Thredded
|
4
|
+
# Marks all private topics as read for the given user.
|
4
5
|
class MarkAllRead
|
5
6
|
def self.run(user)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
unread_topics.each do |topic|
|
10
|
-
last_post = topic.posts.order_oldest_first.last
|
11
|
-
|
12
|
-
Thredded::UserPrivateTopicReadState.touch!(user.id, topic.id, last_post)
|
7
|
+
Thredded::PrivateTopic.unread(user).each do |topic|
|
8
|
+
Thredded::UserPrivateTopicReadState.touch!(user.id, topic.last_post)
|
13
9
|
end
|
14
10
|
end
|
15
11
|
end
|
@@ -8,7 +8,10 @@ module Thredded
|
|
8
8
|
after_action :verify_policy_scoped, except: %i[new create edit update]
|
9
9
|
|
10
10
|
def index
|
11
|
-
@groups = Thredded::MessageboardGroupView.grouped(
|
11
|
+
@groups = Thredded::MessageboardGroupView.grouped(
|
12
|
+
policy_scope(Thredded::Messageboard.all),
|
13
|
+
user: thredded_current_user
|
14
|
+
)
|
12
15
|
end
|
13
16
|
|
14
17
|
def new
|
@@ -54,8 +54,13 @@ module Thredded
|
|
54
54
|
|
55
55
|
def users
|
56
56
|
@users = Thredded.user_class
|
57
|
-
.
|
58
|
-
.merge(
|
57
|
+
.eager_load(:thredded_user_detail)
|
58
|
+
.merge(
|
59
|
+
Thredded::UserDetail.order(
|
60
|
+
Arel.sql('COALESCE(thredded_user_details.moderation_state, 0) ASC,'\
|
61
|
+
'thredded_user_details.moderation_state_changed_at DESC')
|
62
|
+
)
|
63
|
+
)
|
59
64
|
@query = params[:q].to_s
|
60
65
|
@users = DbTextSearch::CaseInsensitive.new(@users, Thredded.user_name_column).prefix(@query) if @query.present?
|
61
66
|
@users = @users.page(current_page)
|
@@ -32,11 +32,7 @@ module Thredded
|
|
32
32
|
.page(current_page)
|
33
33
|
return redirect_to(last_page_params(page_scope)) if page_beyond_last?(page_scope)
|
34
34
|
@posts = Thredded::TopicPostsPageView.new(thredded_current_user, private_topic, page_scope)
|
35
|
-
|
36
|
-
if thredded_signed_in?
|
37
|
-
Thredded::UserPrivateTopicReadState.touch!(thredded_current_user.id, private_topic.id, page_scope.last)
|
38
|
-
end
|
39
|
-
|
35
|
+
Thredded::UserPrivateTopicReadState.touch!(thredded_current_user.id, page_scope.last) if thredded_signed_in?
|
40
36
|
@new_post = Thredded::PrivatePostForm.new(
|
41
37
|
user: thredded_current_user, topic: private_topic, post_params: new_private_post_params
|
42
38
|
)
|
@@ -10,7 +10,11 @@ module Thredded
|
|
10
10
|
else
|
11
11
|
thredded_current_user
|
12
12
|
end
|
13
|
-
@
|
13
|
+
@messageboard_views = [
|
14
|
+
Thredded::MessageboardView.new(@messageboard, unread_topics_count: 3, unread_followed_topics_count: 2),
|
15
|
+
Thredded::MessageboardView.new(Thredded::Messageboard.first(2)[-1], unread_topics_count: 2),
|
16
|
+
Thredded::MessageboardView.new(Thredded::Messageboard.first(3)[-1]),
|
17
|
+
]
|
14
18
|
@topics = Thredded::TopicsPageView.new(@user, @messageboard.topics.page(1).limit(3))
|
15
19
|
@private_topics = Thredded::PrivateTopicsPageView.new(@user, @user.thredded_private_topics.page(1).limit(3))
|
16
20
|
topic = Thredded::Topic.new(messageboard: @messageboard, title: 'Hello', slug: 'hello', user: @user)
|
@@ -60,7 +60,7 @@ module Thredded
|
|
60
60
|
.page(current_page)
|
61
61
|
return redirect_to(last_page_params(page_scope)) if page_beyond_last?(page_scope)
|
62
62
|
@posts = Thredded::TopicPostsPageView.new(thredded_current_user, topic, page_scope)
|
63
|
-
Thredded::UserTopicReadState.touch!(thredded_current_user.id,
|
63
|
+
Thredded::UserTopicReadState.touch!(thredded_current_user.id, page_scope.last) if thredded_signed_in?
|
64
64
|
@new_post = Thredded::PostForm.new(user: thredded_current_user, topic: topic, post_params: new_post_params)
|
65
65
|
end
|
66
66
|
|
@@ -49,7 +49,7 @@ module Thredded
|
|
49
49
|
return false unless @post.valid?
|
50
50
|
was_persisted = @post.persisted?
|
51
51
|
@post.save!
|
52
|
-
Thredded::UserTopicReadState.touch!(@post.user.id, @
|
52
|
+
Thredded::UserTopicReadState.touch!(@post.user.id, @post) unless was_persisted
|
53
53
|
true
|
54
54
|
end
|
55
55
|
end
|
@@ -45,7 +45,7 @@ module Thredded
|
|
45
45
|
return false unless @post.valid?
|
46
46
|
was_persisted = @post.persisted?
|
47
47
|
@post.save!
|
48
|
-
Thredded::UserPrivateTopicReadState.touch!(@post.user.id, @
|
48
|
+
Thredded::UserPrivateTopicReadState.touch!(@post.user.id, @post) unless was_persisted
|
49
49
|
true
|
50
50
|
end
|
51
51
|
end
|
@@ -44,8 +44,10 @@ module Thredded
|
|
44
44
|
return false unless valid?
|
45
45
|
|
46
46
|
ActiveRecord::Base.transaction do
|
47
|
+
new_topic = !private_topic.persisted?
|
47
48
|
private_topic.save!
|
48
49
|
post.save!
|
50
|
+
Thredded::UserPrivateTopicReadState.read_on_first_post!(user, post) if new_topic
|
49
51
|
end
|
50
52
|
true
|
51
53
|
end
|
@@ -31,9 +31,10 @@ module Thredded
|
|
31
31
|
return false unless valid?
|
32
32
|
|
33
33
|
ActiveRecord::Base.transaction do
|
34
|
+
new_topic = !topic.persisted?
|
34
35
|
topic.save!
|
35
36
|
post.save!
|
36
|
-
Thredded::UserTopicReadState.read_on_first_post!(user,
|
37
|
+
Thredded::UserTopicReadState.read_on_first_post!(user, post) if new_topic
|
37
38
|
end
|
38
39
|
true
|
39
40
|
end
|
@@ -50,7 +50,11 @@ module Thredded
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def messageboard_groups
|
53
|
-
@messageboard_groups ||= Thredded::MessageboardGroupView.grouped(
|
53
|
+
@messageboard_groups ||= Thredded::MessageboardGroupView.grouped(
|
54
|
+
@messageboards,
|
55
|
+
user: @user,
|
56
|
+
with_unread_topics_counts: false
|
57
|
+
)
|
54
58
|
end
|
55
59
|
|
56
60
|
def notifications_for_private_topics
|
@@ -19,6 +19,8 @@ module Thredded
|
|
19
19
|
scope :order_newest_first, -> { order(created_at: :desc, id: :desc) }
|
20
20
|
|
21
21
|
before_validation :ensure_user_detail, on: :create
|
22
|
+
|
23
|
+
after_commit :update_unread_posts_count, on: %i[create destroy]
|
22
24
|
end
|
23
25
|
|
24
26
|
def avatar_url
|
@@ -48,17 +50,13 @@ module Thredded
|
|
48
50
|
end
|
49
51
|
|
50
52
|
# Marks all the posts from the given one as unread for the given user
|
51
|
-
# @param
|
52
|
-
# @param page [Integer]
|
53
|
+
# @param [Thredded.user_class] user
|
53
54
|
def mark_as_unread(user)
|
54
55
|
if previous_post.nil?
|
55
56
|
read_state = postable.user_read_states.find_by(user_id: user.id)
|
56
57
|
read_state.destroy if read_state
|
57
58
|
else
|
58
|
-
|
59
|
-
read_at: previous_post.created_at
|
60
|
-
).find_or_create_by(user_id: user.id)
|
61
|
-
read_state.update_columns(read_at: previous_post.created_at)
|
59
|
+
postable.user_read_states.touch!(user.id, previous_post, overwrite_newer: true)
|
62
60
|
end
|
63
61
|
end
|
64
62
|
|
@@ -66,6 +64,12 @@ module Thredded
|
|
66
64
|
@previous_post ||= postable.posts.order_newest_first.find_by('created_at < ?', created_at)
|
67
65
|
end
|
68
66
|
|
67
|
+
protected
|
68
|
+
|
69
|
+
def update_unread_posts_count
|
70
|
+
postable.user_read_states.update_post_counts!
|
71
|
+
end
|
72
|
+
|
69
73
|
private
|
70
74
|
|
71
75
|
def ensure_user_detail
|
@@ -50,16 +50,12 @@ module Thredded
|
|
50
50
|
# @param user [Thredded.user_class]
|
51
51
|
# @return [ActiveRecord::Relation]
|
52
52
|
def unread(user)
|
53
|
-
topics
|
53
|
+
topics = arel_table
|
54
54
|
reads_class = reflect_on_association(:user_read_states).klass
|
55
|
-
reads
|
55
|
+
reads = reads_class.arel_table
|
56
56
|
joins(topics.join(reads, Arel::Nodes::OuterJoin)
|
57
57
|
.on(topics[:id].eq(reads[:postable_id]).and(reads[:user_id].eq(user.id))).join_sources)
|
58
|
-
.merge(reads_class.where(reads[:id].eq(nil).or(reads[:
|
59
|
-
end
|
60
|
-
|
61
|
-
def post_class
|
62
|
-
reflect_on_association(:posts).klass
|
58
|
+
.merge(reads_class.where(reads[:id].eq(nil).or(reads[:unread_posts_count].not_eq(0))))
|
63
59
|
end
|
64
60
|
|
65
61
|
private
|
@@ -69,7 +65,7 @@ module Thredded
|
|
69
65
|
def read_states_by_postable_hash(user)
|
70
66
|
read_states = reflect_on_association(:user_read_states).klass
|
71
67
|
.where(user_id: user.id, postable_id: current_scope.map(&:id))
|
72
|
-
.with_page_info
|
68
|
+
.with_page_info
|
73
69
|
Thredded::TopicCommon::CachingHash.from_relation(read_states)
|
74
70
|
end
|
75
71
|
|
@@ -21,79 +21,73 @@ module Thredded
|
|
21
21
|
post.created_at <= read_at
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
# Upsert gem: https://github.com/seamusabshere/upsert
|
32
|
-
state = find_or_initialize_by(user_id: user_id, postable_id: topic_id)
|
33
|
-
return unless !state.read_at? || state.read_at < post.created_at
|
34
|
-
state.update!(read_at: post.created_at)
|
35
|
-
end
|
24
|
+
def calculate_post_counts
|
25
|
+
unread_posts_count, read_posts_count =
|
26
|
+
self.class.visible_posts_scope(user)
|
27
|
+
.where(postable_id: postable_id)
|
28
|
+
.pluck(*self.class.post_counts_arel(read_at))[0]
|
29
|
+
{ unread_posts_count: unread_posts_count || 0, read_posts_count: read_posts_count || 0 }
|
30
|
+
end
|
36
31
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
32
|
+
module ClassMethods
|
33
|
+
delegate :post_class, to: :topic_class
|
40
34
|
|
41
35
|
# Adds `first_unread_post_page` and `last_read_post_page` columns onto the scope.
|
42
36
|
# Skips the records that have no read posts.
|
43
|
-
def with_page_info(
|
44
|
-
posts_per_page: post_class.default_per_page, posts_scope: post_class.all
|
45
|
-
)
|
37
|
+
def with_page_info(posts_per_page: post_class.default_per_page)
|
46
38
|
states = arel_table
|
47
|
-
|
48
|
-
if
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
states_select_manager = Thredded::ArelCompat.new_arel_select_manager(
|
53
|
-
Arel::Nodes::TableAlias.new(Thredded::ArelCompat.relation_to_arel(self_relation), table_name)
|
54
|
-
)
|
55
|
-
end
|
56
|
-
read = if posts_scope == post_class.unscoped
|
57
|
-
post_class.arel_table
|
58
|
-
else
|
59
|
-
posts_subquery = Thredded::ArelCompat.relation_to_arel(posts_scope)
|
60
|
-
Arel::Nodes::TableAlias.new(posts_subquery, 'read_posts')
|
61
|
-
end
|
62
|
-
unread_topics = topic_class.arel_table
|
63
|
-
page_info =
|
64
|
-
states_select_manager
|
65
|
-
.project(
|
66
|
-
states[:id],
|
67
|
-
Arel::Nodes::Case.new(unread_topics[:id].not_eq(nil))
|
68
|
-
.when(Thredded::ArelCompat.true_value(self)).then(
|
69
|
-
Arel::Nodes::Addition.new(
|
70
|
-
Thredded::ArelCompat.integer_division(self, read[:id].count, posts_per_page), 1
|
71
|
-
)
|
72
|
-
).else(nil)
|
73
|
-
.as('first_unread_post_page'),
|
39
|
+
selects = []
|
40
|
+
selects << states[Arel.star] if !is_a?(ActiveRecord::Relation) || select_values.empty?
|
41
|
+
selects += [
|
42
|
+
Arel::Nodes::Case.new(states[:unread_posts_count].not_eq(0))
|
43
|
+
.when(Thredded::ArelCompat.true_value(self)).then(
|
74
44
|
Arel::Nodes::Addition.new(
|
75
|
-
Thredded::ArelCompat.integer_division(self,
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
)
|
80
|
-
.
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
45
|
+
Thredded::ArelCompat.integer_division(self, states[:read_posts_count], posts_per_page), 1
|
46
|
+
)
|
47
|
+
).else(nil).as('first_unread_post_page'),
|
48
|
+
Arel::Nodes::Addition.new(
|
49
|
+
Thredded::ArelCompat.integer_division(self, states[:read_posts_count], posts_per_page),
|
50
|
+
Arel::Nodes::Case.new(Arel::Nodes::InfixOperation.new(:%, states[:read_posts_count], posts_per_page))
|
51
|
+
.when(0).then(0).else(1)
|
52
|
+
).as('last_read_post_page')
|
53
|
+
]
|
54
|
+
select(selects)
|
55
|
+
end
|
86
56
|
|
87
|
-
|
88
|
-
|
89
|
-
|
57
|
+
# Calculates and saves the `unread_posts_count` and `read_posts_count` columns.
|
58
|
+
def update_post_counts!
|
59
|
+
id_counts = calculate_post_counts_for_users(Thredded.user_class.where(id: distinct.select(:user_id)))
|
60
|
+
transaction do
|
61
|
+
id_counts.each do |(id, unread_posts_count, read_posts_count)|
|
62
|
+
where(id: id).update_all(unread_posts_count: unread_posts_count, read_posts_count: read_posts_count)
|
63
|
+
end
|
64
|
+
end
|
90
65
|
end
|
91
66
|
|
92
|
-
|
93
|
-
|
67
|
+
# @param [DateTime, Arel::Node] read_at
|
68
|
+
# @param [Arel::Table] posts
|
69
|
+
# @return [[Arel::Node, Arel::Node]] `unread_posts_count` and `read_posts_count` nodes.
|
70
|
+
def post_counts_arel(read_at, posts: post_class.arel_table)
|
71
|
+
[
|
72
|
+
Arel::Nodes::Sum.new(
|
73
|
+
[Arel::Nodes::Case.new(posts[:created_at].gt(read_at))
|
74
|
+
.when(Thredded::ArelCompat.true_value(self)).then(1).else(0)]
|
75
|
+
).as('unread_posts_count'),
|
76
|
+
Arel::Nodes::Sum.new(
|
77
|
+
[Arel::Nodes::Case.new(posts[:created_at].gt(read_at))
|
78
|
+
.when(Thredded::ArelCompat.true_value(self)).then(0).else(1)]
|
79
|
+
).as('read_posts_count')
|
80
|
+
]
|
94
81
|
end
|
95
82
|
|
96
|
-
|
83
|
+
# @return [Array<[id, unread_posts_count, read_posts_count]>]
|
84
|
+
def calculate_post_counts
|
85
|
+
states = arel_table
|
86
|
+
posts = post_class.arel_table
|
87
|
+
joins(states.join(posts).on(states[:postable_id].eq(posts[:postable_id])).join_sources)
|
88
|
+
.group(states[:id])
|
89
|
+
.pluck(states[:id], *post_counts_arel(states[:read_at], posts: posts))
|
90
|
+
end
|
97
91
|
end
|
98
92
|
end
|
99
93
|
end
|
@@ -4,7 +4,7 @@ module Thredded
|
|
4
4
|
class Messageboard < ActiveRecord::Base
|
5
5
|
extend FriendlyId
|
6
6
|
friendly_id :slug_candidates,
|
7
|
-
use:
|
7
|
+
use: %i[slugged reserved],
|
8
8
|
# Avoid route conflicts
|
9
9
|
reserved_words: ::Thredded::FriendlyIdReservedWordsAndPagination.new(
|
10
10
|
%w[
|
@@ -51,6 +51,11 @@ module Thredded
|
|
51
51
|
through: :recently_active_user_details,
|
52
52
|
source: :user
|
53
53
|
|
54
|
+
has_many :user_topic_read_states,
|
55
|
+
class_name: 'Thredded::UserTopicReadState',
|
56
|
+
inverse_of: :messageboard,
|
57
|
+
dependent: :delete_all
|
58
|
+
|
54
59
|
belongs_to :group,
|
55
60
|
inverse_of: :messageboards,
|
56
61
|
foreign_key: :messageboard_group_id,
|
@@ -114,5 +119,26 @@ module Thredded
|
|
114
119
|
[:name, '-board']
|
115
120
|
]
|
116
121
|
end
|
122
|
+
|
123
|
+
class << self
|
124
|
+
# @param [Thredded.user_class] user
|
125
|
+
# @param [ActiveRecord::Relation<Thredded::Topic>] topics_scope
|
126
|
+
def unread_topics_counts(user:, topics_scope: Thredded::Topic.all)
|
127
|
+
messageboards = arel_table
|
128
|
+
read_states = Thredded::UserTopicReadState.arel_table
|
129
|
+
topics = topics_scope.arel_table
|
130
|
+
joins(:topics).merge(topics_scope).joins(
|
131
|
+
messageboards.outer_join(read_states).on(
|
132
|
+
messageboards[:id].eq(read_states[:messageboard_id])
|
133
|
+
.and(read_states[:postable_id].eq(topics[:id]))
|
134
|
+
.and(read_states[:user_id].eq(user.id))
|
135
|
+
.and(read_states[:unread_posts_count].eq(0))
|
136
|
+
).join_sources
|
137
|
+
).group(messageboards[:id]).pluck(
|
138
|
+
:id,
|
139
|
+
Arel::Nodes::Subtraction.new(topics[:id].count, read_states[:id].count)
|
140
|
+
).to_h
|
141
|
+
end
|
142
|
+
end
|
117
143
|
end
|
118
144
|
end
|