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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.mkdn +26 -0
  3. data/README.mkdn +52 -7
  4. data/app/assets/images/thredded/moderation.svg +4 -0
  5. data/app/assets/images/thredded/private-messages.svg +1 -1
  6. data/app/assets/images/thredded/settings.svg +1 -1
  7. data/app/assets/stylesheets/thredded/_base.scss +1 -1
  8. data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
  9. data/app/assets/stylesheets/thredded/base/_buttons.scss +16 -0
  10. data/app/assets/stylesheets/thredded/base/_grid.scss +12 -0
  11. data/app/assets/stylesheets/thredded/components/_base.scss +4 -0
  12. data/app/assets/stylesheets/thredded/components/_messageboard.scss +1 -13
  13. data/app/assets/stylesheets/thredded/components/_topic-header.scss +27 -0
  14. data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +1 -1
  15. data/app/assets/stylesheets/thredded/layout/_moderation.scss +45 -0
  16. data/app/assets/stylesheets/thredded/layout/_navigation.scss +20 -6
  17. data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +2 -2
  18. data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +5 -3
  19. data/app/commands/thredded/autofollow_mentioned_users.rb +32 -0
  20. data/app/commands/thredded/moderate_post.rb +35 -0
  21. data/app/commands/thredded/notify_following_users.rb +18 -0
  22. data/app/commands/thredded/notify_private_topic_users.rb +4 -4
  23. data/app/controllers/thredded/application_controller.rb +6 -25
  24. data/app/controllers/thredded/messageboards_controller.rb +5 -3
  25. data/app/controllers/thredded/moderation_controller.rb +56 -0
  26. data/app/controllers/thredded/post_permalinks_controller.rb +1 -1
  27. data/app/controllers/thredded/posts_controller.rb +4 -2
  28. data/app/controllers/thredded/private_post_permalinks_controller.rb +1 -1
  29. data/app/controllers/thredded/private_topics_controller.rb +2 -3
  30. data/app/controllers/thredded/theme_previews_controller.rb +3 -3
  31. data/app/controllers/thredded/topics_controller.rb +32 -11
  32. data/app/forms/thredded/topic_form.rb +1 -0
  33. data/app/helpers/thredded/application_helper.rb +26 -1
  34. data/app/helpers/thredded/urls_helper.rb +7 -5
  35. data/app/jobs/thredded/auto_follow_and_notify_job.rb +13 -0
  36. data/app/jobs/thredded/notify_private_topic_users_job.rb +3 -4
  37. data/app/mailer_previews/thredded/post_mailer_preview.rb +2 -2
  38. data/app/mailers/thredded/post_mailer.rb +2 -2
  39. data/app/models/concerns/thredded/content_moderation_state.rb +53 -0
  40. data/app/models/concerns/thredded/moderation_state.rb +13 -0
  41. data/app/models/concerns/thredded/post_common.rb +6 -71
  42. data/app/models/concerns/thredded/topic_common.rb +26 -11
  43. data/app/models/concerns/thredded/user_topic_read_state_common.rb +4 -0
  44. data/app/models/thredded/messageboard.rb +2 -0
  45. data/app/models/thredded/post.rb +24 -0
  46. data/app/models/thredded/post_moderation_record.rb +45 -0
  47. data/app/models/thredded/private_post.rb +15 -0
  48. data/app/models/thredded/private_topic.rb +8 -0
  49. data/app/models/thredded/topic.rb +39 -0
  50. data/app/models/thredded/user_detail.rb +11 -0
  51. data/app/models/thredded/user_extender.rb +14 -0
  52. data/app/models/thredded/user_topic_follow.rb +20 -0
  53. data/app/policies/thredded/messageboard_policy.rb +15 -0
  54. data/app/policies/thredded/post_policy.rb +17 -1
  55. data/app/policies/thredded/private_post_policy.rb +1 -1
  56. data/app/policies/thredded/private_topic_policy.rb +1 -1
  57. data/app/policies/thredded/topic_policy.rb +18 -1
  58. data/app/view_models/thredded/base_topic_view.rb +0 -13
  59. data/app/view_models/thredded/post_view.rb +8 -1
  60. data/app/view_models/thredded/posts_page_view.rb +6 -8
  61. data/app/view_models/thredded/private_topic_view.rb +8 -0
  62. data/app/view_models/thredded/private_topics_page_view.rb +24 -0
  63. data/app/view_models/thredded/topic_posts_page_view.rb +17 -0
  64. data/app/view_models/thredded/topic_view.rb +27 -1
  65. data/app/view_models/thredded/topics_page_view.rb +2 -4
  66. data/app/views/thredded/moderation/_post.html.erb +7 -0
  67. data/app/views/thredded/moderation/_post_moderation_actions.html.erb +12 -0
  68. data/app/views/thredded/moderation/_post_moderation_record.html.erb +43 -0
  69. data/app/views/thredded/moderation/history.html.erb +19 -0
  70. data/app/views/thredded/moderation/pending.html.erb +29 -0
  71. data/app/views/thredded/post_mailer/{at_notification.html.erb → post_notification.html.erb} +1 -1
  72. data/app/views/thredded/post_mailer/{at_notification.text.erb → post_notification.text.erb} +1 -1
  73. data/app/views/thredded/posts/_post.html.erb +8 -1
  74. data/app/views/thredded/posts_common/_actions.html.erb +11 -0
  75. data/app/views/thredded/posts_common/_content.html.erb +5 -0
  76. data/app/views/thredded/posts_common/_header.html.erb +5 -0
  77. data/app/views/thredded/private_posts/_private_post.html.erb +5 -1
  78. data/app/views/thredded/shared/_nav.html.erb +1 -0
  79. data/app/views/thredded/shared/_page.html.erb +1 -1
  80. data/app/views/thredded/shared/nav/_moderation.html.erb +13 -0
  81. data/app/views/thredded/shared/nav/_private_topics.html.erb +1 -2
  82. data/app/views/thredded/topics/_header.html.erb +15 -0
  83. data/app/views/thredded/topics/index.html.erb +2 -2
  84. data/app/views/thredded/topics/new.html.erb +1 -1
  85. data/config/locales/en.yml +26 -5
  86. data/config/locales/pt-BR.yml +23 -0
  87. data/config/routes.rb +7 -0
  88. data/db/migrate/20160329231848_create_thredded.rb +40 -5
  89. data/db/seeds.rb +5 -5
  90. data/db/upgrade_migrations/20160429222452_upgrade_v0_3_to_v0_4.rb +1 -2
  91. data/db/upgrade_migrations/20160501151908_upgrade_v0_4_to_v0_5.rb +56 -0
  92. data/heroku.gemfile.lock +20 -18
  93. data/lib/generators/thredded/install/templates/initializer.rb +15 -0
  94. data/lib/html/pipeline/at_mention_filter.rb +5 -2
  95. data/lib/thredded.rb +4 -0
  96. data/lib/thredded/at_users.rb +3 -2
  97. data/lib/thredded/content_formatter.rb +81 -0
  98. data/lib/thredded/version.rb +1 -1
  99. metadata +28 -10
  100. data/app/commands/thredded/notify_mentioned_users.rb +0 -55
  101. data/app/jobs/thredded/at_notifier_job.rb +0 -12
  102. data/app/mailer_previews/thredded/private_post_mailer_preview.rb +0 -12
  103. data/app/mailers/thredded/private_post_mailer.rb +0 -17
  104. data/app/views/thredded/posts_common/_post.html.erb +0 -26
  105. data/app/views/thredded/private_post_mailer/at_notification.html.erb +0 -13
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class AutofollowMentionedUsers
4
+ def initialize(post)
5
+ @post = post
6
+ end
7
+
8
+ def run
9
+ autofollowers.each do |user|
10
+ Thredded::UserTopicFollow.create_unless_exists(user.id, post.postable_id, :mentioned)
11
+ end
12
+ end
13
+
14
+ def autofollowers
15
+ user_names = Thredded::AtNotificationExtractor.new(post.content).run
16
+ autofollowers = post.readers_from_user_names(user_names).to_a
17
+ autofollowers.delete post.user
18
+ exclude_those_opting_out_of_at_notifications(autofollowers)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :post
24
+
25
+ def exclude_those_opting_out_of_at_notifications(members)
26
+ members.select do |member|
27
+ member.thredded_user_preference.notify_on_mention? &&
28
+ member.thredded_user_messageboard_preferences.in(post.messageboard).notify_on_mention?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ module ModeratePost
4
+ module_function
5
+
6
+ # @param [Post] post
7
+ # @param [Symbol] moderation_state
8
+ # @param [Thredded.user_class] moderator
9
+ # @return [Thredded::PostModerationRecord]
10
+ def run!(post:, moderation_state:, moderator:)
11
+ Thredded::Post.transaction do
12
+ post_moderation_record = Thredded::PostModerationRecord.record!(
13
+ moderator: moderator,
14
+ post: post,
15
+ previous_moderation_state: post.moderation_state,
16
+ moderation_state: moderation_state,
17
+ )
18
+ if post.user_detail.pending_moderation?
19
+ post.user_detail.update!(moderation_state: moderation_state)
20
+ end
21
+ if post.postable.first_post == post
22
+ post.postable.update!(moderation_state: moderation_state)
23
+ if moderation_state == :blocked
24
+ # When blocking the first post of a topic, also block all the other posts in the topic by this user.
25
+ post.postable.posts.where(user_id: post.user.id).where.not(id: post.id).each do |a_post|
26
+ a_post.update!(moderation_state: moderation_state)
27
+ end
28
+ end
29
+ end
30
+ post.update!(moderation_state: moderation_state)
31
+ post_moderation_record
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class NotifyFollowingUsers
4
+ def initialize(post)
5
+ @post = post
6
+ end
7
+
8
+ def run
9
+ return if targeted_users.empty?
10
+ PostMailer.post_notification(@post.id, targeted_users.map(&:email)).deliver_now
11
+ MembersMarkedNotified.new(@post, targeted_users).run
12
+ end
13
+
14
+ def targeted_users
15
+ @targeted_users ||= @post.postable.following_users.reject { |u| u == @post.user }
16
+ end
17
+ end
18
+ end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class NotifyPrivateTopicUsers
4
- def initialize(private_topic)
5
- @post = private_topic.posts.order_oldest_first.first
6
- @private_topic = private_topic
4
+ def initialize(private_post)
5
+ @post = private_post
6
+ @private_topic = private_post.postable
7
7
  end
