thredded 0.15.4 → 0.15.5

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/stylesheets/thredded/base/_alerts.scss +1 -0
  4. data/app/controllers/thredded/application_controller.rb +14 -7
  5. data/app/controllers/thredded/messageboards_controller.rb +5 -6
  6. data/app/controllers/thredded/moderation_controller.rb +28 -18
  7. data/app/controllers/thredded/topics_controller.rb +1 -1
  8. data/app/forms/thredded/post_form.rb +1 -1
  9. data/app/forms/thredded/private_post_form.rb +1 -1
  10. data/app/forms/thredded/private_topic_form.rb +2 -3
  11. data/app/forms/thredded/topic_form.rb +1 -1
  12. data/app/helpers/thredded/application_helper.rb +10 -9
  13. data/app/mailer_previews/thredded/base_mailer_preview.rb +4 -4
  14. data/app/models/concerns/thredded/content_moderation_state.rb +24 -24
  15. data/app/models/concerns/thredded/topic_common.rb +1 -1
  16. data/app/models/concerns/thredded/user_topic_read_state_common.rb +1 -1
  17. data/app/models/thredded/category.rb +1 -1
  18. data/app/models/thredded/messageboard.rb +6 -6
  19. data/app/models/thredded/messageboard_user.rb +4 -1
  20. data/app/models/thredded/post.rb +4 -2
  21. data/app/models/thredded/post_moderation_record.rb +3 -9
  22. data/app/models/thredded/private_topic.rb +6 -5
  23. data/app/models/thredded/topic.rb +6 -4
  24. data/app/models/thredded/topic_category.rb +2 -2
  25. data/app/models/thredded/user_detail.rb +12 -6
  26. data/app/models/thredded/user_extender.rb +22 -22
  27. data/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb +7 -0
  28. data/app/models/thredded/user_permissions/moderate/none.rb +6 -0
  29. data/app/models/thredded/user_permissions/read/all.rb +7 -0
  30. data/app/models/thredded/user_preference.rb +9 -9
  31. data/app/policies/thredded/messageboard_policy.rb +8 -3
  32. data/app/views/thredded/messageboards/_form.html.erb +3 -2
  33. data/app/views/thredded/messageboards/new.html.erb +1 -1
  34. data/app/views/thredded/private_topics/_form.html.erb +1 -1
  35. data/app/views/thredded/private_topics/edit.html.erb +1 -1
  36. data/app/views/thredded/shared/_field_errors.html.erb +5 -3
  37. data/app/views/thredded/shared/nav/_moderation.html.erb +1 -1
  38. data/app/views/thredded/topics/_form.html.erb +1 -1
  39. data/app/views/thredded/topics/edit.html.erb +1 -1
  40. data/bin/rails +2 -2
  41. data/config/locales/ru.yml +1 -1
  42. data/lib/generators/thredded/install/install_generator.rb +2 -2
  43. data/lib/generators/thredded/install/templates/initializer.rb +3 -0
  44. data/lib/thredded.rb +15 -2
  45. data/lib/thredded/base_notifier.rb +1 -3
  46. data/lib/thredded/database_seeder.rb +2 -5
  47. data/lib/thredded/engine.rb +1 -3
  48. data/lib/thredded/rails_lt_5_2_arel_case_node.rb +4 -4
  49. data/lib/thredded/version.rb +1 -1
  50. metadata +86 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4fb30f1cf973b0e6d8e26feb9006cbf20b1405cddfcaa2deb4e06ccd1d3a6524
4
- data.tar.gz: cd3fa99b4797609c4e840ed02a3117ccf7288d1c706ef879ac6ee3d32546404a
3
+ metadata.gz: 5bd8be4009af787b59ded83d2baa329f83fe8af37162b24d44191bbdae5623a7
4
+ data.tar.gz: 66601634a6691c8b0488011034dc5a03d872d203452651bf5e792ed87edfa801
5
5
  SHA512:
