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
@@ -2,7 +2,7 @@
2
2
  <%= render 'thredded/shared/header' %>
3
3
  <%= render 'thredded/shared/flash_messages' %>
4
4
  <%= yield :thredded_page_content %>
5
- <%= render 'thredded/shared/currently_online' %>
5
+ <%= render 'thredded/shared/currently_online' if Thredded.currently_online_enabled %>
6
6
  <%- end %>
7
7
 
8
8
  <%# If thredded JS is loaded via an [async] script, the JS may
@@ -10,6 +10,7 @@
10
10
  <li class="title">
11
11
  <%= form.label :title, t('thredded.topics.form.title_label') %>
12
12
  <%= form.text_field :title, placeholder: placeholder, required: true %>
13
+ <%= render 'thredded/shared/field_errors', messages: form.object.errors[:title] if form.object.errors.include?(:title) %>
13
14
  </li>
14
15
 
15
16
  <% if form.object.category_options.any? %>
@@ -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 messageboard.name, @edit_topic.messageboard_path %></li>
7
- <li><%= link_to @edit_topic.title, @edit_topic.path %></li>
7
+ <li><%= link_to @edit_topic.title_was || @edit_topic.title, @edit_topic.path %></li>
8
8
  <li><%= link_to t('thredded.nav.edit_topic'), @edit_topic.edit_path %></li>
9
9
  </ul>
10
10
  <% end %>
@@ -22,6 +22,7 @@
22
22
  placeholder: t('thredded.topics.form.title_placeholder'),
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
 
27
28
  <% if form.object.category_options.any? %>
@@ -132,6 +132,8 @@ de:
132
132
  update_btn_submitting: :thredded.form.update_btn_submitting
133
133
  pending_moderation_notice: Dein Beitrag wird veröffentlicht, sobald ein Moderator ihn angeschaut hat.
134
134
  quote_btn: Zitat
135
+ spoiler_summary: Spoiler - klicken um zu zeigen.
136
+ spoiler_summary_for_email: 'Spoiler - Wählen Sie den Inhalt unten, um zu sehen:'
135
137
  preferences:
136
138
  edit:
137
139
  page_title: :thredded.nav.settings
@@ -131,6 +131,8 @@ en:
131
131
  update_btn_submitting: :thredded.form.update_btn_submitting
132
132
  pending_moderation_notice: Your post will be published when it has been reviewed by a moderator.
133
133
  quote_btn: Quote
134
+ spoiler_summary: Spoiler - click to show.
135
+ spoiler_summary_for_email: 'Spoiler - select the contents below to see:'
134
136
  preferences:
135
137
  edit:
136
138
  page_title: :thredded.nav.settings
@@ -133,6 +133,8 @@ es:
133
133
  update_btn_submitting: :thredded.form.update_btn_submitting
134
134
  pending_moderation_notice: Tu post será publicado cuando haya sido revisado por un moderador.
135
135
  quote_btn: Citar
136
+ spoiler_summary: 'Spoiler: haz clic para mostrar.'
137
+ spoiler_summary_for_email: 'Spoiler: selecciona el contenido a continuación para ver:'
136
138
  preferences:
137
139
  edit:
138
140
  page_title: :thredded.nav.settings
@@ -131,6 +131,8 @@ fr:
131
131
  update_btn_submitting: :thredded.form.update_btn_submitting
132
132
  pending_moderation_notice: Votre commentaire sera publié dès qu'il aura été validé par un modérateur.
133
133
  quote_btn: Citer
134
+ spoiler_summary: Spoiler - cliquez pour afficher.
135
+ spoiler_summary_for_email: 'Spoiler - sélectionnez le contenu ci-dessous pour voir:'
134
136
  preferences:
135
137
  edit:
136
138
  page_title: :thredded.nav.settings
@@ -131,6 +131,8 @@ it:
131
131
  update_btn_submitting: :thredded.form.update_btn_submitting
132
132
  pending_moderation_notice: Il tuo messaggio sarà pubblicato dopo essere stato controllato da un moderatore.
133
133
  quote_btn: Citazione
134
+ spoiler_summary: Spoiler - clicca per mostrare.
135
+ spoiler_summary_for_email: 'Spoiler: seleziona i contenuti sotto per vedere:'
134
136
  preferences:
135
137
  edit:
136
138
  page_title: :thredded.nav.settings
