thredded 0.14.4 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -0
  4. data/app/assets/javascripts/thredded/components/spoilers.es6 +37 -0
  5. data/app/assets/stylesheets/thredded/_email.scss +25 -0
  6. data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
  7. data/app/assets/stylesheets/thredded/base/_typography.scss +1 -1
  8. data/app/assets/stylesheets/thredded/base/_variables.scss +2 -3
  9. data/app/assets/stylesheets/thredded/components/_post.scss +0 -11
  10. data/app/assets/stylesheets/thredded/components/_spoiler.scss +41 -0
  11. data/app/commands/thredded/mark_all_read.rb +1 -7
  12. data/app/controllers/concerns/thredded/new_post_params.rb +1 -1
  13. data/app/controllers/concerns/thredded/new_private_post_params.rb +1 -1
  14. data/app/controllers/concerns/thredded/new_private_topic_params.rb +1 -2
  15. data/app/controllers/concerns/thredded/new_topic_params.rb +0 -1
  16. data/app/controllers/thredded/application_controller.rb +10 -0
  17. data/app/controllers/thredded/posts_controller.rb +1 -2
  18. data/app/controllers/thredded/private_posts_controller.rb +1 -2
  19. data/app/controllers/thredded/private_topics_controller.rb +10 -12
  20. data/app/controllers/thredded/topics_controller.rb +14 -17
  21. data/app/forms/thredded/edit_topic_form.rb +2 -1
  22. data/app/forms/thredded/post_form.rb +3 -1
  23. data/app/forms/thredded/private_post_form.rb +3 -1
  24. data/app/forms/thredded/private_topic_form.rb +1 -0
  25. data/app/jobs/thredded/activity_updater_job.rb +18 -8
  26. data/app/jobs/thredded/auto_follow_and_notify_job.rb +2 -1
  27. data/app/models/concerns/thredded/post_common.rb +3 -4
  28. data/app/models/concerns/thredded/topic_common.rb +7 -0
  29. data/app/models/concerns/thredded/user_topic_read_state_common.rb +62 -5
  30. data/app/models/thredded/null_user_topic_read_state.rb +8 -0
  31. data/app/policies/thredded/private_post_policy.rb +16 -0
  32. data/app/view_hooks/thredded/all_view_hooks.rb +3 -0
  33. data/app/view_models/thredded/base_topic_view.rb +5 -1
  34. data/app/view_models/thredded/post_view.rb +13 -1
  35. data/app/view_models/thredded/posts_page_view.rb +1 -1
  36. data/app/view_models/thredded/topic_posts_page_view.rb +13 -1
  37. data/app/views/thredded/posts/_post.html.erb +1 -0
  38. data/app/views/thredded/posts/edit.html.erb +1 -0
  39. data/app/views/thredded/posts_common/_before_first_unread_post.html.erb +7 -0
  40. data/app/views/thredded/posts_common/form/_after_content.html.erb +8 -0
  41. data/app/views/thredded/posts_common/form/_before_content.html.erb +8 -0
  42. data/app/views/thredded/private_posts/_private_post.html.erb +2 -1
  43. data/app/views/thredded/private_posts/edit.html.erb +1 -0
  44. data/app/views/thredded/private_topics/_form.html.erb +1 -0
  45. data/app/views/thredded/private_topics/edit.html.erb +2 -1
  46. data/app/views/thredded/shared/_field_errors.html.erb +3 -0
  47. data/app/views/thredded/shared/_nav.html.erb +1 -1
  48. data/app/views/thredded/shared/_page.html.erb +1 -1
  49. data/app/views/thredded/topics/_form.html.erb +1 -0
  50. data/app/views/thredded/topics/edit.html.erb +2 -1
  51. data/config/locales/de.yml +2 -0
  52. data/config/locales/en.yml +2 -0
  53. data/config/locales/es.yml +2 -0
  54. data/config/locales/fr.yml +2 -0
  55. data/config/locales/it.yml +2 -0
  56. data/config/locales/pl.yml +2 -0
  57. data/config/locales/pt-BR.yml +2 -0
  58. data/config/locales/ru.yml +2 -0
  59. data/config/locales/zh-CN.yml +2 -0
  60. data/db/migrate/20160329231848_create_thredded.rb +37 -23
  61. data/db/upgrade_migrations/{20170811090735_upgrade_thredded_v0_13_to_v_014.rb → 20170811090735_upgrade_thredded_v0_13_to_v0_14.rb} +0 -0
  62. data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +91 -0
  63. data/lib/generators/thredded/install/templates/initializer.rb +16 -7
  64. data/lib/thredded.rb +143 -125
  65. data/lib/thredded/arel_compat.rb +57 -0
  66. data/lib/thredded/base_migration.rb +10 -0
  67. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +35 -9
  68. data/lib/thredded/content_formatter.rb +27 -18
  69. data/lib/thredded/database_seeder.rb +218 -64
  70. data/lib/thredded/email_transformer.rb +5 -2
  71. data/lib/thredded/email_transformer/spoiler.rb +25 -0
  72. data/lib/thredded/formatting_demo_content.rb +12 -0
  73. data/lib/thredded/html_pipeline/onebox_filter.rb +3 -38
  74. data/lib/thredded/html_pipeline/spoiler_tag_filter.rb +128 -0
  75. data/lib/thredded/html_pipeline/utils.rb +47 -0
  76. data/lib/thredded/rails_lt_5_2_arel_case_node.rb +119 -0
  77. data/lib/thredded/version.rb +1 -1
  78. metadata +17 -21
