thredded 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.mkdn +26 -0
- data/README.mkdn +52 -7
- data/app/assets/images/thredded/moderation.svg +4 -0
- data/app/assets/images/thredded/private-messages.svg +1 -1
- data/app/assets/images/thredded/settings.svg +1 -1
- data/app/assets/stylesheets/thredded/_base.scss +1 -1
- data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
- data/app/assets/stylesheets/thredded/base/_buttons.scss +16 -0
- data/app/assets/stylesheets/thredded/base/_grid.scss +12 -0
- data/app/assets/stylesheets/thredded/components/_base.scss +4 -0
- data/app/assets/stylesheets/thredded/components/_messageboard.scss +1 -13
- data/app/assets/stylesheets/thredded/components/_topic-header.scss +27 -0
- data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +1 -1
- data/app/assets/stylesheets/thredded/layout/_moderation.scss +45 -0
- data/app/assets/stylesheets/thredded/layout/_navigation.scss +20 -6
- data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +2 -2
- data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +5 -3
- data/app/commands/thredded/autofollow_mentioned_users.rb +32 -0
- data/app/commands/thredded/moderate_post.rb +35 -0
- data/app/commands/thredded/notify_following_users.rb +18 -0
- data/app/commands/thredded/notify_private_topic_users.rb +4 -4
- data/app/controllers/thredded/application_controller.rb +6 -25
- data/app/controllers/thredded/messageboards_controller.rb +5 -3
- data/app/controllers/thredded/moderation_controller.rb +56 -0
- data/app/controllers/thredded/post_permalinks_controller.rb +1 -1
- data/app/controllers/thredded/posts_controller.rb +4 -2
- data/app/controllers/thredded/private_post_permalinks_controller.rb +1 -1
- data/app/controllers/thredded/private_topics_controller.rb +2 -3
- data/app/controllers/thredded/theme_previews_controller.rb +3 -3
- data/app/controllers/thredded/topics_controller.rb +32 -11
- data/app/forms/thredded/topic_form.rb +1 -0
- data/app/helpers/thredded/application_helper.rb +26 -1
- data/app/helpers/thredded/urls_helper.rb +7 -5
- data/app/jobs/thredded/auto_follow_and_notify_job.rb +13 -0
- data/app/jobs/thredded/notify_private_topic_users_job.rb +3 -4
- data/app/mailer_previews/thredded/post_mailer_preview.rb +2 -2
- data/app/mailers/thredded/post_mailer.rb +2 -2
- data/app/models/concerns/thredded/content_moderation_state.rb +53 -0
- data/app/models/concerns/thredded/moderation_state.rb +13 -0
- data/app/models/concerns/thredded/post_common.rb +6 -71
- data/app/models/concerns/thredded/topic_common.rb +26 -11
- data/app/models/concerns/thredded/user_topic_read_state_common.rb +4 -0
- data/app/models/thredded/messageboard.rb +2 -0
- data/app/models/thredded/post.rb +24 -0
- data/app/models/thredded/post_moderation_record.rb +45 -0
- data/app/models/thredded/private_post.rb +15 -0
- data/app/models/thredded/private_topic.rb +8 -0
- data/app/models/thredded/topic.rb +39 -0
- data/app/models/thredded/user_detail.rb +11 -0
- data/app/models/thredded/user_extender.rb +14 -0
- data/app/models/thredded/user_topic_follow.rb +20 -0
- data/app/policies/thredded/messageboard_policy.rb +15 -0
- data/app/policies/thredded/post_policy.rb +17 -1
- data/app/policies/thredded/private_post_policy.rb +1 -1
- data/app/policies/thredded/private_topic_policy.rb +1 -1
- data/app/policies/thredded/topic_policy.rb +18 -1
- data/app/view_models/thredded/base_topic_view.rb +0 -13
- data/app/view_models/thredded/post_view.rb +8 -1
- data/app/view_models/thredded/posts_page_view.rb +6 -8
- data/app/view_models/thredded/private_topic_view.rb +8 -0
- data/app/view_models/thredded/private_topics_page_view.rb +24 -0
- data/app/view_models/thredded/topic_posts_page_view.rb +17 -0
- data/app/view_models/thredded/topic_view.rb +27 -1
- data/app/view_models/thredded/topics_page_view.rb +2 -4
- data/app/views/thredded/moderation/_post.html.erb +7 -0
- data/app/views/thredded/moderation/_post_moderation_actions.html.erb +12 -0
- data/app/views/thredded/moderation/_post_moderation_record.html.erb +43 -0
- data/app/views/thredded/moderation/history.html.erb +19 -0
- data/app/views/thredded/moderation/pending.html.erb +29 -0
- data/app/views/thredded/post_mailer/{at_notification.html.erb → post_notification.html.erb} +1 -1
- data/app/views/thredded/post_mailer/{at_notification.text.erb → post_notification.text.erb} +1 -1
- data/app/views/thredded/posts/_post.html.erb +8 -1
- data/app/views/thredded/posts_common/_actions.html.erb +11 -0
- data/app/views/thredded/posts_common/_content.html.erb +5 -0
- data/app/views/thredded/posts_common/_header.html.erb +5 -0
- data/app/views/thredded/private_posts/_private_post.html.erb +5 -1
- data/app/views/thredded/shared/_nav.html.erb +1 -0
- data/app/views/thredded/shared/_page.html.erb +1 -1
- data/app/views/thredded/shared/nav/_moderation.html.erb +13 -0
- data/app/views/thredded/shared/nav/_private_topics.html.erb +1 -2
- data/app/views/thredded/topics/_header.html.erb +15 -0
- data/app/views/thredded/topics/index.html.erb +2 -2
- data/app/views/thredded/topics/new.html.erb +1 -1
- data/config/locales/en.yml +26 -5
- data/config/locales/pt-BR.yml +23 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20160329231848_create_thredded.rb +40 -5
- data/db/seeds.rb +5 -5
- data/db/upgrade_migrations/20160429222452_upgrade_v0_3_to_v0_4.rb +1 -2
- data/db/upgrade_migrations/20160501151908_upgrade_v0_4_to_v0_5.rb +56 -0
- data/heroku.gemfile.lock +20 -18
- data/lib/generators/thredded/install/templates/initializer.rb +15 -0
- data/lib/html/pipeline/at_mention_filter.rb +5 -2
- data/lib/thredded.rb +4 -0
- data/lib/thredded/at_users.rb +3 -2
- data/lib/thredded/content_formatter.rb +81 -0
- data/lib/thredded/version.rb +1 -1
- metadata +28 -10
- data/app/commands/thredded/notify_mentioned_users.rb +0 -55
- data/app/jobs/thredded/at_notifier_job.rb +0 -12
- data/app/mailer_previews/thredded/private_post_mailer_preview.rb +0 -12
- data/app/mailers/thredded/private_post_mailer.rb +0 -17
- data/app/views/thredded/posts_common/_post.html.erb +0 -26
- data/app/views/thredded/private_post_mailer/at_notification.html.erb +0 -13
|
@@ -40,18 +40,20 @@ module Thredded
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# @param post [Post, PrivatePost]
|
|
43
|
+
# @param user [Thredded.user_class] the current user
|
|
43
44
|
# @return [String] URL of the topic page with the post anchor.
|
|
44
|
-
def post_url(post, params
|
|
45
|
+
def post_url(post, user:, **params)
|
|
45
46
|
params = params.dup
|
|
46
47
|
params[:anchor] ||= ActionView::RecordIdentifier.dom_id(post)
|
|
47
|
-
params[:page] ||= post.page
|
|
48
|
+
params[:page] ||= post.private_topic_post? ? post.page : post.page(user: user)
|
|
48
49
|
topic_url(post.postable, params)
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
# @param post [Post, PrivatePost]
|
|
53
|
+
# @param user [Thredded.user_class] the current user
|
|
52
54
|
# @return [String] path to the topic page with the post anchor.
|
|
53
|
-
def post_path(post, params
|
|
54
|
-
post_url(post, params.merge(only_path: true))
|
|
55
|
+
def post_path(post, user:, **params)
|
|
56
|
+
post_url(post, params.merge(user: user, only_path: true))
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
# @param post [Post, PrivatePost]
|
|
@@ -65,7 +67,7 @@ module Thredded
|
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
# @param post [Post, PrivatePost]
|
|
68
|
-
# @return [String] path to the DELETE
|
|
70
|
+
# @return [String] path to the DELETE endpoint.
|
|
69
71
|
def delete_post_path(post)
|
|
70
72
|
if post.private_topic_post?
|
|
71
73
|
private_topic_private_post_path(post.postable, post)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Thredded
|
|
3
|
+
class AutoFollowAndNotifyJob < ::ActiveJob::Base
|
|
4
|
+
queue_as :default
|
|
5
|
+
|
|
6
|
+
def perform(post_id)
|
|
7
|
+
post = Post.find(post_id)
|
|
8
|
+
|
|
9
|
+
AutofollowMentionedUsers.new(post).run
|
|
10
|
+
NotifyFollowingUsers.new(post).run
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -3,10 +3,9 @@ module Thredded
|
|
|
3
3
|
class NotifyPrivateTopicUsersJob < ::ActiveJob::Base
|
|
4
4
|
queue_as :default
|
|
5
5
|
|
|
6
|
-
def perform(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
NotifyPrivateTopicUsers.new(private_topic).run
|
|
6
|
+
def perform(private_post_id)
|
|
7
|
+
private_post = Thredded::PrivatePost.find(private_post_id)
|
|
8
|
+
NotifyPrivateTopicUsers.new(private_post).run
|
|
10
9
|
end
|
|
11
10
|
end
|
|
12
11
|
end
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
module Thredded
|
|
3
3
|
# Previews for the PostMailer
|
|
4
4
|
class PostMailerPreview < BaseMailerPreview
|
|
5
|
-
def
|
|
6
|
-
PostMailer.
|
|
5
|
+
def post_notification
|
|
6
|
+
PostMailer.post_notification(
|
|
7
7
|
mock_post(content: mock_content(mention_users: %w(glebm joel))),
|
|
8
8
|
%w(glebm@test.com joel@test.com)
|
|
9
9
|
)
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
require_dependency 'thredded/topic_email_view'
|
|
3
3
|
module Thredded
|
|
4
4
|
class PostMailer < Thredded::BaseMailer
|
|
5
|
-
def
|
|
5
|
+
def post_notification(post_id, emails)
|
|
6
6
|
@post = find_record Post, post_id
|
|
7
7
|
email_details = TopicEmailView.new(@post.postable)
|
|
8
|
-
headers['X-SMTPAPI'] = email_details.smtp_api_tag('
|
|
8
|
+
headers['X-SMTPAPI'] = email_details.smtp_api_tag('post_notification')
|
|
9
9
|
|
|
10
10
|
mail from: email_details.no_reply,
|
|
11
11
|
to: email_details.no_reply,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Thredded
|
|
3
|
+
# Moderation state of a piece of content, such as a Topic or a Post.
|
|
4
|
+
# Requires an integer moderation_state column, a user_id column, and a user_detail association on the including class.
|
|
5
|
+
# @api private
|
|
6
|
+
module ContentModerationState
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
include ModerationState
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
before_validation :set_default_moderation_state, on: :create
|
|
12
|
+
|
|
13
|
+
scope :moderation_state_visible_to_user, (lambda do |user|
|
|
14
|
+
# @type [Arel::Table]
|
|
15
|
+
table = arel_table
|
|
16
|
+
# @type [Arel::Nodes::Node]
|
|
17
|
+
visible_to_all =
|
|
18
|
+
if Thredded.content_visible_while_pending_moderation
|
|
19
|
+
table[:moderation_state].not_eq(moderation_states[:blocked])
|
|
20
|
+
else
|
|
21
|
+
table[:moderation_state].eq(moderation_states[:approved])
|
|
22
|
+
end
|
|
23
|
+
where(
|
|
24
|
+
if user && !user.thredded_anonymous?
|
|
25
|
+
visible_to_all.or(table[:user_id].eq(user.id))
|
|
26
|
+
else
|
|
27
|
+
visible_to_all
|
|
28
|
+
end
|
|
29
|
+
)
|
|
30
|
+
end)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Whether this is visible to anyone based on the moderation state.
|
|
34
|
+
def moderation_state_visible_to_all?
|
|
35
|
+
if Thredded.content_visible_while_pending_moderation
|
|
36
|
+
!blocked?
|
|
37
|
+
else
|
|
38
|
+
approved?
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Whether this is visible to the given user based on the moderation state.
|
|
43
|
+
def moderation_state_visible_to_user?(user)
|
|
44
|
+
moderation_state_visible_to_all? || (!user.thredded_anonymous? && user_id == user.id)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def set_default_moderation_state
|
|
50
|
+
self.moderation_state ||= user_detail.moderation_state
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Thredded
|
|
3
|
+
# Defines a moderation_state enum
|
|
4
|
+
# Requires an integer moderation_state column on the including class.
|
|
5
|
+
module ModerationState
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
enum moderation_state: %i(pending_moderation approved blocked)
|
|
10
|
+
validates :moderation_state, presence: true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -1,42 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require_dependency 'thredded/content_formatter'
|
|
2
3
|
module Thredded
|
|
3
4
|
module PostCommon
|
|
4
5
|
extend ActiveSupport::Concern
|
|
5
6
|
|
|
6
|
-
WHITELIST_TRANSFORMERS = HTML::Pipeline::SanitizationFilter::WHITELIST[:transformers] + [
|
|
7
|
-
lambda do |env|
|
|
8
|
-
node = env[:node]
|
|
9
|
-
|
|
10
|
-
a_tags = node.css('a')
|
|
11
|
-
a_tags.each do |a_tag|
|
|
12
|
-
if a_tag['href'].starts_with? 'http'
|
|
13
|
-
a_tag['target'] = '_blank'
|
|
14
|
-
a_tag['rel'] = 'nofollow noopener'
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
].freeze
|
|
19
|
-
|
|
20
|
-
WHITELIST_ELEMENTS = HTML::Pipeline::SanitizationFilter::WHITELIST[:elements] + %w(
|
|
21
|
-
iframe span
|
|
22
|
-
).freeze
|
|
23
|
-
|
|
24
|
-
WHITELIST = HTML::Pipeline::SanitizationFilter::WHITELIST.deep_merge(
|
|
25
|
-
elements: WHITELIST_ELEMENTS,
|
|
26
|
-
transformers: WHITELIST_TRANSFORMERS,
|
|
27
|
-
attributes: {
|
|
28
|
-
'a' => %w(href rel),
|
|
29
|
-
'iframe' => %w(src width height frameborder allowfullscreen sandbox seamless),
|
|
30
|
-
'span' => %w(class),
|
|
31
|
-
},
|
|
32
|
-
add_attributes: {
|
|
33
|
-
'iframe' => {
|
|
34
|
-
'seamless' => 'seamless',
|
|
35
|
-
'sandbox' => 'allow-forms allow-scripts',
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
).freeze
|
|
39
|
-
|
|
40
7
|
included do
|
|
41
8
|
paginates_per 50
|
|
42
9
|
|
|
@@ -49,11 +16,6 @@ module Thredded
|
|
|
49
16
|
scope :order_oldest_first, -> { order(id: :asc) }
|
|
50
17
|
|
|
51
18
|
after_commit :update_parent_last_user_and_timestamp, on: [:create, :destroy]
|
|
52
|
-
after_commit :notify_at_users, on: [:create, :update]
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def page(per_page: self.class.default_per_page)
|
|
56
|
-
1 + postable.posts.where('id < ?', id).count / per_page
|
|
57
19
|
end
|
|
58
20
|
|
|
59
21
|
def avatar_url
|
|
@@ -61,35 +23,12 @@ module Thredded
|
|
|
61
23
|
end
|
|
62
24
|
|
|
63
25
|
# @param view_context [Object] the context of the rendering view.
|
|
26
|
+
# @return [String] formatted and sanitized html-safe post content.
|
|
64
27
|
def filtered_content(view_context)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
protected
|
|
71
|
-
|
|
72
|
-
# @return [Array<HTML::Pipeline::Filter]>]
|
|
73
|
-
def content_pipeline_filters
|
|
74
|
-
[
|
|
75
|
-
HTML::Pipeline::VimeoFilter,
|
|
76
|
-
HTML::Pipeline::YoutubeFilter,
|
|
77
|
-
HTML::Pipeline::BbcodeFilter,
|
|
78
|
-
HTML::Pipeline::MarkdownFilter,
|
|
79
|
-
HTML::Pipeline::SanitizationFilter,
|
|
80
|
-
HTML::Pipeline::AtMentionFilter,
|
|
81
|
-
HTML::Pipeline::EmojiFilter,
|
|
82
|
-
HTML::Pipeline::AutolinkFilter,
|
|
83
|
-
]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# @return [Hash] options for HTML::Pipeline.new
|
|
87
|
-
def content_pipeline_options
|
|
88
|
-
{
|
|
89
|
-
asset_root: Rails.application.config.action_controller.asset_host || '',
|
|
90
|
-
post: self,
|
|
91
|
-
whitelist: WHITELIST,
|
|
92
|
-
}
|
|
28
|
+
Thredded::ContentFormatter.new(
|
|
29
|
+
view_context,
|
|
30
|
+
users_provider: -> (names) { readers_from_user_names(names) }
|
|
31
|
+
).format_content(content)
|
|
93
32
|
end
|
|
94
33
|
|
|
95
34
|
private
|
|
@@ -103,9 +42,5 @@ module Thredded
|
|
|
103
42
|
end
|
|
104
43
|
postable.update!(last_user_id: last_post.user_id, updated_at: last_post.created_at)
|
|
105
44
|
end
|
|
106
|
-
|
|
107
|
-
def notify_at_users
|
|
108
|
-
AtNotifierJob.perform_later(self.class.name, id)
|
|
109
|
-
end
|
|
110
45
|
end
|
|
111
46
|
end
|
|
@@ -47,24 +47,39 @@ module Thredded
|
|
|
47
47
|
.merge(reads_class.where(reads[:id].eq(nil).or(reads[:read_at].lt(topics[:updated_at]))))
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# @param user [Thredded.user_class]
|
|
53
|
+
# @return [ByPostableLookup]
|
|
54
|
+
def read_states_by_postable_hash(user)
|
|
55
|
+
read_states = reflect_on_association(:user_read_states).klass
|
|
56
|
+
.where(user_id: user.id, postable_id: current_scope.map(&:id))
|
|
57
|
+
Thredded::TopicCommon::CachingHash.from_relation(read_states)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
public
|
|
61
|
+
|
|
50
62
|
# @param user [Thredded.user_class]
|
|
51
63
|
# @return [Array<[TopicCommon, UserTopicReadStateCommon]>]
|
|
52
64
|
def with_read_states(user)
|
|
53
65
|
null_read_state = Thredded::NullUserTopicReadState.new
|
|
54
66
|
return current_scope.zip([null_read_state]) if user.thredded_anonymous?
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.group_by(&:postable_id)
|
|
59
|
-
current_scope.map do |topic|
|
|
60
|
-
read_state = read_state_by_topic_id[topic.id]
|
|
61
|
-
if read_state
|
|
62
|
-
read_state = read_state[0]
|
|
63
|
-
read_state.postable = topic
|
|
64
|
-
end
|
|
65
|
-
[topic, read_state || null_read_state]
|
|
67
|
+
read_states_by_postable = read_states_by_postable_hash(user)
|
|
68
|
+
current_scope.map do |postable|
|
|
69
|
+
[postable, read_states_by_postable[postable] || null_read_state]
|
|
66
70
|
end
|
|
67
71
|
end
|
|
68
72
|
end
|
|
73
|
+
|
|
74
|
+
class CachingHash < Hash
|
|
75
|
+
def self.from_relation(postable_relation)
|
|
76
|
+
self[postable_relation.map { |related| [related.postable_id, related] }]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# lookup related item by postable and set the inverse lookup
|
|
80
|
+
def [](postable)
|
|
81
|
+
super(postable.id).tap { |related| related.postable = postable if related }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
69
84
|
end
|
|
70
85
|
end
|
|
@@ -26,6 +26,10 @@ module Thredded
|
|
|
26
26
|
return unless !state.read_at? || state.read_at < post.updated_at
|
|
27
27
|
state.update!(read_at: post.updated_at, page: post_page)
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
def read_on_first_post!(user, topic)
|
|
31
|
+
create!(user: user, postable: topic, read_at: Time.zone.now, page: 1)
|
|
32
|
+
end
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
35
|
end
|
|
@@ -47,6 +47,8 @@ module Thredded
|
|
|
47
47
|
foreign_key: :messageboard_group_id,
|
|
48
48
|
class_name: 'Thredded::MessageboardGroup'
|
|
49
49
|
|
|
50
|
+
has_many :post_moderation_records, inverse_of: :messageboard, dependent: :delete_all
|
|
51
|
+
|
|
50
52
|
default_scope { where(closed: false).order(topics_count: :desc) }
|
|
51
53
|
|
|
52
54
|
scope :top_level_messageboards, -> { where(group: nil) }
|
data/app/models/thredded/post.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
module Thredded
|
|
3
3
|
class Post < ActiveRecord::Base
|
|
4
4
|
include PostCommon
|
|
5
|
+
include ContentModerationState
|
|
5
6
|
|
|
6
7
|
belongs_to :user,
|
|
7
8
|
class_name: Thredded.user_class,
|
|
@@ -17,18 +18,41 @@ module Thredded
|
|
|
17
18
|
primary_key: :user_id,
|
|
18
19
|
foreign_key: :user_id,
|
|
19
20
|
counter_cache: true
|
|
21
|
+
has_many :moderation_records,
|
|
22
|
+
class_name: 'Thredded::PostModerationRecord',
|
|
23
|
+
dependent: :nullify
|
|
20
24
|
|
|
21
25
|
validates :messageboard_id, presence: true
|
|
22
26
|
|
|
27
|
+
after_commit :auto_follow_and_notify, on: [:create, :update]
|
|
28
|
+
|
|
29
|
+
# @param [Integer] per_page
|
|
30
|
+
# @param [Thredded.user_class] user
|
|
31
|
+
def page(per_page: self.class.default_per_page, user:)
|
|
32
|
+
readable_posts = PostPolicy::Scope.new(user, postable.posts).resolve
|
|
33
|
+
1 + readable_posts.where(postable.posts.arel_table[:id].lt(id)).count / per_page
|
|
34
|
+
end
|
|
35
|
+
|
|
23
36
|
def private_topic_post?
|
|
24
37
|
false
|
|
25
38
|
end
|
|
26
39
|
|
|
40
|
+
def user_detail
|
|
41
|
+
super || build_user_detail
|
|
42
|
+
end
|
|
43
|
+
|
|
27
44
|
# @return [ActiveRecord::Relation<Thredded.user_class>] users from the list of user names that can read this post.
|
|
28
45
|
def readers_from_user_names(user_names)
|
|
29
46
|
DbTextSearch::CaseInsensitive
|
|
30
47
|
.new(Thredded.user_class.thredded_messageboards_readers([messageboard]), Thredded.user_name_column)
|
|
31
48
|
.in(user_names)
|
|
32
49
|
end
|
|
50
|
+
|
|
51
|
+
def auto_follow_and_notify
|
|
52
|
+
# need to do this in-process so that it appears to them immediately
|
|
53
|
+
UserTopicFollow.create_unless_exists(user.id, postable_id, :posted)
|
|
54
|
+
# everything else can happen later
|
|
55
|
+
AutoFollowAndNotifyJob.perform_later(id)
|
|
56
|
+
end
|
|
33
57
|
end
|
|
34
58
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Thredded
|
|
3
|
+
class PostModerationRecord < ActiveRecord::Base
|
|
4
|
+
include ModerationState
|
|
5
|
+
# Rails 4 doesn't support enum _prefix
|
|
6
|
+
if Rails::VERSION::MAJOR >= 5
|
|
7
|
+
enum previous_moderation_state: moderation_states, _prefix: :previous
|
|
8
|
+
end
|
|
9
|
+
validates :previous_moderation_state, presence: true
|
|
10
|
+
|
|
11
|
+
belongs_to :messageboard, inverse_of: :post_moderation_records
|
|
12
|
+
validates :messageboard_id, presence: true
|
|
13
|
+
belongs_to :post, inverse_of: :moderation_records
|
|
14
|
+
belongs_to :post_user, class_name: Thredded.user_class, inverse_of: :thredded_post_moderation_records
|
|
15
|
+
belongs_to :moderator, class_name: Thredded.user_class, inverse_of: :thredded_post_moderation_records
|
|
16
|
+
|
|
17
|
+
validates_each :moderation_state do |record, attr, value|
|
|
18
|
+
if record.previous_moderation_state == value
|
|
19
|
+
record.errors.add attr, "Post moderation_state is already #{value}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param [Thredded.user_class] moderator
|
|
24
|
+
# @param [Thredded::Post] post
|
|
25
|
+
# @param [Symbol, String] previous_moderation_state
|
|
26
|
+
# @param [Symbol, String] moderation_state
|
|
27
|
+
# @return [Thredded::PostModerationRecord] the newly created persisted record
|
|
28
|
+
def self.record!(moderator:, post:, previous_moderation_state:, moderation_state:)
|
|
29
|
+
# Rails 4 doesn't support enum _prefix
|
|
30
|
+
if Rails::VERSION::MAJOR < 5
|
|
31
|
+
previous_moderation_state = moderation_states[previous_moderation_state.to_s]
|
|
32
|
+
end
|
|
33
|
+
create!(
|
|
34
|
+
previous_moderation_state: previous_moderation_state,
|
|
35
|
+
moderation_state: moderation_state,
|
|
36
|
+
moderator: moderator,
|
|
37
|
+
post: post,
|
|
38
|
+
post_content: post.content,
|
|
39
|
+
post_user: post.user,
|
|
40
|
+
post_user_name: post.user.send(Thredded.user_name_column),
|
|
41
|
+
messageboard_id: post.messageboard_id,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -15,15 +15,30 @@ module Thredded
|
|
|
15
15
|
primary_key: :user_id,
|
|
16
16
|
foreign_key: :user_id
|
|
17
17
|
|
|
18
|
+
after_commit :notify_users, on: [:create]
|
|
19
|
+
|
|
20
|
+
# @param [Integer] per_page
|
|
21
|
+
def page(per_page: self.class.default_per_page)
|
|
22
|
+
1 + postable.posts.where(postable.posts.arel_table[:id].lt(id)).count / per_page
|
|
23
|
+
end
|
|
24
|
+
|
|
18
25
|
def private_topic_post?
|
|
19
26
|
true
|
|
20
27
|
end
|
|
21
28
|
|
|
29
|
+
def user_detail
|
|
30
|
+
super || build_user_detail
|
|
31
|
+
end
|
|
32
|
+
|
|
22
33
|
# @return [ActiveRecord::Relation<Thredded.user_class>] users from the list of user names that can read this post.
|
|
23
34
|
def readers_from_user_names(user_names)
|
|
24
35
|
DbTextSearch::CaseInsensitive
|
|
25
36
|
.new(postable.users, Thredded.user_name_column)
|
|
26
37
|
.in(user_names)
|
|
27
38
|
end
|
|
39
|
+
|
|
40
|
+
def notify_users
|
|
41
|
+
Thredded::NotifyPrivateTopicUsersJob.perform_later(id)
|
|
42
|
+
end
|
|
28
43
|
end
|
|
29
44
|
end
|