@@ -131,6 +131,8 @@ pl:
131
131
  update_btn_submitting: :thredded.form.update_btn_submitting
132
132
  pending_moderation_notice: Twój post zostanie zamieszczony po zweryfikowaniu go przez moderatora.
133
133
  quote_btn: Zacytować
134
+ spoiler_summary: Spoiler - kliknij, aby pokazać.
135
+ spoiler_summary_for_email: 'Spoiler - wybierz zawartość poniżej, aby zobaczyć:'
134
136
  preferences:
135
137
  edit:
136
138
  page_title: :thredded.nav.settings
@@ -133,6 +133,8 @@ pt-BR:
133
133
  update_btn_submitting: :thredded.form.update_btn_submitting
134
134
  pending_moderation_notice: O envio da mensagem será publicada quando foi revisado por um moderador.
135
135
  quote_btn: Citar
136
+ spoiler_summary: Spoiler - clique para mostrar.
137
+ spoiler_summary_for_email: 'Spoiler - selecione o conteúdo abaixo para ver:'
136
138
  preferences:
137
139
  edit:
138
140
  page_title: :thredded.nav.settings
@@ -129,6 +129,8 @@ ru:
129
129
  update_btn_submitting: :thredded.form.update_btn_submitting
130
130
  pending_moderation_notice: Ваш пост будет опубликован, когда будет одобрен модератором.
131
131
  quote_btn: Цитировать
132
+ spoiler_summary: Спойлер - нажмите, чтобы показать.
133
+ spoiler_summary_for_email: 'Спойлер - выделите содержимое ниже, чтобы увидеть:'
132
134
  preferences:
133
135
  edit:
134
136
  page_title: :thredded.nav.settings
@@ -126,6 +126,8 @@ zh-CN:
126
126
  update_btn_submitting: :thredded.form.update_btn_submitting
127
127
  pending_moderation_notice: 你的回复将在审核之后公开发表
128
128
  quote_btn: 引用
129
+ spoiler_summary: 剧透 - 点击显示。
130
+ spoiler_summary_for_email: 扰流板 - 选择下面的内容来查看:
129
131
  preferences:
130
132
  edit:
131
133
  page_title: :thredded.nav.settings
@@ -25,18 +25,23 @@ class CreateThredded < Thredded::BaseMigration
25
25
 
26
26
  create_table :thredded_categories do |t|
27
27
  t.references :messageboard, null: false, index: false
28
- t.string :name, limit: 191, null: false
29
- t.string :description, limit: 255
28
+ t.text :name, null: false
29
+ t.text :description
30
30
  t.timestamps null: false
31
- t.string :slug, limit: 191, null: false
32
- t.index %i[messageboard_id slug], name: :index_thredded_categories_on_messageboard_id_and_slug, unique: true
31
+ t.text :slug, null: false
32
+ t.index %i[messageboard_id slug],
33
+ name: :index_thredded_categories_on_messageboard_id_and_slug,
34
+ unique: true,
35
+ length: { slug: max_key_length }
33
36
  t.index [:messageboard_id], name: :index_thredded_categories_on_messageboard_id
34
37
  end
35
- DbTextSearch::CaseInsensitive.add_index connection, :thredded_categories, :name, name: :thredded_categories_name_ci
38
+ DbTextSearch::CaseInsensitive.add_index connection, :thredded_categories, :name,
39
+ name: :thredded_categories_name_ci,
40
+ **(max_key_length ? { length: max_key_length } : {})
36
41
 
37
42
  create_table :thredded_messageboards do |t|
38
- t.string :name, limit: 191, null: false
39
- t.string :slug, limit: 191
43
+ t.text :name, null: false
44
+ t.text :slug
40
45
  t.text :description
41
46
  t.integer :topics_count, default: 0
42
47
  t.integer :posts_count, default: 0
@@ -46,14 +51,16 @@ class CreateThredded < Thredded::BaseMigration
46
51
  t.timestamps null: false
47
52
  t.boolean :locked, null: false, default: false
48
53
  t.index [:messageboard_group_id], name: :index_thredded_messageboards_on_messageboard_group_id
49
- t.index [:slug], name: :index_thredded_messageboards_on_slug
54
+ t.index [:slug],
55
+ name: :index_thredded_messageboards_on_slug,
56
+ unique: true,
57
+ length: { slug: max_key_length }
50
58
  end
51
59
 