@@ -11,7 +11,7 @@ module Thredded
11
11
 
12
12
  # @param user [Thredded.user_class]
13
13
  # @param topic [PrivateTopic]
14
- # @param post [Post]
14
+ # @param post [PrivatePost]
15
15
  # @param post_params [Hash]
16
16
  def initialize(user:, topic:, post: nil, post_params: {})
17
17
  @topic = topic
@@ -43,7 +43,9 @@ module Thredded
43
43
 
44
44
  def save
45
45
  return false unless @post.valid?
46
+ was_persisted = @post.persisted?
46
47
  @post.save!
48
+ Thredded::UserPrivateTopicReadState.touch!(@post.user.id, @topic.id, @post) unless was_persisted
47
49
  true
48
50
  end
49
51
  end
@@ -5,6 +5,7 @@ module Thredded
5
5
  include ActiveModel::Model
6
6
 
7
7
  delegate :id,
8
+ :title_was,
8
9
  to: :private_topic
9
10
 
10
11
  attr_accessor \
@@ -7,15 +7,25 @@ module Thredded
7
7
  def perform(user_id, messageboard_id)
8
8
  now = Time.current
9
9
 
10
- user_detail = Thredded::UserDetail.find_or_initialize_by(user_id: user_id)
11
- user_detail.update!(last_seen_at: now)
10
+ begin
11
+ user_detail = Thredded::UserDetail.find_or_initialize_by(user_id: user_id)
12
+ user_detail.update!(last_seen_at: now)
13
+ rescue ActiveRecord::RecordNotUnique
14
+ # The record has been created from another connection, retry to find it.
15
+ retry
16
+ end
12
17
 
13
- Thredded::MessageboardUser
14
- .find_or_initialize_by(
15
- thredded_messageboard_id: messageboard_id,
16
- thredded_user_detail_id: user_detail.id
17
- )
18
- .update!(last_seen_at: now)
18
+ begin
19
+ Thredded::MessageboardUser
20
+ .find_or_initialize_by(
21
+ thredded_messageboard_id: messageboard_id,
22
+ thredded_user_detail_id: user_detail.id
23
+ )
24
+ .update!(last_seen_at: now)
25
+ rescue ActiveRecord::RecordNotUnique
26
+ # The record has been created from another connection, retry to find it.
27
+ retry
28
+ end
19
29
  end
20
30
  end
21
31
  end
@@ -5,7 +5,8 @@ module Thredded
5
5
  queue_as :default
6
6
 
7
7
  def perform(post_id)
8
- post = Thredded::Post.find(post_id)
8
+ post = Thredded::Post.find_by(id: post_id)
9
+ return if post.nil? || post.postable.nil?
9
10
 
10
11
  Thredded::AutofollowUsers.new(post).run
11
12
  Thredded::NotifyFollowingUsers.new(post).run
@@ -50,16 +50,15 @@ module Thredded
50
50
  # Marks all the posts from the given one as unread for the given user
51
51
  # @param user [Thredded.user_class]
52
52
  # @param page [Integer]
53
- def mark_as_unread(user, page)
53
+ def mark_as_unread(user)
54
54
  if previous_post.nil?
