thredded 0.14.4 → 0.15.1

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 (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' %>