52
60
  create_table :thredded_posts do |t|
53
61
  t.references :user, type: user_id_type, index: false
54
62
  t.text :content, limit: 65_535
55
- t.string :ip, limit: 255
56
- t.string :source, limit: 255, default: 'web'
63
+ t.string :source, limit: 191, default: 'web'
57
64
  t.references :postable, null: false, index: false
58
65
  t.references :messageboard, null: false, index: false
59
66
  t.integer :moderation_state, null: false
@@ -63,7 +70,7 @@ class CreateThredded < Thredded::BaseMigration
63
70
  name: :index_thredded_posts_for_display
64
71
  t.index [:messageboard_id], name: :index_thredded_posts_on_messageboard_id
65
72
  t.index [:postable_id], name: :index_thredded_posts_on_postable_id
66
- t.index [:postable_id], name: :index_thredded_posts_on_postable_id_and_postable_type
73
+ t.index %i[postable_id created_at], name: :index_thredded_posts_on_postable_id_and_created_at
67
74
  t.index [:user_id], name: :index_thredded_posts_on_user_id
68
75
  end
69
76
  DbTextSearch::FullText.add_index connection, :thredded_posts, :content, name: :thredded_posts_content_fts
@@ -72,21 +79,25 @@ class CreateThredded < Thredded::BaseMigration
72
79
  t.references :user, type: user_id_type, index: false
73
80
  t.text :content, limit: 65_535
74
81
  t.references :postable, null: false, index: false
75
- t.string :ip, limit: 255
76
82
  t.timestamps null: false
83
+ t.index %i[postable_id created_at], name: :index_thredded_private_posts_on_postable_id_and_created_at
77
84
  end
78
85
 
79
86
  create_table :thredded_private_topics do |t|
80
87
  t.references :user, type: user_id_type, index: false
81
88
  t.references :last_user, index: false
82
- t.string :title, limit: 255, null: false
83
- t.string :slug, limit: 191, null: false
89
+ t.text :title, null: false
90
+ t.text :slug, null: false
84
91
  t.integer :posts_count, default: 0
85
- t.string :hash_id, limit: 191, null: false
92
+ t.string :hash_id, limit: 20, null: false
86
93
  t.datetime :last_post_at
87
94
  t.timestamps null: false
95
+ t.index [:last_post_at], name: :index_thredded_private_topics_on_last_post_at
88
96
  t.index [:hash_id], name: :index_thredded_private_topics_on_hash_id
89
- t.index [:slug], name: :index_thredded_private_topics_on_slug
97
+ t.index [:slug],
98
+ name: :index_thredded_private_topics_on_slug,
99
+ unique: true,
100
+ length: { slug: max_key_length }
90
101
  end
91
102
 
92
103
  create_table :thredded_private_users do |t|
@@ -107,22 +118,25 @@ class CreateThredded < Thredded::BaseMigration
107
118
  create_table :thredded_topics do |t|
108
119
  t.references :user, type: user_id_type, index: false
109
120
  t.references :last_user, index: false
110
- t.string :title, limit: 255, null: false
111
- t.string :slug, limit: 191, null: false
121
+ t.text :title, null: false
122
+ t.text :slug, null: false
112
123
  t.references :messageboard, null: false, index: false
113
124
  t.integer :posts_count, default: 0, null: false
114
125
  t.boolean :sticky, default: false, null: false
115
126
  t.boolean :locked, default: false, null: false
116
- t.string :hash_id, limit: 191, null: false
117
- t.string :type, limit: 191
127
+ t.string :hash_id, limit: 20, null: false
118
128
  t.integer :moderation_state, null: false
119
129
  t.datetime :last_post_at
120
130
  t.timestamps null: false
121
131
  t.index %i[moderation_state sticky updated_at],
122
132
  order: { sticky: :desc, updated_at: :desc },
123
133
  name: :index_thredded_topics_for_display
134
+ t.index [:last_post_at], name: :index_thredded_topics_on_last_post_at
124
135
  t.index [:hash_id], name: :index_thredded_topics_on_hash_id
125
- t.index [:slug], name: :index_thredded_topics_on_slug, unique: true
136
+ t.index [:slug],
137
+ name: :index_thredded_topics_on_slug,
138
+ unique: true,
139
+ length: { slug: max_key_length }
126
140
  t.index [:messageboard_id], name: :index_thredded_topics_on_messageboard_id