55
55
  read_state = postable.user_read_states.find_by(user_id: user.id)
56
56
  read_state.destroy if read_state
57
57
  else
58
58
  read_state = postable.user_read_states.create_with(
59
- read_at: previous_post.created_at,
60
- page: page
59
+ read_at: previous_post.created_at
61
60
  ).find_or_create_by(user_id: user.id)
62
- read_state.update_columns(read_at: previous_post.created_at, page: page)
61
+ read_state.update_columns(read_at: previous_post.created_at)
63
62
  end
64
63
  end
65
64
 
@@ -17,6 +17,8 @@ module Thredded
17
17
  validates :hash_id, presence: true, uniqueness: true
18
18
  validates :posts_count, numericality: true
19
19
 
20
+ validates :title, presence: true, length: { within: Thredded.topic_title_length_range }
21
+
20
22
  before_validation do
21
23
  self.hash_id = SecureRandom.hex(10) if hash_id.nil?
22
24
  end
@@ -56,6 +58,10 @@ module Thredded
56
58
  .merge(reads_class.where(reads[:id].eq(nil).or(reads[:read_at].lt(topics[:last_post_at]))))
57
59
  end
58
60
 
61
+ def post_class
62
+ reflect_on_association(:posts).klass
63
+ end
64
+
59
65
  private
60
66
 
61
67
  # @param user [Thredded.user_class]
@@ -63,6 +69,7 @@ module Thredded
63
69
  def read_states_by_postable_hash(user)
64
70
  read_states = reflect_on_association(:user_read_states).klass
65
71
  .where(user_id: user.id, postable_id: current_scope.map(&:id))
72
+ .with_page_info(posts_scope: Pundit.policy_scope(user, post_class.all))
66
73
  Thredded::TopicCommon::CachingHash.from_relation(read_states)
67
74
  end
68
75
 
@@ -6,6 +6,8 @@ module Thredded
6
6
  included do
7
7
  extend ClassMethods
8
8
  validates :user_id, uniqueness: { scope: :postable_id }
9
+ attribute :first_unread_post_page, ActiveRecord::Type::Integer.new
10
+ attribute :last_read_post_page, ActiveRecord::Type::Integer.new
9
11
  end
10
12
 
11
13
  # @return [Boolean]
@@ -23,20 +25,75 @@ module Thredded
23
25
  # @param user_id [Integer]
24
26
  # @param topic_id [Integer]
25
27
  # @param post [Thredded::PostCommon]
26
- # @param post_page [Integer]
27
- def touch!(user_id, topic_id, post, post_page)
28
+ def touch!(user_id, topic_id, post)
28
29
  # TODO: Switch to upsert once Travis supports PostgreSQL 9.5.
29
30
  # Travis issue: https://github.com/travis-ci/travis-ci/issues/4264
30
31
  # Upsert gem: https://github.com/seamusabshere/upsert
31
32
  state = find_or_initialize_by(user_id: user_id, postable_id: topic_id)
32
- fail ArgumentError, "expected post_page >= 1, given #{post_page.inspect}" if post_page < 1
33
33
  return unless !state.read_at? || state.read_at < post.created_at
34
- state.update!(read_at: post.created_at, page: post_page)
34
+ state.update!(read_at: post.created_at)
35
35
  end
36
36
 
37
37
  def read_on_first_post!(user, topic)
38
- create!(user: user, postable: topic, read_at: Time.zone.now, page: 1)
38
+ create!(user: user, postable: topic, read_at: Time.zone.now)
39
39
  end
