thredded 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -14,6 +14,10 @@ module Thredded
14
14
  belongs_to :user,
15
15
  class_name: Thredded.user_class,
16
16
  inverse_of: :thredded_private_topics
17
+ belongs_to :user_detail,
18
+ primary_key: :user_id,
19
+ foreign_key: :user_id,
20
+ inverse_of: :private_topics
17
21
 
18
22
  has_many :posts,
19
23
  class_name: 'Thredded::PrivatePost',
@@ -54,6 +58,10 @@ module Thredded
54
58
  false
55
59
  end
56
60
 
61
+ def user_detail
62
+ super || build_user_detail
63
+ end
64
+
57
65
  def should_generate_new_friendly_id?
58
66
  title_changed?
59
67
  end
@@ -3,6 +3,7 @@ require_dependency 'thredded/topics_search'
3
3
  module Thredded
4
4
  class Topic < ActiveRecord::Base
5
5
  include TopicCommon
6
+ include ContentModerationState
6
7
 
7
8
  scope :for_messageboard, -> messageboard { where(messageboard_id: messageboard.id) }
8
9
 
@@ -53,6 +54,14 @@ module Thredded
53
54
  foreign_key: :postable_id,
54
55
  inverse_of: :postable,
55
56
  dependent: :destroy
57
+ has_many :user_follows,
58
+ class_name: 'Thredded::UserTopicFollow',
59
+ inverse_of: :topic,
60
+ dependent: :destroy
61
+ has_many :following_users,
62
+ class_name: Thredded.user_class,
63
+ source: :user,
64
+ through: :user_follows
56
65
 
57
66
  def self.find_by_slug!(slug)
58
67
  friendly.find(slug)
@@ -60,10 +69,40 @@ module Thredded
60
69
  raise Thredded::Errors::TopicNotFound
61
70
  end
62
71
 
72
+ class << self
73
+ private
74
+
75
+ # @param user [Thredded.user_class]
76
+ # @return [ByPostableLookup]
77
+ def follows_by_topic_hash(user)
78
+ Thredded::TopicCommon::CachingHash.from_relation(
79
+ Thredded::UserTopicFollow.where(user_id: user.id, topic_id: current_scope.map(&:id))
80
+ )
81
+ end
82
+
83
+ public
84
+
85
+ # @param user [Thredded.user_class]
86
+ # @return [Array<[TopicCommon, UserTopicReadStateCommon, UserTopicFollow]>]
87
+ def with_read_and_follow_states(user)
88
+ null_read_state = Thredded::NullUserTopicReadState.new
89
+ return current_scope.zip([null_read_state, nil]) if user.thredded_anonymous?
90
+ read_states_by_topic = read_states_by_postable_hash(user)
91
+ follows_by_topic = follows_by_topic_hash(user)
92
+ current_scope.map do |topic|
93
+ [topic, read_states_by_topic[topic] || null_read_state, follows_by_topic[topic]]
94
+ end
95
+ end
96
+ end
97
+
63
98
  def public?
64
99
  true
65
100
  end
66
101
 
102
+ def user_detail
103
+ super || build_user_detail
104
+ end
105
+
67
106
  def should_generate_new_friendly_id?
68
107
  title_changed?
69
108
  end
@@ -1,13 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class UserDetail < ActiveRecord::Base
4
+ include ModerationState
5
+
4
6
  belongs_to :user, class_name: Thredded.user_class, inverse_of: :thredded_user_detail
5
7
  validates :user_id, presence: true
6
8
 
7
9
  has_many :topics, class_name: 'Thredded::Topic', foreign_key: :user_id, primary_key: :user_id
10
+ has_many :private_topics, class_name: 'Thredded::PrivateTopic', foreign_key: :user_id, primary_key: :user_id
8
11
  has_many :posts, class_name: 'Thredded::Post', foreign_key: :user_id, primary_key: :user_id
9
12
  has_many :private_posts, class_name: 'Thredded::PrivatePost', foreign_key: :user_id, primary_key: :user_id
10
13
 
11
14
  scope :recently_active, -> { where(arel_table[:last_seen_at].gt(Thredded.active_user_threshold.ago)) }
15
+
16
+ before_save :set_moderation_state_changed_at
17
+
18
+ private
19
+
20
+ def set_moderation_state_changed_at
21
+ self.moderation_state_changed_at = Time.current if moderation_state_changed?
22
+ end
12
23
  end
13
24
  end