8
8
 
9
9
  def run
@@ -18,7 +18,7 @@ module Thredded
18
18
  end
19
19
 
20
20
  def private_topic_recipients
21
- members = private_topic.users - [private_topic.user]
21
+ members = private_topic.users - [post.user]
22
22
  members = exclude_those_opting_out_of_message_notifications(members)
23
23
  members = exclude_previously_notified(members)
24
24
  members
@@ -12,7 +12,6 @@ module Thredded
12
12
  :messageboard,
13
13
  :messageboard_or_nil,
14
14
  :preferences,
15
- :unread_private_topics_count,
16
15
  :signed_in?
17
16
 
18
17
  rescue_from Thredded::Errors::MessageboardNotFound,
@@ -64,34 +63,16 @@ module Thredded
64
63
  Thredded.layout
65
64
  end
66
65
 
67
- def unread_private_topics_count
68
- @unread_private_topics_count ||=
69
- if signed_in?
70
- Thredded::PrivateTopic
71
- .for_user(thredded_current_user)
72
- .unread(thredded_current_user)
73
- .count
74
- else
75
- 0
76
- end
77
- end
78
-
79
66
  def authorize_reading(obj)
80
- return if policy(obj).read?
81
-
82
- class_name = obj.class.to_s
83
- error = class_name.gsub(/Thredded::/, 'Thredded::Errors::') + 'ReadDenied'
84
-
85
- fail error.constantize
67
+ authorize obj, :read?
68
+ rescue Pundit::NotAuthorizedError
69
+ raise "#{obj.class.to_s.sub(/Thredded::/, 'Thredded::Errors::')}ReadDenied".constantize
86
70
  end