40
+
41
+ # Adds `first_unread_post_page` and `last_read_post_page` columns onto the scope.
42
+ # Skips the records that have no read posts.
43
+ def with_page_info( # rubocop:disable Metrics/MethodLength
44
+ posts_per_page: topic_class.default_per_page, posts_scope: post_class.all
45
+ )
46
+ states = arel_table
47
+ self_relation = is_a?(ActiveRecord::Relation) ? self : all
48
+ if self_relation == unscoped
49
+ states_select_manager = states
50
+ else
51
+ # Using the relation here is redundant but massively improves performance.
52
+ states_select_manager = Thredded::ArelCompat.new_arel_select_manager(
53
+ Arel::Nodes::TableAlias.new(Thredded::ArelCompat.relation_to_arel(self_relation), table_name)
54
+ )
55
+ end
56
+ read = if posts_scope == post_class.unscoped
57
+ post_class.arel_table
58
+ else
59
+ posts_subquery = Thredded::ArelCompat.relation_to_arel(posts_scope)
60
+ Arel::Nodes::TableAlias.new(posts_subquery, 'read_posts')
61
+ end
62
+ unread_topics = topic_class.arel_table
63
+ page_info =
64
+ states_select_manager
65
+ .project(
66
+ states[:id],
67
+ Arel::Nodes::Case.new(unread_topics[:id].not_eq(nil))
68
+ .when(Thredded::ArelCompat.true_value(self)).then(
69
+ Arel::Nodes::Addition.new(
70
+ Thredded::ArelCompat.integer_division(self, read[:id].count, posts_per_page), 1
71
+ )
72
+ ).else(nil)
73
+ .as('first_unread_post_page'),
74
+ Arel::Nodes::Addition.new(
75
+ Thredded::ArelCompat.integer_division(self, read[:id].count, posts_per_page),
76
+ Arel::Nodes::Case.new(Arel::Nodes::InfixOperation.new(:%, read[:id].count, posts_per_page))
77
+ .when(0).then(0).else(1)
78
+ ).as('last_read_post_page')
79
+ )
80
+ .join(read)
81
+ .on(read[:postable_id].eq(states[:postable_id]).and(read[:created_at].lteq(states[:read_at])))
82
+ .outer_join(unread_topics)
83
+ .on(states[:postable_id].eq(unread_topics[:id]).and(unread_topics[:last_post_at].gt(states[:read_at])))
84
+ .group(states[:id], unread_topics[:id])
85
+ .as('id_and_page_info')
86
+
87
+ # We use a subquery because selected fields must appear in the GROUP BY or be used in an aggregate function.
88
+ select(states[Arel.star], page_info[:first_unread_post_page], page_info[:last_read_post_page])
89
+ .joins(states.join(page_info).on(states[:id].eq(page_info[:id])).join_sources)
90
+ end
91
+
92
+ def topic_class
93
+ reflect_on_association(:postable).klass
94
+ end
95
+
96
+ delegate :post_class, to: :topic_class
40
97
  end
41
98
  end
42
99
  end
@@ -13,5 +13,13 @@ module Thredded
13
13
  def post_read?(_post)
14
14
  false
15
15
  end
16
+
17
+ def first_unread_post_page
18
+ nil
19
+ end
20
+
21
+ def last_read_post_page
22
+ 1
23
+ end
16
24
  end
17
25
  end
@@ -2,6 +2,22 @@
2
2
 
3
3
  module Thredded
4
4
  class PrivatePostPolicy
5
+ # The scope of readable private posts.
6
+ # {PrivateTopicPolicy} 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
18
+ end
19
+ end
20
+
5
21
  # @param user [Thredded.user_class]
6
22
  # @param post [Thredded::PrivatePost]
7
23
  def initialize(user, post)
@@ -35,12 +35,15 @@ module Thredded
35
35
 
36
36
  # View hooks for collections of public or private posts.
37
37
  class PostsCommon
38
+ # @return [Thredded::AllViewHooks::ViewHook]
39
+ attr_reader :before_first_unread_post
38
40
  # @return [Thredded::AllViewHooks::ViewHook]
39
41
  attr_reader :pagination_top
40
42
  # @return [Thredded::AllViewHooks::ViewHook]
41
43
  attr_reader :pagination_bottom
42
44
 
43
45
  def initialize
46
+ @before_first_unread_post = ViewHook.new
44
47
  @pagination_top = ViewHook.new
45
48
  @pagination_bottom = ViewHook.new
46
49
  end
@@ -37,7 +37,11 @@ module Thredded
37
37
  end
38
38
 
39
39
  def path
40
- Thredded::UrlsHelper.topic_path(@topic, page: @read_state.page)
40
+ Thredded::UrlsHelper.topic_path(
41
+ @topic,
42
+ page: @read_state.first_unread_post_page || @read_state.last_read_post_page,
43
+ anchor: ('unread' if @read_state.first_unread_post_page)
44
+ )
41
45
  end
42
46
  end
43
47
  end
@@ -17,10 +17,14 @@ module Thredded
17
17
  # @param post [Thredded::PostCommon]