127
141
  t.index [:user_id], name: :index_thredded_topics_on_user_id
128
142
  end
@@ -149,7 +163,8 @@ class CreateThredded < Thredded::BaseMigration
149
163
  t.references :thredded_messageboard, null: false, index: false
150
164
  t.datetime :last_seen_at, null: false
151
165
  t.index %i[thredded_messageboard_id thredded_user_detail_id],
152
- name: :index_thredded_messageboard_users_primary
166
+ name: :index_thredded_messageboard_users_primary,
167
+ unique: true
153
168
  t.index %i[thredded_messageboard_id last_seen_at],
154
169
  name: :index_thredded_messageboard_users_for_recently_active
155
170
  end
@@ -182,7 +197,6 @@ class CreateThredded < Thredded::BaseMigration
182
197
  create_table table_name do |t|
183
198
  t.references :user, type: user_id_type, null: false, index: false
184
199
  t.references :postable, null: false, index: false
185
- t.integer :page, default: 1, null: false
186
200
  t.timestamp :read_at, null: false
187
201
  t.index %i[user_id postable_id], name: :"#{table_name}_user_postable", unique: true
188
202
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thredded/base_migration'
4
+
5
+ class UpgradeThreddedV014ToV015 < Thredded::BaseMigration
6
+ def change # rubocop:disable Metrics/MethodLength
7
+ # Work around race condition on last_seen_at update
8
+ # https://github.com/thredded/thredded/pull/674
9
+ remove_index :thredded_messageboard_users,
10
+ name: :index_thredded_messageboard_users_primary
11
+ add_index :thredded_messageboard_users,
12
+ %i[thredded_messageboard_id thredded_user_detail_id],
13
+ name: :index_thredded_messageboard_users_primary,
14
+ unique: true
15
+
16
+ # Remove database string length limits.
17
+ # https://github.com/thredded/thredded/pull/703
18
+ remove_index :thredded_categories, name: :thredded_categories_name_ci
19
+ remove_string_limit :thredded_categories, :name
20
+ DbTextSearch::CaseInsensitive.add_index connection, :thredded_categories, :name,
21
+ name: :thredded_categories_name_ci,
22
+ **(max_key_length ? { length: max_key_length } : {})
23
+ remove_string_limit :thredded_categories, :description
24
+ remove_string_limit :thredded_categories, :slug,
25
+ indices: [
26
+ [%i[messageboard_id slug],
27
+ name: :index_thredded_categories_on_messageboard_id_and_slug,
28
+ unique: true,
29
+ length: { slug: max_key_length }]
30
+ ]
31
+
32
+ remove_string_limit :thredded_messageboards, :name
33
+ remove_string_limit :thredded_messageboards, :slug,
34
+ indices: [
35
+ [%i[slug],
36
+ name: :index_thredded_messageboards_on_slug,
37
+ unique: true,
38
+ length: { slug: max_key_length }]
39
+ ]
40
+
41
+ change_column :thredded_posts, :ip, :string, limit: 45
42
+ change_column :thredded_posts, :source, :string, limit: 191
43
+
44
+ remove_string_limit :thredded_private_topics, :title
45
+ remove_string_limit :thredded_private_topics, :slug,
46
+ indices: [
47
+ [%i[slug],
48
+ name: :index_thredded_private_topics_on_slug,
49
+ unique: true,
50
+ length: { slug: max_key_length }]
51
+ ]
52
+ change_column :thredded_private_topics, :hash_id, :string, limit: 20
53
+
54
+ remove_string_limit :thredded_topics, :title
55
+ remove_string_limit :thredded_topics, :slug,
56
+ indices: [
57
+ [%i[slug],
58
+ name: :index_thredded_topics_on_slug,
59
+ unique: true,
60
+ length: { slug: max_key_length }]
61
+ ]
62
+ change_column :thredded_topics, :hash_id, :string, limit: 20
63
+ remove_column :thredded_topics, :type
64
+
65
+ # Remove IP tracking column from posts
66
+ # https://github.com/thredded/thredded/pull/705
67
+ remove_column :thredded_posts, :ip
68
+ remove_column :thredded_private_posts, :ip
69
+
70
+ # Jump to first unread post
71
+ # https://github.com/thredded/thredded/pull/695
72
+ remove_column :thredded_user_topic_read_states, :page
73
+ remove_column :thredded_user_private_topic_read_states, :page
74
+ add_index :thredded_topics, [:last_post_at], name: :index_thredded_topics_on_last_post_at
75
+ add_index :thredded_private_topics, [:last_post_at], name: :index_thredded_private_topics_on_last_post_at
76
+ add_index :thredded_posts, %i[postable_id created_at], name: :index_thredded_posts_on_postable_id_and_created_at
77
+ add_index :thredded_private_posts, %i[postable_id created_at],
78
+ name: :index_thredded_private_posts_on_postable_id_and_created_at
79
+
80
+ # Cleanup
81
+ remove_index :thredded_posts, name: :index_thredded_posts_on_postable_id_and_created_at
82
+ end
83
+
84
+ private
85
+
86
+ def remove_string_limit(table, column, type: :text, indices: [])
87
+ indices.each { |(_, options)| remove_index table, name: options[:name] }
88
+ change_column table, column, type, limit: nil
89
+ indices.each { |args| add_index table, *args }
90
+ end
91
+ end
@@ -51,13 +51,10 @@ Thredded.admin_column = :admin
51
51
  # Whether posts and topics pending moderation are visible to regular users.