87
71
 
88
72
  def authorize_creating(obj)
89
- return if policy(obj).create?
90
-
91
- class_name = obj.class.to_s
92
- error = class_name.gsub(/Thredded::/, 'Thredded::Errors::') + 'CreateDenied'
93
-
94
- fail error.constantize
73
+ authorize obj, :create?
74
+ rescue Pundit::NotAuthorizedError
75
+ raise "#{obj.class.to_s.sub(/Thredded::/, 'Thredded::Errors::')}CreateDenied".constantize
95
76
  end
96
77
 
97
78
  def update_user_activity
@@ -3,9 +3,11 @@ module Thredded
3
3
  class MessageboardsController < Thredded::ApplicationController
4
4
  before_action :thredded_require_login!, only: [:new, :create, :edit, :update]
5
5
 
6
+ after_action :verify_authorized, except: %i(index)
7
+ after_action :verify_policy_scoped, except: %i(new create edit update)
8
+
6
9
  def index
7
- @groups = thredded_current_user
8
- .thredded_can_read_messageboards
10
+ @groups = policy_scope(Messageboard.all)
9
11
  .preload(:group).group_by(&:group)
10
12
  .map { |(group, messageboards)| MessageboardGroupView.new(group, messageboards) }
11
13
  end
@@ -41,7 +43,7 @@ module Thredded
41
43
  if @messageboard.update(messageboard_params)