6
- metadata.gz: ef7c3bd8c922fc954a55e00625ffe267b0a164be7e4f36eb434de72a70f974571ae72cde41d799643ce893ef3f55aff7b5daebc70119cfe7462027d150ddf897
7
- data.tar.gz: e005eb547cbbb181d9f118ff54e9d48427a28c6b3b66dd2e19289e8b5468ceeb059b35e80d9d62b896af964ea416184ac3ea4c4fc8d189675a9b2d5dd9d23616
6
+ metadata.gz: 416d1ccbef43a2d7b2127151cba2727013a348cf3b36bb5db91cae85e179f7eb579359e945e70296fe965685b73d394125db5bb66dac691c37a756ac4ca49ac4
7
+ data.tar.gz: 37e9420a65e8278e3dd85c6159817c0336ca544d09119720234a87bad97ba4189238e159eb9a739ce5a90748eb8b6f230cfb95486b05e0c156e41b019c7ff8eb
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.15.4'
98
+ gem 'thredded', '~> 0.15.5'
99
99
  ```
100
100
 
101
101
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -17,6 +17,7 @@
17
17
  %thredded--alert {
18
18
  border: solid 1px;
19
19
  border-radius: 3px;
20
+ box-sizing: border-box;
20
21
  margin-bottom: $thredded-small-spacing;
21
22
  padding: $thredded-small-spacing;
22
23
  text-decoration: none;
@@ -16,7 +16,8 @@ module Thredded
16
16
  :unread_followed_topics_count,
17
17
  :unread_topics_count,
18
18
  :preferences,
19
- :thredded_signed_in?
19
+ :thredded_signed_in?,
20
+ :thredded_moderator?
20
21
 
21
22
  rescue_from Thredded::Errors::MessageboardNotFound,
22
23
  Thredded::Errors::PrivateTopicNotFound,
@@ -57,6 +58,11 @@ module Thredded
57
58
  !thredded_current_user.thredded_anonymous?
58
59
  end
59
60
 
61
+ def thredded_moderator?
62
+ return @is_thredded_moderator unless @is_thredded_moderator.nil?
63
+ @is_thredded_moderator = !thredded_current_user.thredded_can_moderate_messageboards.empty?
64
+ end
65
+
60
66
  if Rails::VERSION::MAJOR < 5
61
67
  # redirect_back polyfill
62
68
  def redirect_back(fallback_location:, **args)
@@ -154,9 +160,7 @@ module Thredded
154
160
  def unread_followed_topics_count
155
161
  @unread_followed_topics_count ||=
156
162
  if thredded_signed_in?
157
- scope = topics_scope
158
- scope = topics_scope.where(messageboard_id: messageboard.id) if messageboard_or_nil
159
- scope.unread_followed_by(thredded_current_user).count
163
+ topics_scope.unread_followed_by(thredded_current_user).count
160
164
  else
161
165
  0
162
166
  end
@@ -165,9 +169,7 @@ module Thredded
165
169
  def unread_topics_count
166
170
  @unread_topics_count ||=
167
171
  if thredded_signed_in?
168
- scope = topics_scope
169
- scope = topics_scope.where(messageboard_id: messageboard.id) if messageboard_or_nil
170
- scope.unread(thredded_current_user).count
172
+ topics_scope.unread(thredded_current_user).count
171
173
  else
172
174
  0
173
175
  end
@@ -190,5 +192,10 @@ module Thredded
190
192
  def thredded_require_login!
191
193
  fail Thredded::Errors::LoginRequired if thredded_current_user.thredded_anonymous?
192
194
  end
195
+
196
+ def thredded_require_moderator!
197
+ return if thredded_moderator?
198
+ fail Pundit::NotAuthorizedError, 'You are not authorized to perform this action.'
199
+ end
193
200
  end
194
201
  end
@@ -12,15 +12,14 @@ module Thredded
12
12
  end
13
13
 
14
14
  def new
15
- @messageboard = Thredded::Messageboard.new
16
- @messageboard_group = Thredded::MessageboardGroup.all
17
- authorize_creating @messageboard
15
+ @new_messageboard = Thredded::Messageboard.new
16
+ authorize_creating @new_messageboard
18
17
  end
19
18
 
20
19
  def create
21
- @messageboard = Thredded::Messageboard.new(messageboard_params)
22
- authorize_creating @messageboard
23
- if Thredded::CreateMessageboard.new(@messageboard, thredded_current_user).run
20
+ @new_messageboard = Thredded::Messageboard.new(messageboard_params)
21
+ authorize_creating @new_messageboard
22
+ if Thredded::CreateMessageboard.new(@new_messageboard, thredded_current_user).run
24
23
  redirect_to root_path
25
24
  else
26
25
  render :new
@@ -3,7 +3,7 @@
3
3
  module Thredded
4
4
  class ModerationController < Thredded::ApplicationController
5
5
  before_action :thredded_require_login!
6
- before_action :load_moderatable_messageboards
6
+ before_action :thredded_require_moderator!
7
7
 
8
8
  def pending
9
9
  @posts = Thredded::PostsPageView.new(
@@ -35,12 +35,20 @@ module Thredded
35
35
  end
36
36
 
37
37
  def moderate_post
38
- return head(:bad_request) unless Thredded::Post.moderation_states.include?(params[:moderation_state])
39
- flash[:last_moderated_record_id] = Thredded::ModeratePost.run!(
40
- post: moderatable_posts.find(params[:id]),
41
- moderation_state: params[:moderation_state],
42
- moderator: thredded_current_user,
43
- ).id
38
+ moderation_state = params[:moderation_state].to_s
39
+ return head(:bad_request) unless Thredded::Post.moderation_states.include?(moderation_state)
40
+ post = moderatable_posts.find(params[:id].to_s)
41
+ if post.moderation_state != moderation_state
42
+ flash[:last_moderated_record_id] = Thredded::ModeratePost.run!(
43
+ post: post,
44
+ moderation_state: moderation_state,
45
+ moderator: thredded_current_user,
46
+ ).id
47
+ else
48
+ flash[:alert] = "Post was already #{moderation_state}:"
49
+ flash[:last_moderated_record_id] =
50
+ Thredded::PostModerationRecord.order_newest_first.find_by(post_id: post.id)&.id
51
+ end
44
52
  redirect_back fallback_location: pending_moderation_path
45
53
  end
46
54
 
@@ -49,9 +57,7 @@ module Thredded
49
57
  .left_join_thredded_user_details
50
58
  .merge(Thredded::UserDetail.order(moderation_state_changed_at: :desc))
51
59
  @query = params[:q].to_s
52
- if @query.present?
53
- @users = DbTextSearch::CaseInsensitive.new(@users, Thredded.user_name_column).prefix(@query)
54
- end
60
+ @users = DbTextSearch::CaseInsensitive.new(@users, Thredded.user_name_column).prefix(@query) if @query.present?
55
61
  @users = @users.page(current_page)
56
62
  end
57
63
 
@@ -81,19 +87,23 @@ module Thredded
81
87
  end
82
88
 
83
89
  def moderatable_posts
84
- Thredded::Post.where(messageboard_id: @moderatable_messageboards)
90
+ if moderatable_messageboards == Thredded::Messageboard.all
91
+ Thredded::Post.all
92
+ else
93
+ Thredded::Post.where(messageboard_id: moderatable_messageboards)
94
+ end
85
95
  end
86
96
 
87
97
  def accessible_post_moderation_records
88
- Thredded::PostModerationRecord
89
- .where(messageboard_id: @moderatable_messageboards)
98
+ if moderatable_messageboards == Thredded::Messageboard.all
99
+ Thredded::PostModerationRecord.all
100
+ else
101
+ Thredded::PostModerationRecord.where(messageboard_id: moderatable_messageboards)
102
+ end
90
103
  end
91
104
 
92
- def load_moderatable_messageboards
93
- @moderatable_messageboards = thredded_current_user.thredded_can_moderate_messageboards.to_a
94
- if @moderatable_messageboards.empty? # rubocop:disable Style/GuardClause
95
- fail Pundit::NotAuthorizedError, 'You are not authorized to perform this action.'
96
- end
105
+ def moderatable_messageboards
106
+ @moderatable_messageboards ||= thredded_current_user.thredded_can_moderate_messageboards
97
107
  end
98
108
 
99
109
  def current_page
@@ -56,7 +56,7 @@ module Thredded
56
56
  return redirect_to(canonical_topic_params) unless params_match?(canonical_topic_params)
57
57
  page_scope = policy_scope(topic.posts)
58
58
  .order_oldest_first
59
- .includes(:user, :messageboard, :postable)
59
+ .includes(:user, :messageboard)
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)
@@ -16,7 +16,7 @@ module Thredded
16
16
  def initialize(user:, topic:, post: nil, post_params: {})
17
17
  @messageboard = topic.messageboard
18
18
  @topic = topic
19
- @post = post ? post : topic.posts.build
19
+ @post = post || topic.posts.build
20
20
  user ||= Thredded::NullUser.new
21
21
 
22
22
  if post_params.include?(:quote_post)
@@ -15,7 +15,7 @@ module Thredded
15
15
  # @param post_params [Hash]
16
16
  def initialize(user:, topic:, post: nil, post_params: {})
17
17
  @topic = topic
18
- @post = post ? post : topic.posts.build
18
+ @post = post || topic.posts.build
19
19
  user ||= Thredded::NullUser.new
20
20
 
21
21
  if post_params.include?(:quote_post)
@@ -14,8 +14,7 @@ module Thredded
14
14
  :user_ids,
15
15
  :locked,
16
16
  :sticky,
17
- :content,
18
- :private_topic
17
+ :content
19
18
 
20
19
  attr_reader :user, :params
21
20
  attr_writer :user_names
@@ -131,7 +130,7 @@ module Thredded
131
130
 
132
131
  def parse_names(text) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
133
132
  result = []
134
- current = String.new
133
+ current = +''
135
134
  in_name = in_quoted = false
136
135
  text.each_char do |char|
137
136
  case char
@@ -4,7 +4,7 @@ module Thredded
4
4
  class TopicForm
5
5
  include ActiveModel::Model
6
6
 
7
- attr_accessor :title, :category_ids, :locked, :sticky, :content, :topic
7
+ attr_accessor :title, :category_ids, :locked, :sticky, :content
8
8
  attr_reader :user, :messageboard
9
9
 
10
10
  validate :validate_children
@@ -24,7 +24,7 @@ module Thredded
24
24
  'thredded--main-container',
25
25
  content_for(:thredded_page_id),
26
26
  "thredded--global-nav-icons-#{global_nav_icons_count}",
27
- ('thredded--is-moderator' unless moderatable_messageboards_ids.empty?),
27
+ ('thredded--is-moderator' if thredded_moderator?),
28
28
  ('thredded--private-messaging-enabled' if Thredded.private_messaging_enabled),
29
29
  ].compact
30
30
  end
@@ -32,7 +32,7 @@ module Thredded
32
32
  def global_nav_icons_count
33
33
  result = 1 # Notification Settings
34
34
  result += 1 if Thredded.private_messaging_enabled
35
- result += 1 if moderatable_messageboards_ids.present?
35
+ result += 1 if thredded_moderator?
36
36
  result
37
37
  end
38
38
 
@@ -115,14 +115,15 @@ module Thredded
115
115
  end
116
116
  end
117
117
 
118
- def moderatable_messageboards_ids
119
- @moderatable_messageboards_ids ||=
120
- thredded_current_user.thredded_can_moderate_messageboards.pluck(:id)
121
- end
122
-
123
118
  def posts_pending_moderation_count
124
- @posts_pending_moderation_count ||=
125
- Thredded::Post.where(messageboard_id: moderatable_messageboards_ids).pending_moderation.count
119
+ @posts_pending_moderation_count ||= begin
120
+ scope = Thredded::Post.pending_moderation
121
+ moderatable_messageboards = thredded_current_user.thredded_can_moderate_messageboards
122
+ unless moderatable_messageboards == Thredded::Messageboard.all
123
+ scope = scope.where(messageboard_id: moderatable_messageboards.pluck(:id))
124
+ end
125
+ scope.count
126
+ end
126
127
  end
127
128
  end
128
129
  end
@@ -31,7 +31,7 @@ Additionally, Markdown is extended to support the following:
31
31
  last_user: mock_user,
32
32
  locked: [false, true].sample,
33
33
  messageboard: mock_messageboard,
34
- posts_count: 1 + rand(42),
34
+ posts_count: rand(1..42),
35
35
  sticky: [false, true].sample,
36
36
  updated_at: Time.zone.now,
37
37
  user: mock_user,
@@ -45,7 +45,7 @@ Additionally, Markdown is extended to support the following:
45
45
  attr.reverse_merge(
46
46
  content: 'A test post',
47
47
  created_at: Time.zone.now,
48
- id: 1 + rand(1334),
48
+ id: rand(1..1334),
49
49
  messageboard: topic.messageboard,
50
50
  postable: topic,
51
51
  updated_at: Time.zone.now,
@@ -62,7 +62,7 @@ Additionally, Markdown is extended to support the following:
62
62
  slug: 'a-test-private-topic',
63
63
  created_at: 3.days.ago,
64
64
  last_user: mock_user,
65
- posts_count: 1 + rand(42),
65
+ posts_count: rand(1..42),
66
66
  updated_at: Time.zone.now,
67
67
  user: mock_user,
68
68
  )
@@ -75,7 +75,7 @@ Additionally, Markdown is extended to support the following:
75
75
  attr.reverse_merge(
76
76
  content: 'A test private post',
77
77
  created_at: Time.zone.now,
78
- id: 1 + rand(1334),
78
+ id: rand(1..1334),
79
79
  postable: private_topic,
80
80
  updated_at: Time.zone.now,
81
81
  user: private_topic.last_user,
@@ -11,35 +11,35 @@ module Thredded
11
11
  included do
12
12
  before_validation :set_default_moderation_state, on: :create
13
13
 
14
- scope :moderation_state_visible_to_all, -> { where(visible_to_all_arel_node) }
15
-
16
- scope :moderation_state_visible_to_user, ->(user) {
17
- visible = visible_to_all_arel_node
18
- # @type [Arel::Table]
19
- table = arel_table
20
- if user && !user.thredded_anonymous?
21
- # Own content
22
- visible = visible.or(table[:user_id].eq(user.id))
23
- # Content that one can moderate
24
- moderatable_messageboard_ids = user.thredded_can_moderate_messageboards.map(&:id)
25
- if moderatable_messageboard_ids.present?
26
- visible = visible.or(table[:messageboard_id].in(moderatable_messageboard_ids))
27
- end
28
- end
29
- where(visible)
30
- }
31
-
32
- # @return [Arel::Nodes::Node]
33
- # @api private
34
- def self.visible_to_all_arel_node
14
+ scope :moderation_state_visible_to_all, -> do
35
15
  if Thredded.content_visible_while_pending_moderation
36
16
  # All non-blocked content
37
- arel_table[:moderation_state].not_eq(moderation_states[:blocked])
17
+ where.not(moderation_state: moderation_states[:blocked])
38
18
  else
39
19
  # Only approved content
40
- arel_table[:moderation_state].eq(moderation_states[:approved])
20
+ where(moderation_state: moderation_states[:approved])
41
21
  end
42
22
  end
23
+
24
+ scope :moderation_state_visible_to_user, ->(user) {
25
+ moderatable_messageboards = user.thredded_can_moderate_messageboards
26
+ if moderatable_messageboards == Thredded::Messageboard.all
27
+ # If the user can moderate all messageboards, they can see all the content.
28
+ result = all
29
+ else
30
+ # Visible to all.
31
+ result = moderation_state_visible_to_all
32
+
33
+ # Own content.
34
+ result = result.or(where(user_id: user.id)) if user && !user.thredded_anonymous?
35
+
36
+ # Content that the user can moderate.
37
+ if moderatable_messageboards != Thredded::Messageboard.none
38
+ result = result.or(messageboard_id: moderatable_messageboards)
39
+ end
40
+ end
41
+ result
42
+ }
43
43
  end
44
44
 
45
45
  # Whether this is visible to anyone based on the moderation state.
@@ -55,7 +55,7 @@ module Thredded
55
55
  def moderation_state_visible_to_user?(user)
56
56
  moderation_state_visible_to_all? ||
57
57
  (!user.thredded_anonymous? &&
58
- (user_id == user.id || user.thredded_can_moderate_messageboards.map(&:id).include?(messageboard_id)))
58
+ (user_id == user.id || user.thredded_can_moderate_messageboard?(messageboard)))
59
59
  end
60
60
 
61
61
  private
@@ -6,7 +6,7 @@ module Thredded
6
6
  included do
7
7
  paginates_per Thredded.topics_per_page if respond_to?(:paginates_per)
8
8
 
9
- belongs_to :last_user,
9
+ belongs_to :last_user, # rubocop:disable Rails/InverseOf
10
10
  class_name: Thredded.user_class_name,
11
11
  foreign_key: 'last_user_id',
12
12
  **(Thredded.rails_gte_51? ? { optional: true } : {})
@@ -41,7 +41,7 @@ module Thredded
41
41
  # Adds `first_unread_post_page` and `last_read_post_page` columns onto the scope.
42
42
  # Skips the records that have no read posts.
43
43
  def with_page_info( # rubocop:disable Metrics/MethodLength
44
- posts_per_page: topic_class.default_per_page, posts_scope: post_class.all
44
+ posts_per_page: post_class.default_per_page, posts_scope: post_class.all
45
45
  )
46
46
  states = arel_table
47
47
  self_relation = is_a?(ActiveRecord::Relation) ? self : all
@@ -4,7 +4,7 @@ module Thredded
4
4
  class Category < ActiveRecord::Base
5
5
  extend FriendlyId
6
6
  belongs_to :messageboard
7
- has_many :topic_categories
7
+ has_many :topic_categories, inverse_of: :category, dependent: :delete_all
8
8
  has_many :topics, through: :topic_categories
9
9
  friendly_id :name, use: %i[history scoped], scope: :messageboard
10
10
 
@@ -20,7 +20,7 @@ module Thredded
20
20
  ]
21
21
  )
22
22
 
23
- validates :name, uniqueness: true, length: { maximum: 60 }, presence: true
23
+ validates :name, uniqueness: true, length: { within: Thredded.messageboard_name_length_range }, presence: true
24
24
  validates :topics_count, numericality: true
25
25
  validates :position, presence: true, on: :update
26
26
  before_save :ensure_position, on: :create
@@ -31,7 +31,7 @@ module Thredded
31
31
 
32
32
  has_many :categories, dependent: :destroy
33
33
  has_many :user_messageboard_preferences, dependent: :destroy
34
- has_many :posts, dependent: :destroy
34
+ has_many :posts, dependent: :destroy, inverse_of: :messageboard
35
35
  has_many :topics, dependent: :destroy, inverse_of: :messageboard
36
36
 
37
37
  belongs_to :last_topic, class_name: 'Thredded::Topic', **(Thredded.rails_gte_51? ? { optional: true } : {})
@@ -72,13 +72,13 @@ module Thredded
72
72
  ordered_by_topics_count_desc
73
73
  end.ordered_by_position.order(id: :asc)
74
74
  }
75
- scope :ordered_by_position, ->() { order(position: :asc) }
76
- scope :ordered_by_created_at_asc, ->() { order(created_at: :asc) }
77
- scope :ordered_by_last_post_at_desc, ->() {
75
+ scope :ordered_by_position, -> { order(position: :asc) }
76
+ scope :ordered_by_created_at_asc, -> { order(created_at: :asc) }
77
+ scope :ordered_by_last_post_at_desc, -> {
78
78
  joins('LEFT JOIN thredded_topics AS last_topics ON thredded_messageboards.last_topic_id = last_topics.id')
79
79
  .order(Arel.sql('COALESCE(last_topics.last_post_at, thredded_messageboards.created_at) DESC'))
80
80
  }
81
- scope :ordered_by_topics_count_desc, ->() {
81
+ scope :ordered_by_topics_count_desc, -> {
82
82
  order(topics_count: :desc)
83
83
  }
84
84