52
52
  Thredded.content_visible_while_pending_moderation = true
53
53
 
54
- # Whether users that are following a topic are listed on topic page.
55
- Thredded.show_topic_followers = false
56
-
57
54
  # This model can be customized further by overriding a handful of methods on the User model.
58
55
  # For more information, see app/models/thredded/user_extender.rb.
59
56
 
60
- # ==> Ordering configuration
57
+ # ==> UI configuration
61
58
 
62
59
  # How to calculate the position of messageboards in a list:
63
60
  # :position (default) set the position manually (new messageboards go to the bottom, by creation timestamp)
@@ -65,6 +62,18 @@ Thredded.show_topic_followers = false
65
62
  # :topics_count_desc most topics first
66
63
  Thredded.messageboards_order = :position
67
64
 
65
+ # Whether users that are following a topic are listed on the topic page.
66
+ Thredded.show_topic_followers = false
67
+
68
+ # Whether the list of users who are currently online is displayed.
69
+ Thredded.currently_online_enabled = true
70
+
71
+ # Whether private messaging functionality is enabled.
72
+ Thredded.private_messaging_enabled = true
73
+
74
+ # The layout for rendering Thredded views.
75
+ Thredded.layout = 'thredded/application'
76
+
68
77
  # ==> Email Configuration
69
78
  # Email "From:" field will use the following
70
79
  # Thredded.email_from = 'no-reply@example.com'
@@ -75,9 +84,9 @@ Thredded.messageboards_order = :position
75
84
  # The parent mailer for all Thredded mailers
76
85
  # Thredded.parent_mailer = 'ActionMailer::Base'
77
86
 
78
- # ==> View Configuration
79
- # Set the layout for rendering the thredded views.
80
- Thredded.layout = 'thredded/application'
87
+ # ==> Model configuration
88
+ # The range of valid topic title lengths. Default:
89
+ # Thredded.topic_title_length_range = (1..200)
81
90
 
82
91
  # ==> URLs
83
92
  # How Thredded generates URL slugs from text.
data/lib/thredded.rb CHANGED
@@ -16,11 +16,13 @@ require 'inline_svg'
16
16
  # Require these explictly to make sure they are not reloaded.
17
17
  # This allows for configuring them by accessing class methods in the initializer.
18
18
  require 'thredded/formatting_demo_content'
19
+ require 'thredded/html_pipeline/utils'
19
20
  require 'thredded/html_pipeline/at_mention_filter'
20
21
  require 'thredded/html_pipeline/autolink_filter'
21
22
  require 'thredded/html_pipeline/kramdown_filter'
22
23
  require 'thredded/html_pipeline/onebox_filter'
23
24
  require 'thredded/html_pipeline/wrap_iframes_filter'
25
+ require 'thredded/html_pipeline/spoiler_tag_filter'
24
26
 
25
27
  # Asset compilation
26
28
  require 'autoprefixer-rails'
@@ -39,152 +41,168 @@ require 'thredded/content_formatter'
39
41
  require 'thredded/email_transformer'
40
42
  require 'thredded/base_notifier'
41
43
 
44
+ require 'thredded/arel_compat'
42
45
  require 'thredded/collection_to_strings_with_cache_renderer'