@@ -27,6 +27,7 @@ module Thredded
27
27
  opt.has_many :thredded_private_users, class_name: 'Thredded::PrivateUser'
28
28
  opt.has_many :thredded_topic_read_states, class_name: 'Thredded::UserTopicReadState'
29
29
  opt.has_many :thredded_private_topic_read_states, class_name: 'Thredded::UserPrivateTopicReadState'
30
+ opt.has_many :thredded_topic_follows, class_name: 'Thredded::UserTopicFollow'
30
31
  opt.has_one :thredded_user_detail, class_name: 'Thredded::UserDetail'
31
32
  opt.has_one :thredded_user_preference, class_name: 'Thredded::UserPreference'
32
33
  end
@@ -35,14 +36,27 @@ module Thredded
35
36
  through: :thredded_private_users,
36
37
  class_name: 'Thredded::PrivateTopic',
37
38
  source: :private_topic
39
+
40
+ with_options dependent: :nullify, class_name: 'Thredded::PostModerationRecord' do |opt|
41
+ opt.has_many :thredded_post_moderation_records, foreign_key: 'post_user_id', inverse_of: :post_user
42
+ opt.has_many :thredded_post_moderated_records, foreign_key: 'moderator_id', inverse_of: :moderator
43
+ end
38
44
  end
39
45
 
40
46
  def thredded_user_preference
41
47
  super || build_thredded_user_preference
42
48
  end
43
49
 
50
+ def thredded_user_detail
51
+ super || build_thredded_user_detail
52
+ end
53
+
44
54
  def thredded_anonymous?
45
55
  false
46
56
  end
57
+
58
+ def following?(topic)
59
+ UserTopicFollow.find_by(topic_id: topic.id, user: self)
60
+ end
47
61
  end
48
62
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class UserTopicFollow < ActiveRecord::Base
4
+ enum reason: [:manual, :posted, :mentioned]
5
+
6
+ belongs_to :user, inverse_of: :thredded_topic_follows
7
+ belongs_to :topic, inverse_of: :user_follows
8
+
9
+ validates :user_id, presence: true
10
+ validates :topic_id, presence: true
11
+
12
+ # shim to behave like postable-related (though actually only ever related to topic)
13
+ alias_attribute :postable_id, :topic_id
14
+ alias_attribute :postable, :topic
15
+
16
+ def self.create_unless_exists(user_id, topic_id, reason = :manual)
17
+ create_with(reason: reason).find_or_create_by(user_id: user_id, topic_id: topic_id)
18
+ end
19
+ end
20
+ end
@@ -1,6 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class MessageboardPolicy
4
+ # The scope of readable messageboards
5
+ class Scope
6
+ # @param user [Thredded.user_class]
7
+ # @param scope [ActiveRecord::Relation<Thredded::Messageboard>]
8
+ def initialize(user, scope)
9
+ @user = user
10
+ @scope = scope
11
+ end
12
+
13
+ # @return [ActiveRecord::Relation<Thredded::Messageboards>]
14
+ def resolve
15
+ @scope.merge(@user.thredded_can_read_messageboards)
16
+ end
17
+ end
18
+
4
19
  # @param user [Thredded.user_class]
5
20
  # @param messageboard [Thredded::Messageboard]
6
21
  def initialize(user, messageboard)
@@ -2,6 +2,22 @@
2
2
  require_dependency 'thredded/topic_policy'
3
3
  module Thredded
4
4
  class PostPolicy
5
+ # The scope of readable posts.
6
+ # MessageboardPolicy must be applied separately.
7
+ class Scope
8
+ # @param user [Thredded.user_class]
9
+ # @param scope [ActiveRecord::Relation<Thredded::Post>]
10
+ def initialize(user, scope)
11
+ @user = user
12
+ @scope = scope
13
+ end
14
+
15
+ # @return [ActiveRecord::Relation<Thredded::Post>]
16
+ def resolve
17
+ @scope.moderation_state_visible_to_user(@user)
18
+ end
19
+ end
20
+
5
21
  # @param user [Thredded.user_class]
6
22
  # @param post [Thredded::Post]
7
23
  def initialize(user, post)
@@ -14,7 +30,7 @@ module Thredded
14
30
  end
15
31
 
16
32
  def read?
17
- TopicPolicy.new(@user, @post.postable).read?
33
+ TopicPolicy.new(@user, @post.postable).read? && @post.moderation_state_visible_to_user?(@user)
18
34
  end