18
18
  # @param policy [#create? #update? #destroy? #moderate?]
19
19
  # @param topic_view [Thredded::TopicView]
20
- def initialize(post, policy, topic_view: nil)
20
+ # @param first_in_page [Boolean]
21
+ # @param first_unread_in_page [Boolean]
22
+ def initialize(post, policy, topic_view: nil, first_in_page: false, first_unread_in_page: false)
21
23
  @post = post
22
24
  @policy = policy
23
25
  @topic_view = topic_view
26
+ @first_unread_in_page = first_unread_in_page
27
+ @first_in_page = first_in_page
24
28
  end
25
29
 
26
30
  def can_reply?
@@ -85,5 +89,13 @@ module Thredded
85
89
  POST_IS_UNREAD
86
90
  end
87
91
  end
92
+
93
+ def first_unread_in_page?
94
+ @first_unread_in_page
95
+ end
96
+
97
+ def first_in_page?
98
+ @first_in_page
99
+ end
88
100
  end
89
101
  end
@@ -19,7 +19,7 @@ module Thredded
19
19
  # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>]
20
20
  def initialize(user, paginated_scope, topic_view: nil)
21
21
  @paginated_scope = paginated_scope
22
- @post_views = paginated_scope.map do |post|
22
+ @post_views = paginated_scope.map do |post|
23
23
  Thredded::PostView.new(post, Pundit.policy!(user, post), topic_view: topic_view)
24
24
  end
25
25
  end
@@ -10,8 +10,20 @@ module Thredded
10
10
  # @param topic [Thredded::TopicCommon]
11
11
  # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>]
12
12
  def initialize(user, topic, paginated_scope)
13
+ @paginated_scope = paginated_scope
13
14
  @topic = "#{paginated_scope.reflect_on_association(:postable).klass}View".constantize.from_user(topic, user)
14
- super(user, paginated_scope, topic_view: @topic)
15
+ prev_read = false
16
+ @post_views = paginated_scope.map.with_index do |post, i|
17
+ post_read = @topic.post_read?(post)
18
+ post_view = Thredded::PostView.new(
19
+ post, Pundit.policy!(user, post),
20
+ topic_view: @topic,
21
+ first_in_page: i.zero?,
22
+ first_unread_in_page: !post_read && prev_read
23
+ )
24
+ prev_read = post_read
25
+ post_view
26
+ end
15
27
  end
16
28
  end
17
29
  end
@@ -1,4 +1,5 @@
1
1
  <% post, content = post_and_content if local_assigns.key?(:post_and_content) %>
2
+ <%= render 'thredded/posts_common/before_first_unread_post', post: post if post.first_unread_in_page? %>
2
3
  <%= content_tag :article, id: dom_id(post), class: "thredded--post thredded--#{post.read_state}--post" do %>
3
4
  <%= render 'thredded/posts_common/actions', post: post, actions: local_assigns[:actions] %>
4
5
  <%= render 'thredded/posts_common/header', post: post %>
@@ -2,6 +2,7 @@
2
2
  <% content_for :thredded_page_id, 'thredded--edit-post' %>
3
3
  <% content_for :thredded_breadcrumbs do %>
4
4
  <ul class="thredded--navigation-breadcrumbs">
5
+ <li><%= link_to @post_form.topic.title, post_path(@post_form.post, user: thredded_current_user) %></li>
5
6
  <li><%= link_to t('thredded.nav.edit_post'), edit_post_path(@post_form.post) %></li>
6
7
  </ul>
7
8
  <% end %>
@@ -0,0 +1,7 @@
1
+ <div class="thredded--before-first-unread-post<%= ' unread--before-first-unread-post--first-in-page' if post.first_in_page? %>">
2
+ <%= view_hooks.posts_common.before_first_unread_post.render(self, post: post) do %>
3
+ <% unless post.first_in_page? %>
4
+ <span id="unread"></span>
5
+ <% end %>
6
+ <% end %>
7
+ </div>
@@ -0,0 +1,8 @@
1
+ <%#
2
+ # This partial is deprecated. Add a view hook to the initializer instead, for example:
3
+
4
+ Thredded.view_hooks.post_form.content_text_area.config.after do
5
+ # This is rendered in the Thredded view context, so all Thredded helpers and URLs are accessible here directly.
6
+ render partial: 'my_partial'
7
+ end
8
+ %>
@@ -0,0 +1,8 @@
1
+ <%#
2
+ # This partial is deprecated. Add a view hook to the initializer instead, for example:
3
+
4
+ Thredded.view_hooks.post_form.content_text_area.config.before do
5
+ # This is rendered in the Thredded view context, so all Thredded helpers and URLs are accessible here directly.
6
+ render partial: 'my_partial'
7
+ end
8
+ %>
@@ -1,5 +1,6 @@
1
1
  <% private_post, content = post_and_content if local_assigns.key?(:post_and_content) %>
