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
@@ -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