43
46
 
44
47
  module Thredded
45
- mattr_accessor \
46
- :autocomplete_min_length,
47
- :active_user_threshold,
48
- :avatar_url,
49
- :email_from,
50
- :email_outgoing_prefix,
51
- :layout,
52
- :messageboards_order,
53
- :routes_id_constraint,
54
- :user_display_name_method,
55
- :user_name_column,
56
- :user_path
57
-
58
- # @return [Symbol] The name of the method used by Thredded controllers and views to fetch the currently signed-in user
59
- mattr_accessor :current_user_method
60
-
61
- # @return [Symbol] The name of the moderator flag column on the users table for the default permissions model
62
- mattr_accessor :moderator_column
63
-
64
- # @return [Symbol] The name of the admin flag column on the users table for the default permissions model
65
- mattr_accessor :admin_column
66
-
67
- # @return [Boolean] Whether posts that are pending moderation are visible to regular users.
68
- mattr_accessor :content_visible_while_pending_moderation
69
-
70
- # @return [Array] The notifiers, by default just the EmailNotifier
71
- mattr_accessor :notifiers
72
-
73
- # @return [Boolean] Whether users that are following a topic are listed on topic page.
74
- mattr_accessor :show_topic_followers
75
-
76
- # @return [Symbol] The name of the method used by Thredded to display users
77
- mattr_accessor :user_display_name_method
78
-
79
- # @return [String] The name of the parent mailer class for Thredded mailers.
80
- mattr_accessor :parent_mailer
81
- self.parent_mailer = 'ActionMailer::Base'
48
+ class << self
49
+ attr_accessor \
50
+ :autocomplete_min_length,
51
+ :active_user_threshold,
52
+ :avatar_url,
53
+ :email_from,
54
+ :email_outgoing_prefix,
55
+ :layout,
56
+ :messageboards_order,
57
+ :routes_id_constraint,
58
+ :user_name_column
82
59
 
83
- # @return [Proc] The proc that Thredded uses to generate URL slugs from text.
84
- mattr_accessor :slugifier
85
- self.slugifier = ->(input) { input.parameterize }
60
+ # A lambda that generates a URL path to the user page for the given user.
61
+ attr_writer :user_path
86
62
 
87
- # @return [Boolean] Whether the user should get subscribed to a new topic they've created.
88
- mattr_accessor :auto_follow_when_creating_topic
89
- self.auto_follow_when_creating_topic = true
63
+ # @return [Symbol] The name of the method used by Thredded controllers and views to get the currently signed-in user
64
+ attr_accessor :current_user_method
90
65
 
91
- # @return [Boolean] Whether the user should get subscribed to a topic after posting in it.
92
- mattr_accessor :auto_follow_when_posting_in_topic
93
- self.auto_follow_when_posting_in_topic = true
66
+ # @return [Symbol] The name of the moderator flag column on the users table for the default permissions model
67
+ attr_accessor :moderator_column
94
68
 
95
- # @return [String] The name of the user class
96
- mattr_reader :user_class_name
69
+ # @return [Symbol] The name of the admin flag column on the users table for the default permissions model
70
+ attr_accessor :admin_column
97
71
 
98
- self.active_user_threshold = 5.minutes
99
- self.admin_column = :admin
100
- self.avatar_url = ->(user) { Gravatar.src(user.email, 156, 'mm') }
101
- self.layout = 'thredded/application'
102
- self.moderator_column = :admin
103
- self.user_name_column = :name
104
- self.content_visible_while_pending_moderation = true
105
- self.show_topic_followers = false
106
- self.messageboards_order = :position
107
- self.autocomplete_min_length = 2
108
- self.routes_id_constraint = /[1-9]\d*/
72
+ # @return [Boolean] Whether posts that are pending moderation are visible to regular users.
73
+ attr_accessor :content_visible_while_pending_moderation
109
74
 