2
- <%= content_tag :article, id: dom_id(private_post), class: 'thredded--post' do %>
2
+ <%= render 'thredded/posts_common/before_first_unread_post', post: private_post if private_post.first_unread_in_page? %>
3
+ <%= content_tag :article, id: dom_id(private_post), class: "thredded--post thredded--#{private_post.read_state}--post" do %>
3
4
  <%= render 'thredded/posts_common/actions', post: private_post, actions: local_assigns[:actions] %>
4
5
  <%= render 'thredded/posts_common/header', post: private_post %>
5
6
  <%= content || render('thredded/private_posts/content', post: post) %>
@@ -2,6 +2,7 @@
2
2
  <% content_for :thredded_page_id, 'thredded--edit-post' %>
3
3
  <% content_for :thredded_breadcrumbs do %>
4
4
  <ul class="thredded--navigation-breadcrumbs">
5
+ <li><%= link_to @post_form.topic.title, post_path(@post_form.post, user: thredded_current_user) %></li>
5
6
  <li><%= link_to t('thredded.nav.edit_post'), edit_post_path(@post_form.post) %></li>
6
7
  </ul>
7
8
  <% end %>
@@ -12,6 +12,7 @@
12
12
  <li class="title">
13
13
  <%= form.label :title, t('thredded.private_topics.form.title_label') %>
14
14
  <%= form.text_field :title, placeholder: placeholder, required: true %>
15
+ <%= render 'thredded/shared/field_errors', messages: form.object.errors[:title] if form.object.errors.include?(:title) %>
15
16
  </li>
16
17
  <li>
17
18
  <%= form.label :user_names, t('thredded.private_topics.form.users_label') %>
@@ -4,7 +4,7 @@
4
4
  <ul class="thredded--navigation-breadcrumbs">
5
5
  <li><%= link_to t('thredded.nav.all_messageboards'), messageboards_path %></li>
6
6
  <li><%= link_to t('thredded.nav.private_topics'), private_topics_path %></li>
7
- <li><%= link_to @private_topic.title, topic_path(@private_topic) %></li>
7
+ <li><%= link_to @private_topic.title_was || @private_topic.title, topic_path(@private_topic) %></li>
8
8
  <li><%= link_to t('thredded.nav.edit_private_topic'), edit_private_topic_path(@private_topic) %></li>
9
9
  </ul>
10
10
  <% end %>
@@ -22,6 +22,7 @@
22
22
  placeholder: t('thredded.private_topics.form.title_placeholder_new'),
23
23
  autofocus: true,
24
24
  required: true %>
25
+ <%= render 'thredded/shared/field_errors', messages: form.object.errors[:title] if form.object.errors.include?(:title) %>
25
26
  </li>
26
27
  <li>
27
28
  <button type="submit" class="thredded--form--submit"
@@ -0,0 +1,3 @@
1
+ <div class="thredded--alert thredded--alert-danger">
2
+ <%= safe_join messages, raw("<br>") %>
3
+ </div>
@@ -2,7 +2,7 @@
2
2
  <ul class="thredded--user-navigation<%= ' thredded--user-navigation-standalone' if Thredded.standalone_layout? %>">
3
3
  <%= render 'thredded/shared/nav/moderation' %>
4
4
  <%= render 'thredded/shared/nav/notification_preferences', messageboard: messageboard_or_nil %>
5
- <%= render 'thredded/shared/nav/private_topics' %>
5
+ <%= render 'thredded/shared/nav/private_topics' if Thredded.private_messaging_enabled %>
6
6
  <% if Thredded.standalone_layout? %>
7
7
  <%= render 'thredded/shared/nav/standalone_profile' if thredded_signed_in? %>
8
8
  <%= render 'thredded/shared/nav/standalone' %>