42
44
  redirect_to messageboard_topics_path(@messageboard), notice: I18n.t('thredded.messageboard.updated_notice')
43
45
  else
44
- render :new
46
+ render :edit
45
47
  end
46
48
  end
47
49
 
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ require_dependency 'thredded/moderate_post'
3
+ require_dependency 'thredded/posts_page_view'
4
+ module Thredded
5
+ class ModerationController < ApplicationController
6
+ before_action :thredded_require_login!
7
+ before_action :load_moderatable_messageboards
8
+
9
+ def pending
10
+ @posts = PostsPageView.new(
11
+ thredded_current_user,
12
+ moderatable_posts
13
+ .pending_moderation
14
+ .order_oldest_first
15
+ .page(params[:page] || 1)
16
+ )
17
+ if flash[:last_moderated_record_id]
18
+ @last_moderated_record = accessible_moderation_records.find(flash[:last_moderated_record_id].to_i)
19
+ end
20
+ end
21
+
22
+ def history
23
+ @post_moderation_records = accessible_moderation_records
24
+ .order(created_at: :desc)
25
+ .page(params[:page] || 1)
26
+ end
27
+
28
+ def moderate_post
29
+ return head(:bad_request) unless Thredded::Post.moderation_states.include?(params[:moderation_state])
30
+ flash[:last_moderated_record_id] = ModeratePost.run!(
31
+ post: moderatable_posts.find(params[:id]),
32
+ moderation_state: params[:moderation_state],
33
+ moderator: thredded_current_user,
34
+ ).id
35
+ redirect_back fallback_location: pending_moderation_path
36
+ end
37
+
38
+ private
39
+
40
+ def moderatable_posts
41
+ Thredded::Post.where(messageboard_id: @moderatable_messageboards)
42
+ end
43
+
44
+ def accessible_moderation_records
45
+ Thredded::PostModerationRecord
46
+ .where(messageboard_id: @moderatable_messageboards)
47
+ end
48
+
49
+ def load_moderatable_messageboards
50
+ @moderatable_messageboards = thredded_current_user.thredded_can_moderate_messageboards.to_a
51
+ if @moderatable_messageboards.empty?
52
+ fail Pundit::NotAuthorizedError, 'You are not authorized to perform this action.'
53
+ end
54
+ end
55
+ end
56
+ end
@@ -4,7 +4,7 @@ module Thredded
4
4
  def show
5
5
  post = Post.find(params[:id])
6
6
  authorize post, :read?
7
- redirect_to post_url(post), status: :found
7
+ redirect_to post_url(post, user: thredded_current_user), status: :found
8
8
  end
9
9
  end
10
10
  end
@@ -6,12 +6,14 @@ module Thredded
6
6
  helper_method :topic
7
7
  before_action :update_user_activity
8
8
 
9
+ after_action :verify_authorized
10
+
9
11
  def create
10
12
  post = parent_topic.posts.build(post_params)
11
13
  authorize_creating post
12
14
  post.save!
13
15
 
14
- redirect_to post_path(post)
16
+ redirect_to post_path(post, user: thredded_current_user)
15
17
  end
16
18
 
17
19
  def edit
@@ -22,7 +24,7 @@ module Thredded
22
24
  authorize post, :update?
23
25
  post.update_attributes(post_params.except(:user, :ip))
24
26
 
25
- redirect_to post_path(post)
27
+ redirect_to post_path(post, user: thredded_current_user)
26
28
  end
27
29
 
28
30
  def destroy
@@ -5,7 +5,7 @@ module Thredded
5
5
  def show
6
6
  private_post = PrivatePost.find(params[:id])
7
7
  authorize private_post, :read?
8
- redirect_to post_url(private_post), status: :found
8
+ redirect_to post_url(private_post, user: thredded_current_user), status: :found
9
9
  end
10
10
  end
11
11
  end