19
35
 
20
36
  def update?
@@ -10,7 +10,7 @@ module Thredded
10
10
  end
11
11
 
12
12
  def create?
13
- @user.thredded_admin? || @post.postable.users.include?(@user)
13
+ @user.thredded_admin? || @post.postable.users.include?(@user) && !@user.thredded_user_detail.blocked?
14
14
  end
15
15
 
16
16
  def read?
@@ -9,7 +9,7 @@ module Thredded
9
9
  end
10
10
 
11
11
  def create?
12
- !@user.thredded_anonymous?
12
+ !@user.thredded_anonymous? && !@user.thredded_user_detail.blocked?
13
13
  end
14
14
 
15
15
  def read?
@@ -1,6 +1,23 @@
1
1
  # frozen_string_literal: true
2
+ require_dependency 'thredded/messageboard_policy'
2
3
  module Thredded
3
4
  class TopicPolicy
5
+ # The scope of readable topics.
6
+ # MessageboardPolicy must be applied separately.
7
+ class Scope
8
+ # @param user [Thredded.user_class]
9
+ # @param scope [ActiveRecord::Relation<Thredded::Topic>]
10
+ def initialize(user, scope)
11
+ @user = user
12
+ @scope = scope
13
+ end
14
+
15
+ # @return [ActiveRecord::Relation<Thredded::Topic>]
16
+ def resolve
17
+ @scope.moderation_state_visible_to_user(@user)
18
+ end
19
+ end
20
+
4
21
  # @param user [Thredded.user_class]
5
22
  # @param topic [Thredded::Topic]
6
23
  def initialize(user, topic)
@@ -14,7 +31,7 @@ module Thredded
14
31
  end
15
32
 
16
33
  def read?
17
- @messageboard_user_permission.read?
34
+ @messageboard_user_permission.read? && @topic.moderation_state_visible_to_user?(@user)
18
35
  end
19
36
 
20
37
  def update?
@@ -39,18 +39,5 @@ module Thredded
39
39
  def path
40
40
  Thredded::UrlsHelper.topic_path(@topic, page: @read_state.page)
41
41
  end
42
-
43
- def self.inherited(subclass)
44
- subclass.extend ClassMethods
45
- end
46
-
47
- module ClassMethods
48
- def from_user(topic, user)
49
- read_state = if user && !user.thredded_anonymous?
50
- topic.association(:user_read_states).klass.find_by(user_id: user.id, postable_id: topic.id)
51
- end
52
- new(topic, read_state, Pundit.policy!(user, topic))
53
- end
54
- end
55
42
  end
56
43
  end
@@ -8,6 +8,9 @@ module Thredded
8
8
  :created_at,
9
9
  :user,
10
10
  :to_model,
11
+ :pending_moderation?,
12
+ :approved?,
13
+ :blocked?,
11
14
  to: :@post
12
15
 
13
16
  # @param post [Thredded::PostCommon]
@@ -38,7 +41,11 @@ module Thredded
38
41
  I18n.locale,
39
42
  @post,
40
43
  @post.user,
41
- [can_update?, can_destroy?].map { |p| p ? '+' : '-' } * ''
44
+ [
45
+ !@post.private_topic_post? && @post.pending_moderation? && !Thredded.content_visible_while_pending_moderation,
46
+ can_update?,
47
+ can_destroy?
48
+ ].map { |p| p ? '+' : '-' } * ''
42
49
  ]
43
50
  end
44
51
  end
@@ -6,22 +6,20 @@ module Thredded
6
6
  # A view model for a page of PostViews.
7
7
  class PostsPageView
8
8
  delegate :to_ary,
9
- to: :@topic_views
9
+ to: :@post_views
10
10
  delegate :total_pages,
11
11
  :current_page,
12
12
  :limit_value,
13
- to: :@topics_page_scope
13
+ to: :@paginated_scope
14
14
 
15
15
  # @return [Thredded::BaseTopicView]
16
16
  attr_reader :topic
17
17
 
18
18
  # @param user [Thredded.user_class] the user who is viewing the posts page
