thredded 0.6.2 → 0.6.3

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