110
- # @return [Thredded::AllViewHooks] View hooks configuration.
111
- def self.view_hooks
112
- instance = Thredded::AllViewHooks.instance
113
- unless instance
114
- fail '`Thredded.view_hooks` must be configured in a `Rails.application.config.to_prepare` block!'
75
+ # @return [Boolean] Whether users that are following a topic are listed on the topic page.
76
+ attr_accessor :show_topic_followers
77
+
78
+ # @return [Symbol] The name of the method used by Thredded to display users
79
+ attr_writer :user_display_name_method
80
+
81
+ # @return [String] The name of the parent mailer class for Thredded mailers.
82
+ attr_accessor :parent_mailer
83
+
84
+ # @return [Proc] The proc that Thredded uses to generate URL slugs from text.
85
+ attr_accessor :slugifier
86
+
87
+ # @return [Boolean] Whether the user should get subscribed to a new topic they've created.
88
+ attr_accessor :auto_follow_when_creating_topic
89
+
90
+ # @return [Boolean] Whether the user should get subscribed to a topic after posting in it.
91
+ attr_accessor :auto_follow_when_posting_in_topic
92
+
93
+ # @return [String] The name of the user class
94
+ attr_reader :user_class_name
95
+
96
+ # @return [Range<Integer>] The range of valid topic title lengths.
97
+ attr_accessor :topic_title_length_range
98
+
99
+ # @return [Boolean] Whether the list of users who are currently online is displayed.
100
+ attr_accessor :currently_online_enabled
101
+
102
+ # @return [Boolean] Whether the private messaging functionality is enabled.
103
+ attr_accessor :private_messaging_enabled
104
+
105
+ # @return [Thredded::AllViewHooks] View hooks configuration.
106
+ def view_hooks
107
+ instance = Thredded::AllViewHooks.instance
108
+ unless instance
109
+ fail '`Thredded.view_hooks` must be configured in a `Rails.application.config.to_prepare` block!'
110
+ end
111
+ instance
115
112
  end
116
- instance
117
- end
118
113
 
119
- def self.notifiers
120
- @@notifiers ||= [Thredded::EmailNotifier.new] # rubocop:disable Style/ClassVars
121
- end
114
+ # @return [Array] The notifiers, by default just the EmailNotifier
115
+ def notifiers
116
+ @notifiers ||= [Thredded::EmailNotifier.new]
117
+ end
122
118
 
123
- def self.notifiers=(notifiers)
124
- notifiers.each { |notifier| BaseNotifier.validate_notifier(notifier) }
125
- @@notifiers = notifiers # rubocop:disable Style/ClassVars
126
- end
119
+ # @param [Array] notifiers
120
+ def notifiers=(notifiers)
121
+ notifiers.each { |notifier| Thredded::BaseNotifier.validate_notifier(notifier) }
122
+ @notifiers = notifiers
123
+ end
127
124
 
128
- def self.user_display_name_method
129
- @@user_display_name_method || user_name_column
130
- end
125
+ # @return [Symbol] The name of the method used by Thredded to display users
126
+ def user_display_name_method
127
+ @user_display_name_method || user_name_column
128
+ end
131
129
 
132
- # @param value [:position, :topics_count_desc, :last_post_at_desc]
133
- def self.messageboards_order=(value)
134
- case value
135
- when :position, :topics_count_desc, :last_post_at_desc
136
- @@messageboards_order = value # rubocop:disable Style/ClassVars
137
- else
138
- fail ArgumentError, "Unexpected value for Thredded.messageboards_order: #{value}"
130
+ # @param value [:position, :topics_count_desc, :last_post_at_desc]
131
+ def messageboards_order=(value)
132
+ case value
133
+ when :position, :topics_count_desc, :last_post_at_desc
134
+ @messageboards_order = value
135
+ else
136
+ fail ArgumentError, "Unexpected value for Thredded.messageboards_order: #{value}"
137
+ end
139
138
  end
140
- end
141
139
 
142
- # @param user_class_name [String]
143
- def self.user_class=(user_class_name)
144
- unless user_class_name.is_a?(String)
145
- fail "Thredded.user_class must be set to a String, got #{user_class_name.inspect}"
140
+ # @param user_class_name [String]
141
+ def user_class=(user_class_name)
142
+ unless user_class_name.is_a?(String)
143
+ fail "Thredded.user_class must be set to a String, got #{user_class_name.inspect}"
144
+ end
145
+ @user_class_name = user_class_name
146
146
  end
147
- @@user_class_name = user_class_name # rubocop:disable Style/ClassVars
148
- end
149
147
 
150
- # @return [Class<Thredded::UserExtender>] the user class from the host application.
151
- def self.user_class
152
- # This is nil before the initializer is installed.
153
- return nil if @@user_class_name.nil?
154
- @@user_class_name.constantize
155
- end
148
+ # @return [Class<Thredded::UserExtender>] the user class from the host application.
149
+ def user_class
150
+ # This is nil before the initializer is installed.
151
+ return nil if @user_class_name.nil?
152
+ @user_class_name.constantize
153
+ end
156
154
 