19
- # @param topic [Thredded::TopicCommon]
20
- # @param posts_page_scope [ActiveRecord::Relation<Thredded::PostCommon>]
21
- def initialize(user, topic, posts_page_scope)
22
- @topics_page_scope = posts_page_scope
23
- @topic_views = posts_page_scope.map { |post| PostView.new(post, Pundit.policy!(user, post)) }
24
- @topic = "#{posts_page_scope.reflect_on_association(:postable).klass}View".constantize.from_user(topic, user)
19
+ # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>]
20
+ def initialize(user, paginated_scope)
21
+ @paginated_scope = paginated_scope
22
+ @post_views = paginated_scope.map { |post| PostView.new(post, Pundit.policy!(user, post)) }
25
23
  end
26
24
  end
27
25
  end
@@ -5,5 +5,13 @@ module Thredded
5
5
  def edit_path
6
6
  Thredded::UrlsHelper.edit_private_topic_path(@topic)
7
7
  end
8
+
9
+ def self.from_user(topic, user)
10
+ read_state = if user && !user.thredded_anonymous?
11
+ UserPrivateTopicReadState
12
+ .find_by(user_id: user.id, postable_id: topic.id)
13
+ end
14
+ new(topic, read_state, Pundit.policy!(user, topic))
15
+ end
8
16
  end
9
17
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ require_dependency 'thredded/private_topic_view'
3
+ module Thredded
4
+ # A view model for a page of BaseTopicViews.
5
+ class PrivateTopicsPageView
6
+ delegate :to_ary,
7
+ :blank?,
8
+ :empty?,
9
+ to: :@topic_views
10
+ delegate :total_pages,
11
+ :current_page,
12
+ :limit_value,
13
+ to: :@topics_page_scope
14
+
15
+ # @param user [Thredded.user_class] the user who is viewing the posts page
16
+ # @param topics_page_scope [ActiveRecord::Relation<Thredded::Topic>]
17
+ def initialize(user, topics_page_scope)
18
+ @topics_page_scope = topics_page_scope
19
+ @topic_views = @topics_page_scope.with_read_states(user).map do |(topic, read_state)|
20
+ PrivateTopicView.new(topic, read_state, Pundit.policy!(user, topic))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require_dependency 'thredded/posts_page_view'
3
+ module Thredded
4
+ # A view model for a page of PostViews of a Topic.
5
+ class TopicPostsPageView < PostsPageView
6
+ # @return [Thredded::BaseTopicView]
7
+ attr_reader :topic
8
+
9
+ # @param user [Thredded.user_class] the user who is viewing the posts page
10
+ # @param topic [Thredded::TopicCommon]
11
+ # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>]
12
+ def initialize(user, topic, paginated_scope)
13
+ super(user, paginated_scope)
14
+ @topic = "#{paginated_scope.reflect_on_association(:postable).klass}View".constantize.from_user(topic, user)
15
+ end
16
+ end
17
+ end
@@ -2,13 +2,31 @@
2
2
  module Thredded
3
3
  # A view model for Topic.
4
4
  class TopicView < BaseTopicView
5
- delegate :categories,
5
+ delegate :categories, :id,
6
6
  to: :@topic
7
7
 
8
+ # @param topic [TopicCommon]
9
+ # @param read_state [UserTopicReadStateCommon, nil]
10
+ # @param policy [#destroy?]
11
+ def initialize(topic, read_state, follow, policy)
12
+ super(topic, read_state, policy)
13
+ @follow = follow
14
+ end
15
+
16
+ def self.from_user(topic, user)
17
+ read_state = follow = nil
18
+ if user && !user.thredded_anonymous?
19
+ read_state = UserTopicReadState.find_by(user_id: user.id, postable_id: topic.id)
20
+ follow = UserTopicFollow.find_by(user_id: user.id, topic_id: topic.id)
21
+ end
22
+ new(topic, read_state, follow, Pundit.policy!(user, topic))
23
+ end
24
+
8
25
  def states
9
26
  super + [
10
27
  (:locked if @topic.locked?),
11
28
  (:sticky if @topic.sticky?),
29
+ (@follow ? :following : :notfollowing)
12
30
  ].compact
13
31
  end
14
32
 
@@ -19,5 +37,13 @@ module Thredded
19
37
  def destroy_path
20
38
  Thredded::UrlsHelper.messageboard_topic_path(@topic.messageboard, @topic)
21
39
  end
40
+
41
+ def follow_path
42
+ Thredded::UrlsHelper.follow_messageboard_topic_path(@topic.messageboard, @topic)
43
+ end
44
+
45
+ def unfollow_path
46
+ Thredded::UrlsHelper.unfollow_messageboard_topic_path(@topic.messageboard, @topic)
47
+ end
22
48
  end
23
49
  end