@@ -6,7 +6,7 @@ module Thredded
6
6
  before_action :thredded_require_login!
7
7
 
8
8
  def index
9
- @private_topics = Thredded::TopicsPageView.new(
9
+ @private_topics = Thredded::PrivateTopicsPageView.new(
10
10
  thredded_current_user,
11
11
  PrivateTopic
12
12
  .distinct
@@ -29,7 +29,7 @@ module Thredded
29
29
  .includes(:user)
30
30
  .order_oldest_first
31
31
  .page(current_page)
32
- @posts = Thredded::PostsPageView.new(thredded_current_user, private_topic, page_scope)
32
+ @posts = Thredded::TopicPostsPageView.new(thredded_current_user, private_topic, page_scope)
33
33
 
34
34
  if signed_in?
35
35
  UserPrivateTopicReadState.touch!(thredded_current_user.id, private_topic.id, page_scope.last, current_page)
@@ -46,7 +46,6 @@ module Thredded
46
46
  def create
47
47
  @private_topic = PrivateTopicForm.new(new_private_topic_params)
48
48
  if @private_topic.save
49
- NotifyPrivateTopicUsersJob.perform_later(@private_topic.private_topic.id)
50
49
  redirect_to @private_topic.private_topic
51
50
  else
52
51
  render :new
@@ -11,17 +11,17 @@ module Thredded
11
11
  end
12
12
  @messageboards = Messageboard.where(closed: false)
13
13
  @topics = TopicsPageView.new(@user, @messageboard.topics.page(1).limit(3))
14
- @private_topics = TopicsPageView.new(@user, @user.thredded_private_topics.page(1).limit(3))
14
+ @private_topics = PrivateTopicsPageView.new(@user, @user.thredded_private_topics.page(1).limit(3))
15
15
  topic = Topic.new(messageboard: @messageboard, title: 'Hello', slug: 'hello', user: @user)
16
16
  @topic = TopicView.from_user(topic, @user)
17
- @posts = PostsPageView.new(@user, topic, topic.posts.page(1).limit(3))
17
+ @posts = TopicPostsPageView.new(@user, topic, topic.posts.page(1).limit(3))
18
18
  @post = topic.posts.build(id: 1337, postable: topic, content: 'Hello world', user: @user)
19
19
  @new_post = @messageboard.posts.build(postable: topic)
20
20
  @new_topic = TopicForm.new(user: @user, messageboard: @messageboard)
21
21
  @new_private_topic = PrivateTopicForm.new(user: @user)
22
22
  private_topic = PrivateTopic.new(id: 1337, title: 'Hello', user: @user, last_user: @user, users: [@user])
23
23
  @private_topic = PrivateTopicView.from_user(private_topic, @user)
24
- @private_posts = PostsPageView.new(@user, private_topic, private_topic.posts.page(1).limit(3))
24
+ @private_posts = TopicPostsPageView.new(@user, private_topic, private_topic.posts.page(1).limit(3))
25
25
  @private_post = private_topic.posts.build(
26
26
  id: 1337, postable: private_topic, content: 'A private hello world', user: @user
27
27
  )
@@ -4,15 +4,18 @@ require_dependency 'thredded/topics_page_view'
4
4
  module Thredded
5
5
  class TopicsController < Thredded::ApplicationController
6
6
  before_action :thredded_require_login!,
7
- only: [:edit, :new, :update, :create, :destroy]
7
+ only: %i(edit new update create destroy follow unfollow)
8
8
  after_action :update_user_activity
9
9
 
10
+ after_action :verify_authorized, except: %i(search)
11
+ after_action :verify_policy_scoped, except: %i(show new create edit update destroy follow unfollow)
12
+
10
13
  def index
11
14
  authorize_reading messageboard
12
15
 
13
16
  @topics = Thredded::TopicsPageView.new(
14
17
  thredded_current_user,
15
- messageboard.topics
18
+ policy_scope(messageboard.topics)
16
19
  .order_sticky_first.order_recently_updated_first
17
20
  .includes(:categories, :last_user, :user)
18
21
  .page(current_page)
@@ -24,11 +27,11 @@ module Thredded
24
27
 
25
28
  def show
26
29
  authorize topic, :read?
27
- page_scope = topic.posts
28
- .includes(:user, :messageboard, :postable)
30
+ page_scope = policy_scope(topic.posts)
29
31
  .order_oldest_first
32
+ .includes(:user, :messageboard, :postable)
30
33
  .page(current_page)
31
- @posts = Thredded::PostsPageView.new(thredded_current_user, topic, page_scope)
34
+ @posts = Thredded::TopicPostsPageView.new(thredded_current_user, topic, page_scope)
32
35
 
33
36
  UserTopicReadState.touch!(thredded_current_user.id, topic.id, page_scope.last, current_page) if signed_in?
34
37
 
@@ -36,12 +39,15 @@ module Thredded
36
39
  end
37
40
 
38
41
  def search
42
+ authorize_reading messageboard if messageboard_or_nil
39
43
  @query = params[:q].to_s
40
- topics_scope = if messageboard_or_nil
41
- messageboard.topics
42
- else
43
- Topic.where(messageboard_id: thredded_current_user.thredded_can_read_messageboards.pluck(:id))
44
- end
44
+ topics_scope = policy_scope(
45
+ if messageboard_or_nil
46
+ messageboard.topics
47
+ else
48
+ Topic.where(messageboard_id: policy_scope(Messageboard.all).pluck(:id))
49
+ end
50
+ )
45
51
  @topics = Thredded::TopicsPageView.new(
46
52
  thredded_current_user,
47
53
  topics_scope
@@ -62,7 +68,7 @@ module Thredded
62
68
  @category = messageboard.categories.friendly.find(params[:category_id])
63
69
  @topics = Thredded::TopicsPageView.new(
64
70
  thredded_current_user,
65
- @category.topics
71
+ policy_scope(@category.topics)
66
72
  .unstuck
67
73
  .order_recently_updated_first
68
74
  .page(current_page)
@@ -101,6 +107,21 @@ module Thredded
101
107
  notice: t('thredded.topics.deleted_notice')
102
108
  end
103
109
 
110
+ def follow
111
+ authorize topic, :read?
112
+ UserTopicFollow.create_unless_exists(thredded_current_user.id, topic.id)
113
+ redirect_to messageboard_topic_url(messageboard, topic),
114
+ notice: t('thredded.topics.followed_notice')
115
+ end
116
+
117
+ def unfollow
118
+ authorize topic, :read?
119
+ follow = thredded_current_user.following?(topic)
120
+ follow.destroy if follow
121
+ redirect_to messageboard_topic_url(messageboard, topic),
122
+ notice: t('thredded.topics.unfollowed_notice')
123
+ end
124
+
104
125
  private
105
126
 
106
127
  def topic
@@ -36,6 +36,7 @@ module Thredded
36
36
  ActiveRecord::Base.transaction do
37
37
  topic.save!
38
38
  post.save!
39
+ UserTopicReadState.read_on_first_post!(user, topic) if topic.previous_changes.include?(:id)
39
40
  end
40
41
  true
41
42
  end
@@ -20,7 +20,10 @@ module Thredded
20
20
  # @param datetime [DateTime]
21
21
  # @return [String] html_safe datetime presentation
22
22
  def time_ago(datetime)
23
- timeago_tag datetime, lang: I18n.locale.to_s.downcase, format: :short, nojs: true
23
+ timeago_tag datetime,
24
+ lang: I18n.locale.to_s.downcase,
25
+ format: -> (t, _opts) { t.year == Time.current.year ? :short : :long },
26
+ nojs: true
24
27
  end
25
28
 
26
29
  def paginate(collection, args = {})
@@ -36,5 +39,27 @@ module Thredded
36
39
  *('thredded--private-topic' if topic.is_a?(Thredded::PrivateTopicView))
37
40
  ]
38
41
  end
42
+
43
+ def unread_private_topics_count
44
+ @unread_private_topics_count ||=
45
+ if signed_in?
46
+ Thredded::PrivateTopic
47
+ .for_user(thredded_current_user)
48
+ .unread(thredded_current_user)
49
+ .count
50
+ else
51
+ 0
52
+ end
53
+ end
54
+
55
+ def moderatable_messageboards_ids
56
+ @moderatable_messageboards_ids ||=
57
+ thredded_current_user.thredded_can_moderate_messageboards.pluck(:id)
58
+ end
59
+
60
+ def posts_pending_moderation_count
61
+ @posts_pending_moderation_count ||=
62
+ Thredded::Post.where(messageboard_id: moderatable_messageboards_ids).pending_moderation.count
63
+ end
39
64
  end
40
65
  end