157
- # @param view_context [Object] context to execute the lambda in.
158
- # @param user [Thredded.user_class]
159
- # @return [String] path to the user evaluated in the specified context.
160
- def self.user_path(view_context, user)
161
- view_context.instance_exec(user, &@@user_path)
162
- end
155
+ # @param view_context [Object] context to execute the lambda in.
156
+ # @param user [Thredded.user_class]
157
+ # @return [String] path to the user evaluated in the specified context.
158
+ def user_path(view_context, user)
159
+ view_context.instance_exec(user, &@user_path)
160
+ end
163
161
 
164
- # Whether the layout is a thredded layout as opposed to the application layout.
165
- def self.standalone_layout?
166
- layout.is_a?(String) && layout.start_with?('thredded/')
167
- end
162
+ # Whether the layout is a thredded layout as opposed to the application layout.
163
+ def standalone_layout?
164
+ layout.is_a?(String) && layout.start_with?('thredded/')
165
+ end
168
166
 
169
- # Returns a view for the given posts' scope, applying read permission filters to the scope.
170
- # Can be used in main_app, e.g. for showing the recent user posts on the profile page.
171
- #
172
- # @param scope [ActiveRecord::Relation<Thredded::Post>] the posts scope for which to return the view.
173
- # @param current_user [Thredded.user_class, nil] the user viewing the posts.
174
- # @return [PostsPageView]
175
- def self.posts_page_view(scope:, current_user:)
176
- current_user ||= Thredded::NullUser.new
177
- PostsPageView.new(
178
- current_user,
179
- Pundit.policy_scope!(current_user, scope)
180
- .where(messageboard_id: Pundit.policy_scope!(current_user, Thredded::Messageboard.all).pluck(:id))
181
- .includes(:postable)
182
- )
183
- end
167
+ # Returns a view for the given posts' scope, applying read permission filters to the scope.
168
+ # Can be used in main_app, e.g. for showing the recent user posts on the profile page.
169
+ #
170
+ # @param scope [ActiveRecord::Relation<Thredded::Post>] the posts scope for which to return the view.
171
+ # @param current_user [Thredded.user_class, nil] the user viewing the posts.
172
+ # @return [PostsPageView]
173
+ def posts_page_view(scope:, current_user:)
174
+ current_user ||= Thredded::NullUser.new
175
+ Thredded::PostsPageView.new(
176
+ current_user,
177
+ Pundit.policy_scope!(current_user, scope)
178
+ .where(messageboard_id: Pundit.policy_scope!(current_user, Thredded::Messageboard.all).pluck(:id))
179
+ .includes(:postable)
180
+ )
181
+ end
184
182
 
185
- # @api private
186
- def self.rails_gte_51?
187
- @rails_gte_51 = (Rails.gem_version >= Gem::Version.new('5.1.0')) if @rails_gte_51.nil?
188
- @rails_gte_51
183
+ # @api private
184
+ def rails_gte_51?
185
+ @rails_gte_51 = (Rails.gem_version >= Gem::Version.new('5.1.0')) if @rails_gte_51.nil?
186
+ @rails_gte_51
187
+ end
189
188
  end
189
+
190
+ self.active_user_threshold = 5.minutes
191
+ self.admin_column = :admin
192
+ self.auto_follow_when_creating_topic = true
193
+ self.auto_follow_when_posting_in_topic = true
194
+ self.autocomplete_min_length = 2
195
+ self.avatar_url = ->(user) { Gravatar.src(user.email, 156, 'mm') }
196
+ self.content_visible_while_pending_moderation = true
197
+ self.layout = 'thredded/application'
198
+ self.messageboards_order = :position
199
+ self.moderator_column = :admin
200
+ self.parent_mailer = 'ActionMailer::Base'
201
+ self.routes_id_constraint = /[1-9]\d*/
202
+ self.show_topic_followers = false
203
+ self.slugifier = ->(input) { input.parameterize }
204
+ self.topic_title_length_range = (1..200)
205
+ self.user_name_column = :name
206
+ self.private_messaging_enabled = true
207
+ self.currently_online_enabled = true
190
208
  end