thredded 0.6.2 → 0.6.3

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +1 -1
  4. data/app/assets/stylesheets/thredded/base/_buttons.scss +17 -0
  5. data/app/assets/stylesheets/thredded/base/_variables.scss +8 -1
  6. data/app/commands/thredded/mark_all_read.rb +21 -0
  7. data/app/controllers/thredded/messageboard_groups_controller.rb +1 -1
  8. data/app/controllers/thredded/moderation_controller.rb +1 -1
  9. data/app/controllers/thredded/read_states_controller.rb +12 -0
  10. data/app/forms/thredded/topic_form.rb +0 -1
  11. data/app/helpers/thredded/urls_helper.rb +1 -1
  12. data/app/models/concerns/thredded/post_common.rb +14 -2
  13. data/app/models/concerns/thredded/topic_common.rb +9 -1
  14. data/app/models/thredded/messageboard.rb +7 -0
  15. data/app/models/thredded/post.rb +11 -18
  16. data/app/models/thredded/private_post.rb +1 -5
  17. data/app/models/thredded/private_topic.rb +0 -4
  18. data/app/models/thredded/topic.rb +26 -12
  19. data/app/view_models/thredded/topic_view.rb +1 -0
  20. data/app/view_models/thredded/topics_page_view.rb +3 -2
  21. data/app/views/thredded/preferences/_form.html.erb +1 -1
  22. data/app/views/thredded/private_topic_mailer/message_notification.html.erb +1 -1
  23. data/app/views/thredded/private_topic_mailer/message_notification.text.erb +1 -1
  24. data/app/views/thredded/private_topics/index.html.erb +14 -6
  25. data/config/locales/en.yml +2 -1
  26. data/config/locales/pt-BR.yml +9 -10
  27. data/config/routes.rb +2 -1
  28. data/db/seeds.rb +1 -118
  29. data/heroku.gemfile +1 -2
  30. data/heroku.gemfile.lock +60 -64
  31. data/lib/thredded/database_seeder.rb +269 -0
  32. data/lib/thredded/main_app_route_delegator.rb +1 -1
  33. data/lib/thredded/version.rb +1 -1
  34. data/thredded.gemspec +3 -3
  35. metadata +12 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb9af60f2898343a1404af723bb3dd314807876a
4
- data.tar.gz: 7b6ac99f8869dc6722671e4c7491997815463cf7
3
+ metadata.gz: 7a1acf1b52acfeb414676b3d0b4c60cf9ea347af
4
+ data.tar.gz: cc19fecffc62460f24b8781ab0f4dc27bcf2db95
5
5
  SHA512:
6
- metadata.gz: 28a32a71b5e598e03fe027194b96b7577be422cae39f4208b28719e991d740ec78e8580be3f03b202f28b1bf19bb825d936608ecf18968ac1ebe5462c6be0b94
7
- data.tar.gz: fe05b0331bb1f3fa9d4ed33749dbe78e6e836f5ece7cc53dc864e3ff69792a308e403854a6eb516f21865e7734ca7833cafc009f006a2274f0fe8b9b21b72264
6
+ metadata.gz: 4085dd28328ce761c890fdf976d199cc3b6ac2aab5ca7da0ec32ab866ee95f8c8ae465f9f74c387dfdc13b4dbe201dca503820060c02e636472e35bd3548c4e6
7
+ data.tar.gz: 917c71e13f4b5bfd94d8567ff146c871f39201ed9727e0e1fe77b88bfdd19304cf3dbd25a5935015e03ae37982986828fbb100b5b88960afef9b5c3c6ae2f252
@@ -1,3 +1,18 @@
1
+ # v0.6.3
2
+
3
+ This is a minor bugfix release.
4
+
5
+ ## Fixed
6
+
7
+ * Last topic post and last messageboard topic is kept in sync correctly with regards to moderation state changes.
8
+ [#384](https://github.com/thredded/thredded/issues/384) [#387](https://github.com/thredded/thredded/issues/387)
9
+ * Posts and private posts are now ordered by created at and not by ID, to avoid ordering issues in cases such as
10
+ imported content. [#360](https://github.com/thredded/thredded/issues/360)
11
+ * The global preferences URL now works when the Thredde URL helpers are included into another engine.
12
+ [#355](https://github.com/thredded/thredded/pull/355)
13
+
14
+ See the full list of changes here: https://github.com/thredded/thredded/compare/v0.6.2...v0.6.3.
15
+
1
16
  # v0.6.2
2
17
 
3
18
  This is a minor bugfix release.
data/README.md CHANGED
@@ -44,7 +44,7 @@ application and not an engine like Thredded.
44
44
  Add the gem to your Gemfile:
45
45
 
46
46
  ```ruby
47
- gem 'thredded', '~> 0.6.1'
47
+ gem 'thredded', '~> 0.6.3'
48
48
  ```
49
49
 
50
50
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -39,6 +39,23 @@
39
39
  }
40
40
  }
41
41
 
42
+ .thredded--button-light {
43
+ background-color: $thredded-button-light-background;
44
+ color: $thredded-button-light-color;
45
+
46
+ &:hover,
47
+ &:active,
48
+ &:focus {
49
+ background-color: $thredded-button-light-hover-background;
50
+ color: $thredded-button-light-hover-color;
51
+ }
52
+ }
53
+
54
+ .thredded--button-wide {
55
+ width: 100%;
56
+ text-align: center;
57
+ }
58
+
42
59
  %thredded--buttons-list {
43
60
  text-align: center;
44
61
  a, .button_to {
@@ -63,8 +63,15 @@ $thredded-form-color: $thredded-text-color !default;
63
63
  // Buttons
64
64
  $thredded-button-background: $thredded-action-color !default;
65
65
  $thredded-button-color: #fff !default;
66
- $thredded-button-hover-color: $thredded-button-color !default;
67
66
  $thredded-button-hover-background: darken($thredded-button-background, 15%) !default;
67
+ $thredded-button-hover-color: $thredded-button-color !default;
68
+
69
+ $thredded-button-light-background: lighten($thredded-button-background, 35%) !default;
70
+ $thredded-button-light-color: darken($thredded-button-background, 15%) !default;
71
+ $thredded-button-light-hover-background: darken($thredded-button-light-background, 15%) !default;
72
+ $thredded-button-light-hover-color: darken($thredded-button-light-color, 15%) !default;
73
+
74
+
68
75
  $thredded-button-border-radius: 3px !default;
69
76
  $thredded-button-font-family: $thredded-base-font-family !default;
70
77
  $thredded-button-font-size: $thredded-font-size-small !default;
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class MarkAllRead
4
+ def self.run(user)
5
+ unread_topics = Thredded::PrivateTopic.unread(user)
6
+ return if unread_topics.empty?
7
+
8
+ unread_topics.each do |topic|
9
+ last_post = topic.posts.order_oldest_first.last
10
+ total_pages = topic.posts.page(1).total_pages
11
+
12
+ UserPrivateTopicReadState.touch!(
13
+ user.id,
14
+ topic.id,
15
+ last_post,
16
+ total_pages
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -12,7 +12,7 @@ module Thredded
12
12
  authorize @messageboard_group, :create?
13
13
 
14
14
  if @messageboard_group.save
15
- redirect_to root_path
15
+ redirect_to root_path, notice: I18n.t('thredded.messageboard_group.created_notice')
16
16
  else
17
17
  render action: :new
18
18
  end
@@ -68,7 +68,7 @@ module Thredded
68
68
  .order_newest_first
69
69
  .includes(:postable)
70
70
  .page(current_page)
71
- @posts = PostsPageView.new(current_user, posts_scope)
71
+ @posts = PostsPageView.new(thredded_current_user, posts_scope)
72
72
  end
73
73
 
74
74
  def moderate_user
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class ReadStatesController < Thredded::ApplicationController
4
+ before_action :thredded_require_login!
5
+
6
+ def update
7
+ MarkAllRead.run(thredded_current_user) if signed_in?
8
+
9
+ redirect_to request.referer
10
+ end
11
+ end
12
+ end
@@ -47,7 +47,6 @@ module Thredded
47
47
  locked: locked,
48
48
  sticky: sticky,
49
49
  user: non_null_user,
50
- last_user: non_null_user,
51
50
  categories: topic_categories,
52
51
  )
53
52
  end
@@ -83,7 +83,7 @@ module Thredded
83
83
  if messageboard.try(:persisted?)
84
84
  edit_messageboard_preferences_url(messageboard, params)
85
85
  else
86
- super(params)
86
+ edit_global_preferences_url(params)
87
87
  end
88
88
  end
89
89
 
@@ -13,18 +13,30 @@ module Thredded
13
13
 
14
14
  validates :content, presence: true
15
15
 
16
- scope :order_oldest_first, -> { order(id: :asc) }
17
- scope :order_newest_first, -> { order(id: :desc) }
16
+ scope :order_oldest_first, -> { order(created_at: :asc, id: :asc) }
17
+ scope :order_newest_first, -> { order(created_at: :desc, id: :desc) }
18
+
19
+ before_validation :ensure_user_detail, on: :create
18
20
  end
19
21
 
20
22
  def avatar_url
21
23
  Thredded.avatar_url.call(user)
22
24
  end
23
25
 
26
+ def calculate_page(postable_posts, per_page)
27
+ 1 + postable_posts.where(postable_posts.arel_table[:created_at].lt(created_at)).count / per_page
28
+ end
29
+
24
30
  # @param view_context [Object] the context of the rendering view.
25
31
  # @return [String] formatted and sanitized html-safe post content.
26
32
  def filtered_content(view_context, users_provider: -> (names) { readers_from_user_names(names) })
27
33
  Thredded::ContentFormatter.new(view_context, users_provider: users_provider).format_content(content)
28
34
  end
35
+
36
+ private
37
+
38
+ def ensure_user_detail
39
+ build_user_detail if user && !user_detail
40
+ end
29
41
  end
30
42
  end
@@ -10,7 +10,7 @@ module Thredded
10
10
  foreign_key: 'last_user_id'
11
11
 
12
12
  scope :order_recently_updated_first, -> { order(updated_at: :desc, id: :desc) }
13
- scope :on_page, -> (page_num) { page(page_num).per(30) }
13
+ scope :on_page, -> (page_num) { page(page_num) }
14
14
 
15
15
  validates :hash_id, presence: true, uniqueness: true
16
16
  validates :posts_count, numericality: true
@@ -20,6 +20,8 @@ module Thredded
20
20
  end
21
21
 
22
22
  delegate :name, :name=, :email, :email=, to: :user, prefix: true
23
+
24
+ before_validation :ensure_user_detail, on: :create
23
25
  end
24
26
 
25
27
  def user
@@ -34,6 +36,12 @@ module Thredded
34
36
  !public?
35
37
  end
36
38
 
39
+ private
40
+
41
+ def ensure_user_detail
42
+ build_user_detail if user && !user_detail
43
+ end
44
+
37
45
  module ClassMethods
38
46
  # @param user [Thredded.user_class]
39
47
  # @return [ActiveRecord::Relation]
@@ -64,5 +64,12 @@ module Thredded
64
64
  [:name, '-board']
65
65
  ]
66
66
  end
67
+
68
+ def update_last_topic!
69
+ return if destroyed?
70
+ update_column(
71
+ :last_topic_id, topics.order_recently_updated_first.moderation_state_visible_to_all.select(:id).first.try(:id)
72
+ )
73
+ end
67
74
  end
68
75
  end
@@ -26,24 +26,22 @@ module Thredded
26
26
 
27
27
  validates :messageboard_id, presence: true
28
28
 
29
- after_commit :update_parent_last_user_and_timestamp, on: [:create, :destroy]
29
+ after_commit :update_parent_last_user_and_time_from_last_post, on: [:create, :destroy]
30
+ after_commit :update_parent_last_user_and_time_from_last_post_if_moderation_state_changed, on: :update
31
+
30
32
  after_commit :auto_follow_and_notify, on: [:create, :update]
31
33
 
32
34
  # @param [Integer] per_page
33
35
  # @param [Thredded.user_class] user
34
36
  def page(per_page: self.class.default_per_page, user:)
35
37
  readable_posts = PostPolicy::Scope.new(user, postable.posts).resolve
36
- 1 + readable_posts.where(postable.posts.arel_table[:id].lt(id)).count / per_page
38
+ calculate_page(readable_posts, per_page)
37
39
  end
38
40
 
39
41
  def private_topic_post?
40
42
  false
41
43
  end
42
44
 
43
- def user_detail
44
- super || build_user_detail
45
- end
46
-
47
45
  # @return [ActiveRecord::Relation<Thredded.user_class>] users from the list of user names that can read this post.
48
46
  def readers_from_user_names(user_names)
49
47
  DbTextSearch::CaseInsensitive
@@ -61,18 +59,13 @@ module Thredded
61
59
  AutoFollowAndNotifyJob.perform_later(id)
62
60
  end
63
61
 
64
- def update_parent_last_user_and_timestamp
65
- return if postable.destroyed?
66
- last_post = if destroyed? || !moderation_state_visible_to_all?
67
- postable.posts.order_oldest_first.moderation_state_visible_to_all.select(:user_id, :updated_at).last
68
- else
69
- self
70
- end
71
- if last_post
72
- postable.update_columns(last_user_id: last_post.user_id, updated_at: last_post.updated_at)
73
- else
74
- postable.update_columns(last_user_id: nil, updated_at: postable.created_at)
75
- end
62
+ def update_parent_last_user_and_time_from_last_post
63
+ postable.update_last_user_and_time_from_last_post!
64
+ messageboard.update_last_topic!
65
+ end
66
+
67
+ def update_parent_last_user_and_time_from_last_post_if_moderation_state_changed
68
+ update_parent_last_user_and_time_from_last_post if previous_changes.include?('moderation_state')
76
69
  end
77
70
  end
78
71
  end
@@ -20,17 +20,13 @@ module Thredded
20
20
 
21
21
  # @param [Integer] per_page
22
22
  def page(per_page: self.class.default_per_page)
23
- 1 + postable.posts.where(postable.posts.arel_table[:id].lt(id)).count / per_page
23
+ calculate_page(postable.posts, per_page)
24
24
  end
25
25
 
26
26
  def private_topic_post?
27
27
  true
28
28
  end
29
29
 
30
- def user_detail
31
- super || build_user_detail
32
- end
33
-
34
30
  # @return [ActiveRecord::Relation<Thredded.user_class>] users from the list of user names that can read this post.
35
31
  def readers_from_user_names(user_names)
36
32
  DbTextSearch::CaseInsensitive
@@ -58,10 +58,6 @@ module Thredded
58
58
  false
59
59
  end
60
60
 
61
- def user_detail
62
- super || build_user_detail
63
- end
64
-
65
61
  def should_generate_new_friendly_id?
66
62
  title_changed?
67
63
  end
@@ -15,6 +15,12 @@ module Thredded
15
15
 
16
16
  scope :order_sticky_first, -> { order(sticky: :desc) }
17
17
 
18
+ scope :followed_by, lambda { |user|
19
+ joins(:user_follows)
20
+ .where(thredded_user_topic_follows: { user_id: user.id })
21
+ }
22
+ scope :unread_followed_by, ->(user) { followed_by(user).unread(user) }
23
+
18
24
  extend FriendlyId
19
25
  friendly_id :slug_candidates,
20
26
  use: [:history, :reserved, :scoped],
@@ -46,6 +52,9 @@ module Thredded
46
52
  has_one :first_post, -> { order_oldest_first },
47
53
  class_name: 'Thredded::Post',
48
54
  foreign_key: :postable_id
55
+ has_one :last_post, -> { order_newest_first },
56
+ class_name: 'Thredded::Post',
57
+ foreign_key: :postable_id
49
58
 
50
59
  has_many :topic_categories, dependent: :destroy
51
60
  has_many :categories, through: :topic_categories
@@ -63,7 +72,8 @@ module Thredded
63
72
  source: :user,
64
73
  through: :user_follows
65
74
 
66
- after_commit :update_messageboard_last_topic, on: [:create, :destroy]
75
+ after_commit :update_messageboard_last_topic, on: :update, if: -> { previous_changes.include?('moderation_state') }
76
+ after_update :update_last_user_and_time_from_last_post!, if: -> { previous_changes.include?('moderation_state') }
67
77
 
68
78
  def self.find_by_slug!(slug)
69
79
  friendly.find(slug)
@@ -101,10 +111,6 @@ module Thredded
101
111
  true
102
112
  end
103
113
 
104
- def user_detail
105
- super || build_user_detail
106
- end
107
-
108
114
  def should_generate_new_friendly_id?
109
115
  title_changed?
110
116
  end
@@ -114,6 +120,20 @@ module Thredded
114
120
  first_post.try(:last_moderation_record)
115
121
  end
116
122
 
123
+ def update_last_user_and_time_from_last_post!
124
+ return if destroyed?
125
+ scope = posts.order_newest_first
126
+ scope = scope.moderation_state_visible_to_all if moderation_state_visible_to_all?
127
+ last_post = scope.select(:user_id, :created_at).first
128
+ if last_post
129
+ update_columns(last_user_id: last_post.user_id, updated_at: last_post.created_at)
130
+ else
131
+ # Either a visible topic is left with no visible posts, or an invisible topic is left with no posts at all.
132
+ # This shouldn't happen in stock Thredded.
133
+ update_columns(last_user_id: nil, updated_at: created_at)
134
+ end
135
+ end
136
+
117
137
  private
118
138
 
119
139
  def slug_candidates
@@ -124,13 +144,7 @@ module Thredded
124
144
  end
125
145
 
126
146
  def update_messageboard_last_topic
127
- return if messageboard.destroyed?
128
- last_topic = if destroyed? || !moderation_state_visible_to_all?
129
- messageboard.topics.order_recently_updated_first.moderation_state_visible_to_all.select(:id).first
130
- else
131
- self
132
- end
133
- messageboard.update_columns(last_topic_id: last_topic.try(:id))
147
+ messageboard.update_last_topic!
134
148
  end
135
149
  end
136
150
  end
@@ -3,6 +3,7 @@ module Thredded
3
3
  # A view model for Topic.
4
4
  class TopicView < BaseTopicView
5
5
  delegate :categories, :id, :blocked?, :last_moderation_record,
6
+ :last_post,
6
7
  to: :@topic
7
8
 
8
9
  # @param topic [TopicCommon]
@@ -14,10 +14,11 @@ module Thredded
14
14
 
15
15
  # @param user [Thredded.user_class] the user who is viewing the posts page
16
16
  # @param topics_page_scope [ActiveRecord::Relation<Thredded::Topic>]
17
- def initialize(user, topics_page_scope)
17
+ # @param topic_view_class [Class] view_model for topic instances
18
+ def initialize(user, topics_page_scope, topic_view_class: TopicView)
18
19
  @topics_page_scope = topics_page_scope
19
20
  @topic_views = @topics_page_scope.with_read_and_follow_states(user).map do |(topic, read_state, follow)|
20
- TopicView.new(topic, read_state, follow, Pundit.policy!(user, topic))
21
+ topic_view_class.new(topic, read_state, follow, Pundit.policy!(user, topic))
21
22
  end
22
23
  end
23
24
  end
@@ -1,7 +1,7 @@
1
1
  <%= form_for(
2
2
  preferences,
3
3
  method: :patch,
4
- url: (preferences.messageboard ? messageboard_preferences_path(preferences.messageboard) : preferences_path),
4
+ url: (preferences.messageboard ? messageboard_preferences_path(preferences.messageboard) : global_preferences_path),
5
5
  html: {
6
6
  class: 'thredded--form thredded--notification-preferences-form',
7
7
  'data-thredded-user-preferences-form' => true
@@ -1,5 +1,5 @@
1
1
  <blockquote><%= @topic.posts.first.filtered_content(self) %></blockquote>
2
-
2
+ <% # as this is only sent on first post, doesn't need to define order %>
3
3
  <hr />
4
4
 
5
5
  <p>
@@ -1,5 +1,5 @@
1
1
  <%= @topic.posts.first.content %>
2
-
2
+ <% # as this is only sent on first post, doesn't need to define order %>
3
3
  ---
4
4
 
5
5
  This email was sent to you because <%= @topic.user %>