thredded 0.16.14 → 1.0.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -7
  3. data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -1
  4. data/app/controllers/concerns/thredded/render_preview.rb +6 -0
  5. data/app/controllers/thredded/application_controller.rb +5 -10
  6. data/app/controllers/thredded/topics_controller.rb +0 -6
  7. data/app/helpers/thredded/application_helper.rb +1 -1
  8. data/app/helpers/thredded/icon_helper.rb +1 -1
  9. data/app/helpers/thredded/urls_helper.rb +1 -1
  10. data/app/models/concerns/thredded/post_common.rb +5 -4
  11. data/app/models/concerns/thredded/topic_common.rb +1 -1
  12. data/app/models/concerns/thredded/user_topic_read_state_common.rb +7 -8
  13. data/app/models/thredded/messageboard.rb +5 -4
  14. data/app/models/thredded/notifications_for_followed_topics.rb +1 -1
  15. data/app/models/thredded/post.rb +3 -2
  16. data/app/models/thredded/post_moderation_record.rb +8 -9
  17. data/app/models/thredded/private_post.rb +2 -2
  18. data/app/models/thredded/private_topic.rb +4 -2
  19. data/app/models/thredded/topic.rb +2 -2
  20. data/app/models/thredded/user_detail.rb +1 -1
  21. data/app/models/thredded/user_extender.rb +1 -1
  22. data/app/models/thredded/user_private_topic_read_state.rb +3 -0
  23. data/app/models/thredded/user_topic_read_state.rb +3 -0
  24. data/app/view_models/thredded/post_view.rb +1 -0
  25. data/app/view_models/thredded/posts_page_view.rb +10 -9
  26. data/app/view_models/thredded/private_topics_page_view.rb +10 -6
  27. data/app/view_models/thredded/topic_posts_page_view.rb +16 -3
  28. data/app/view_models/thredded/topics_page_view.rb +9 -6
  29. data/app/views/layouts/thredded/application.html.erb +2 -2
  30. data/app/views/thredded/messageboards/_form.html.erb +3 -3
  31. data/app/views/thredded/posts_common/_actions.html.erb +1 -1
  32. data/app/views/thredded/posts_common/_content.html.erb +1 -1
  33. data/app/views/thredded/posts_common/actions/_quote.html.erb +1 -1
  34. data/app/views/thredded/shared/_page.html.erb +1 -1
  35. data/app/views/thredded/shared/nav/_moderation.html.erb +3 -3
  36. data/app/views/thredded/shared/nav/_notification_preferences.html.erb +1 -1
  37. data/app/views/thredded/shared/nav/_private_topics.html.erb +3 -3
  38. data/app/views/thredded/shared/nav/_standalone.html.erb +2 -2
  39. data/app/views/thredded/topics/_header.html.erb +1 -1
  40. data/bin/create_migration_fixture +23 -0
  41. data/config/i18n-tasks.yml +5 -0
  42. data/config/locales/de.yml +2 -0
  43. data/config/locales/en.yml +3 -1
  44. data/config/locales/es.yml +2 -0
  45. data/config/locales/fr.yml +2 -0
  46. data/config/locales/it.yml +4 -2
  47. data/config/locales/pl.yml +2 -0
  48. data/config/locales/pt-BR.yml +2 -0
  49. data/config/locales/ru.yml +2 -0
  50. data/config/locales/zh-CN.yml +2 -0
  51. data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +1 -1
  52. data/lib/generators/thredded/install/templates/initializer.rb +2 -2
  53. data/lib/thredded/arel_compat.rb +0 -32
  54. data/lib/thredded/base_migration.rb +1 -1
  55. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +91 -28
  56. data/lib/thredded/compat.rb +35 -0
  57. data/lib/thredded/database_seeder.rb +11 -4
  58. data/lib/thredded/db_tools.rb +2 -4
  59. data/lib/thredded/email_transformer.rb +2 -2
  60. data/lib/thredded/html_pipeline/onebox_filter.rb +1 -4
  61. data/lib/thredded/version.rb +1 -1
  62. data/lib/thredded.rb +3 -23
  63. metadata +43 -31
  64. data/bin/rubocop +0 -18
@@ -1,16 +1,16 @@
1
1
  <%= form_for messageboard, html: { class: 'thredded--form' } do |f| %>
2
2
  <ul class="thredded--form-list">
3
3
  <li>
4
- <%= f.label t('thredded.messageboard.form.title_label') %>
4
+ <%= f.label :name, t('thredded.messageboard.form.title_label') %>
5
5
  <%= f.text_field :name, required: true %>
6
6
  <%= render 'thredded/shared/field_errors', messages: f.object.errors[:name] %>
7
7
  </li>
8
8
  <li>
9
- <%= f.label t('thredded.messageboard.form.description_label') %>
9
+ <%= f.label :description, t('thredded.messageboard.form.description_label') %>
10
10
  <%= f.text_field :description %>
11
11
  </li>
12
12
  <li>
13
- <%= f.label t('thredded.messageboard.form.messageboard_group_id_label') %>
13
+ <%= f.label :messageboard_group_id, t('thredded.messageboard.form.messageboard_group_id_label') %>
14
14
  <%= f.collection_select :messageboard_group_id, Thredded::MessageboardGroup.all, :id, :name,
15
15
  include_blank: t('thredded.messageboard.form.no_group') %>
16
16
  </li>
@@ -20,7 +20,7 @@
20
20
 
21
21
  <%- if actions_html.present? %>
22
22
  <div class='thredded--post--dropdown'>
23
- <%= inline_svg 'thredded/three-dot-menu.svg', class: 'thredded--post--dropdown--toggle' %>
23
+ <%= inline_svg_tag 'thredded/three-dot-menu.svg', class: 'thredded--post--dropdown--toggle' %>
24
24
  <div class='thredded--post--dropdown--actions'>
25
25
  <%= actions_html %>
26
26
  </div>
@@ -1,3 +1,3 @@
1
1
  <div class="thredded--post--content">
2
- <%= post.filtered_content(self, local_assigns[:options] || {}) %>
2
+ <%= post.filtered_content(self, **(local_assigns[:options] || {})) %>
3
3
  </div>
@@ -1,4 +1,4 @@
1
- <%= link_to t('thredded.posts.quote_btn'), url_for(post.quote_url_params),
1
+ <%= link_to t('thredded.posts.quote_btn'), post.quote_url_params,
2
2
  'data-thredded-quote-post' => post.quote_path,
3
3
  class: 'thredded--post--quote thredded--post--dropdown--actions--item',
4
4
  rel: 'nofollow' %>
@@ -8,7 +8,7 @@
8
8
  <%# If thredded JS is loaded via an [async] script, the JS may
9
9
  run before or after DOMContentLoaded. Expose a flag to Thredded
10
10
  so it can initialize correctly. %>
11
- <%= javascript_tag 'data-turbolinks-eval': 'false', **(Thredded.rails_supports_csp_nonce? ? {nonce: true} : {}) do %>
11
+ <%= javascript_tag 'data-turbolinks-eval': 'false', nonce: true do %>
12
12
  document.addEventListener('DOMContentLoaded', function() {
13
13
  (window.Thredded = window.Thredded || {}).DOMContentLoadedFired = true;
14
14
  });
@@ -2,9 +2,9 @@
2
2
  <% current = current_page_moderation? %>
3
3
  <li class="thredded--user-navigation--item thredded--user-navigation--moderation<%= ' thredded--is-current' if current %>">
4
4
  <%= link_to current ? nav_back_path : pending_moderation_path, rel: 'nofollow' do %>
5
- <%= inline_svg 'thredded/moderation.svg',
6
- class: 'thredded--icon',
7
- title: t('thredded.nav.moderation') %>
5
+ <%= inline_svg_tag 'thredded/moderation.svg',
6
+ class: 'thredded--icon',
7
+ title: t('thredded.nav.moderation') %>
8
8
  <span class="thredded--nav-text"><%= t 'thredded.nav.moderation' %></span>
9
9
  <% if posts_pending_moderation_count > 0 %>
10
10
  <span class="thredded--user-navigation--moderation--pending-count"><%= posts_pending_moderation_count %></span>
@@ -1,7 +1,7 @@
1
1
  <% current = current_page_preferences? %>
2
2
  <li class="thredded--user-navigation--settings thredded--user-navigation--item<%= ' thredded--is-current' if current %>">
3
3
  <%= link_to current ? nav_back_path(messageboard) : edit_preferences_path(messageboard), rel: 'nofollow' do %>
4
- <%= inline_svg 'thredded/settings.svg', class: 'thredded--icon', title: t('thredded.nav.settings') %>
4
+ <%= inline_svg_tag 'thredded/settings.svg', class: 'thredded--icon', title: t('thredded.nav.settings') %>
5
5
  <span class="thredded--nav-text"><%= t('thredded.nav.settings') %></span>
6
6
  <% end %>
7
7
  </li>
@@ -1,9 +1,9 @@
1
1
  <% current = current_page_private_topics? %>
2
2
  <li class="thredded--user-navigation--item thredded--user-navigation--private-topics<%= ' thredded--is-current' if current %>">
3
3
  <%= link_to current ? nav_back_path : private_topics_path, rel: 'nofollow' do %>
4
- <%= inline_svg 'thredded/private-messages.svg',
5
- class: 'thredded--icon',
6
- title: t('thredded.nav.private_topics') %>
4
+ <%= inline_svg_tag 'thredded/private-messages.svg',
5
+ class: 'thredded--icon',
6
+ title: t('thredded.nav.private_topics') %>
7
7
  <span class="thredded--nav-text"><%= t('thredded.nav.private_topics') %></span>
8
8
  <% if unread_private_topics_count > 0 -%>
9
9
  <span class="thredded--user-navigation--private-topics--unread"><%= unread_private_topics_count %></span>
@@ -2,11 +2,11 @@
2
2
  <% resource_name = Thredded.user_class_name.demodulize.underscore %>
3
3
  <% if thredded_signed_in? %>
4
4
  <%= link_to main_app.send(:"destroy_#{resource_name}_session_path"), method: :delete do %>
5
- Sign Out
5
+ t('thredded.nav.sign_out')
6
6
  <% end %>
7
7
  <% else %>
8
8
  <%= link_to main_app.send(:"new_#{resource_name}_session_path") do %>
9
- Sign In / Register
9
+ t('thredded.nav.sign_in')
10
10
  <% end %>
11
11
  <% end %>
12
12
  </li>
@@ -20,7 +20,7 @@
20
20
  </span>
21
21
  <%= button_to topic.unfollow_path, form: {class: 'thredded--topic-header--follow-info--unfollow'} do %>
22
22
  <%= t('thredded.topics.unfollow') %>
23
- <%= inline_svg 'thredded/follow.svg', class: 'thredded--topic-header--follow-icon' %>
23
+ <%= inline_svg_tag 'thredded/follow.svg', class: 'thredded--topic-header--follow-icon' %>
24
24
  <% end %>
25
25
  </div>
26
26
  <% else %>
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # edit constants in lib/thredded/db_tools.rb
4
+ # git checkout v0.x.0 -b create-0.x-migration-fixture
5
+ # bundle
6
+
7
+ DIR=$(pwd)
8
+
9
+ cd spec/dummy && DB=sqlite3 rails db:environment:set RAILS_ENV=development
10
+ cd $DIR
11
+ DB=sqlite3 rake db:drop db:create db:migrate db:miniseed_dump
12
+
13
+ cd spec/dummy && DB=postgresql rails db:environment:set RAILS_ENV=development
14
+ cd $DIR
15
+ DB=postgresql rake db:drop db:create db:migrate db:miniseed_dump
16
+
17
+ cd spec/dummy && DB=mysql2 rails db:environment:set RAILS_ENV=development
18
+ cd $DIR
19
+ DB=mysql2 rake db:drop db:create db:migrate db:miniseed_dump
20
+
21
+ git add `pwd`/spec/migration/*.dump
22
+
23
+ git commit -em "add sample data for 0.8 base for migration spec"
@@ -15,3 +15,8 @@ data:
15
15
  yaml:
16
16
  write:
17
17
  line_width: 110
18
+
19
+ ## Exclude these keys from the `i18n-tasks check-consistent-interpolations` report:
20
+ ignore_inconsistent_interpolations:
21
+ - 'thredded.users.posts_count.one'
22
+ - 'thredded.users.started_topics_count.one'
@@ -128,6 +128,8 @@ de:
128
128
  moderation_users: Nutzer
129
129
  private_topics: Private Unterhaltungen
130
130
  settings: Benachrichtigungseinstellungen
131
+ sign_in: Anmelden
132
+ sign_out: Ausloggen
131
133
  unread_topics: Ungelesen
132
134
  null_user_name: Gelöschte Nutzer
133
135
  posts:
@@ -127,6 +127,8 @@ en:
127
127
  moderation_users: Users
128
128
  private_topics: Private Messages
129
129
  settings: Notification Settings
130
+ sign_in: Sign In / Register
131
+ sign_out: Sign Out
130
132
  unread_topics: Unread
131
133
  null_user_name: Deleted user
132
134
  posts:
@@ -262,7 +264,7 @@ en:
262
264
  last_active_html: Last active %{time_ago}
263
265
  posted_in_topic_html: Posted in %{topic_link}
264
266
  posts_count:
265
- one: Posted %{count} time
267
+ one: Posted once
266
268
  other: Posted %{count} times
267
269
  recent_activity: :thredded.recent_activity
268
270
  send_private_message: Send private message
@@ -129,6 +129,8 @@ es:
129
129
  moderation_users: Usuarios
130
130
  private_topics: Mensajes Privados
131
131
  settings: Ajuste de Notificaciones
132
+ sign_in: Registrarse
133
+ sign_out: Desconectar
132
134
  unread_topics: No leído
133
135
  null_user_name: Usuario eliminado
134
136
  posts:
@@ -127,6 +127,8 @@ fr:
127
127
  moderation_users: Utilisateurs
128
128
  private_topics: Messages privés
129
129
  settings: Paramètres de notifications
130
+ sign_in: Se connecter
131
+ sign_out: Déconnexion
130
132
  unread_topics: Non lu
131
133
  null_user_name: Utilisateur effacé
132
134
  posts:
@@ -127,6 +127,8 @@ it:
127
127
  moderation_users: Utenti
128
128
  private_topics: Messaggi Privati
129
129
  settings: Impostazioni Notifiche
130
+ sign_in: Entra
131
+ sign_out: Esci
130
132
  unread_topics: Non letto
131
133
  null_user_name: Utente cancellato
132
134
  posts:
@@ -266,13 +268,13 @@ it:
266
268
  last_active_html: Ultima attività %{time_ago}
267
269
  posted_in_topic_html: Ha scritto in %{topic_link}
268
270
  posts_count:
269
- one: Ha commentato %{count} volta
271
+ one: Ha commentato una volta
270
272
  other: Ha commentato %{count} volte
271
273
  recent_activity: :thredded.recent_activity
272
274
  send_private_message: Invia un messaggio privato
273
275
  started_topic_html: Ha iniziato %{topic_link}
274
276
  started_topics_count:
275
- one: Ha iniziato %{count} discussione
277
+ one: Ha iniziato una discussione
276
278
  other: Ha iniziato %{count} discussioni
277
279
  user_posted_in_topic_html: "%{user_link} ha commentato in %{topic_link}"
278
280
  user_since_html: Utente da %{time_ago}
@@ -127,6 +127,8 @@ pl:
127
127
  moderation_users: Użytkownicy
128
128
  private_topics: Prywatne wiadomości
129
129
  settings: Ustawienia powiadomień
130
+ sign_in: Zaloguj
131
+ sign_out: Wyloguj się
130
132
  unread_topics: Nieprzeczytane
131
133
  null_user_name: Usunięci użytkownicy
132
134
  posts:
@@ -129,6 +129,8 @@ pt-BR:
129
129
  moderation_users: Usuários
130
130
  private_topics: Mensagens Privadas
131
131
  settings: Configurações de Notificação
132
+ sign_in: Entrar / Cadastrar
133
+ sign_out: Sair
132
134
  unread_topics: Não lida
133
135
  null_user_name: Usuário deletado
134
136
  posts:
@@ -125,6 +125,8 @@ ru:
125
125
  moderation_users: Пользователи
126
126
  private_topics: Личное
127
127
  settings: Настройки
128
+ sign_in: Войти / Зарегистрироваться
129
+ sign_out: Выход
128
130
  unread_topics: Непрочитанные
129
131
  null_user_name: Пользователь удален
130
132
  posts:
@@ -120,6 +120,8 @@ zh-CN:
120
120
  moderation_users: 用户
121
121
  private_topics: 私人对话
122
122
  settings: 通知设置
123
+ sign_in: 登录/注册
124
+ sign_out: 登出
123
125
  unread_topics: 未读
124
126
  null_user_name: 删除用户
125
127
  posts:
@@ -86,6 +86,6 @@ class UpgradeThreddedV014ToV015 < Thredded::BaseMigration
86
86
  def remove_string_limit(table, column, type: :text, indices: [])
87
87
  indices.each { |(_, options)| remove_index table, name: options[:name] }
88
88
  change_column table, column, type, limit: nil
89
- indices.each { |args| add_index table, *args }
89
+ indices.each { |(columns, options)| add_index table, columns, **options }
90
90
  end
91
91
  end
@@ -29,8 +29,8 @@ Thredded.user_path = ->(user) {
29
29
  # This method is used by Thredded controllers and views to fetch the currently signed-in user
30
30
  Thredded.current_user_method = :"current_#{Thredded.user_class_name.demodulize.underscore}"
31
31
 
32
- # User avatar URL. rb-gravatar gem is used by default:
33
- Thredded.avatar_url = ->(user) { Gravatar.src(user.email, 156, 'mm') }
32
+ # User avatar URL. rails_gravatar gem is used by default:
33
+ Thredded.avatar_url = ->(user) { RailsGravatar.src(user.email, 156, 'mm') }
34
34
 
35
35
  # ==> Permissions Configuration
36
36
  # By default, thredded uses a simple permission model, where all the users can post to all message boards,
@@ -1,27 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- unless Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2 || Rails::VERSION::MAJOR > 5
4
- require 'thredded/rails_lt_5_2_arel_case_node.rb'
5
- end
6
-
7
- if Rails::VERSION::MAJOR == 4
8
- # Make `pluck` compatible with Arel.
9
- require 'active_record/relation'
10
- ActiveRecord::Relation.prepend(Module.new do
11
- def pluck(*column_names)
12
- super(*column_names.map do |n|
13
- if n.is_a?(Arel::Node)
14
- Arel.sql(n.to_sql)
15
- elsif n.is_a?(Arel::Attributes::Attribute)
16
- n.name
17
- else
18
- n
19
- end
20
- end)
21
- end
22
- end)
23
- end
24
-
25
3
  module Thredded
26
4
  module ArelCompat
27
5
  module_function
@@ -37,15 +15,5 @@ module Thredded
37
15
  Arel::Nodes::Division.new(a, b)
38
16
  end
39
17
  end
40
-
41
- if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2 || Rails::VERSION::MAJOR > 5
42
- def true_value(_engine)
43
- true
44
- end
45
- else
46
- def true_value(engine)
47
- engine.connection.adapter_name =~ /sqlite|mysql|mariadb/i ? 1 : true
48
- end
49
- end
50
18
  end
51
19
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thredded
4
- class BaseMigration < (Thredded.rails_gte_51? ? ActiveRecord::Migration[5.1] : ActiveRecord::Migration)
4
+ class BaseMigration < ActiveRecord::Migration[5.1]
5
5
  protected
6
6
 
7
7
  def user_id_type
@@ -22,17 +22,18 @@ module Thredded
22
22
  def render_collection_to_strings_with_cache( # rubocop:disable Metrics/ParameterLists
23
23
  view_context, collection:, partial:, expires_in:, render_threads: self.class.render_threads, locals: {}, **opts
24
24
  )
25
- template = @lookup_context.find_template(partial, [], true, locals, {})
25
+ template = @lookup_context.find_template(partial, [], true, locals.keys, {})
26
26
  collection = collection.to_a
27
- instrument(:collection, identifier: template.identifier, count: collection.size) do |instrumentation_payload|
27
+ ActiveSupport::Notifications.instrument(:collection,
28
+ identifier: template.identifier,
29
+ count: collection.size) do |instrumentation_payload|
28
30
  return [] if collection.blank?
29
- keyed_collection = collection.each_with_object({}) do |item, hash|
30
- key = ActiveSupport::Cache.expand_cache_key(
31
- view_context.cache_fragment_name(item, virtual_path: template.virtual_path), :views
32
- )
33
- # #read_multi & #write may require key mutability, Dalli 2.6.0.
34
- hash[key.frozen? ? key.dup : key] = item
35
- end
31
+
32
+ # Result is a hash with the key represents the
33
+ # key used for cache lookup and the value is the item
34
+ # on which the partial is being rendered
35
+ keyed_collection, ordered_keys = collection_by_cache_keys(collection, view_context, template)
36
+
36
37
  cache = collection_cache
37
38
  cached_partials = cache.read_multi(*keyed_collection.keys)
38
39
  instrumentation_payload[:cache_hits] = cached_partials.size if instrumentation_payload
@@ -44,9 +45,9 @@ module Thredded
44
45
  partial: partial, locals: locals, **opts
45
46
  ).each
46
47
 
47
- keyed_collection.map do |cache_key, item|
48
- [item, cached_partials[cache_key] || rendered_partials.next.tap do |rendered|
49
- cache.write(cache_key, rendered, expires_in: expires_in)
48
+ ordered_keys.map do |cache_key|
49
+ [keyed_collection[cache_key], cached_partials[cache_key] || rendered_partials.next.tap do |rendered|
50
+ cached_partials[cache_key] = cache.write(cache_key, rendered, expires_in: expires_in)
50
51
  end]
51
52
  end
52
53
  end
@@ -54,16 +55,24 @@ module Thredded
54
55
 
55
56
  private
56
57
 
57
- def collection_cache
58
- if ActionView::PartialRenderer.respond_to?(:collection_cache)
59
- # Rails 5.0+
60
- ActionView::PartialRenderer.collection_cache
61
- else
62
- # Rails 4.2.x
63
- Rails.application.config.action_controller.cache_store
58
+ def collection_by_cache_keys(collection, view, template)
59
+ digest_path = digest_path_from_template(view, template)
60
+
61
+ collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
62
+ key = expanded_cache_key(item, view, template, digest_path)
63
+ ordered_keys << key
64
+ hash[key] = item
64
65
  end
65
66
  end
66
67
 
68
+ def expanded_cache_key(key, view, template, digest_path)
69
+ key = combined_fragment_cache_key(
70
+ view,
71
+ cache_fragment_name(view, key, virtual_path: template.virtual_path, digest_path: digest_path)
72
+ )
73
+ key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
74
+ end
75
+
67
76
  # @return [Array<String>]
68
77
  def render_partials(view_context, collection:, render_threads:, **opts)
69
78
  return [] if collection.empty?
@@ -72,25 +81,79 @@ module Thredded
72
81
  render_partials_serial(view_context, collection, opts)
73
82
  else
74
83
  collection.each_slice(collection.size / num_threads).map do |slice|
75
- Thread.start { render_partials_serial(view_context.dup, slice, opts) }
84
+ Thread.start do
85
+ # `ActionView::PartialRenderer` mutates the contents of `opts[:locals]`, `opts[:locals][:as]` in particular:
86
+ # https://github.com/rails/rails/blob/v6.0.2.1/actionview/lib/action_view/renderer/partial_renderer.rb#L379
87
+ # https://github.com/rails/rails/blob/v6.0.2.1/actionview/lib/action_view/renderer/partial_renderer.rb#L348-L356
88
+ opts[:locals] = opts[:locals].dup if opts[:locals]
89
+ ActiveRecord::Base.connection_pool.with_connection do
90
+ render_partials_serial(view_context.dup, slice, opts)
91
+ end
92
+ end
76
93
  end.flat_map(&:value)
77
94
  end
78
95
  end
79
96
 
80
- # @param [Array<Object>] collection
81
- # @param [Hash] opts
82
- # @param view_context
83
- # @return [Array<String>]
84
- def render_partials_serial(view_context, collection, opts)
85
- partial_renderer = ActionView::PartialRenderer.new(@lookup_context)
86
- collection.map { |object| render_partial(partial_renderer, view_context, opts.merge(object: object)) }
97
+ if Thredded::Compat.rails_gte_61?
98
+ # @param [Array<Object>] collection
99
+ # @param [Hash] opts
100
+ # @param view_context
101
+ # @return [Array<String>]
102
+ def render_partials_serial(view_context, collection, opts)
103
+ # https://github.com/rails/rails/pull/38594
104
+ collection.map do |object|
105
+ renderer = ActionView::ObjectRenderer.new(@lookup_context, opts)
106
+ renderer.render_object_with_partial(object, opts[:partial], view_context, nil).body
107
+ end
108
+ end
109
+ else
110
+ def render_partials_serial(view_context, collection, opts)
111
+ partial_renderer = ActionView::PartialRenderer.new(@lookup_context)
112
+ collection.map { |object| render_partial(partial_renderer, view_context, **opts.merge(object: object)) }
113
+ end
87
114
  end
88
115
 
89
- if Rails::VERSION::MAJOR >= 6
116
+ def collection_cache
117
+ ActionView::PartialRenderer.collection_cache
118
+ end
119
+
120
+ def combined_fragment_cache_key(view, key)
121
+ view.combined_fragment_cache_key(key)
122
+ end
123
+
124
+ if Thredded::Compat.rails_gte_60?
125
+ def cache_fragment_name(view, key, virtual_path:, digest_path:)
126
+ if Thredded::Compat.rails_gte_61?
127
+ view.cache_fragment_name(key, digest_path: digest_path)
128
+ else
129
+ view.cache_fragment_name(key, virtual_path: virtual_path, digest_path: digest_path)
130
+ end
131
+ end
132
+
133
+ def digest_path_from_template(view, template)
134
+ view.digest_path_from_template(template)
135
+ end
136
+
90
137
  def render_partial(partial_renderer, view_context, opts)
91
138
  partial_renderer.render(view_context, opts, nil).body
92
139
  end
93
140
  else
141
+ def cache_fragment_name(_view, key, virtual_path:, digest_path:)
142
+ if digest_path
143
+ ["#{virtual_path}:#{digest_path}", key]
144
+ else
145
+ [virtual_path, key]
146
+ end
147
+ end
148
+
149
+ def digest_path_from_template(view, template)
150
+ ActionView::Digestor.digest(
151
+ name: template.virtual_path,
152
+ finder: @lookup_context,
153
+ dependencies: view.view_cache_dependencies
154
+ ).presence
155
+ end
156
+
94
157
  def render_partial(partial_renderer, view_context, opts)
95
158
  partial_renderer.render(view_context, opts, nil)
96
159
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module Compat
5
+ class << self
6
+ # @api private
7
+ def rails_gte_60?
8
+ @rails_gte_60 = (Rails.gem_version >= Gem::Version.new('6.0.0')) if @rails_gte_60.nil?
9
+ @rails_gte_60
10
+ end
11
+
12
+ # @api private
13
+ def rails_gte_61?
14
+ @rails_gte_61 = (Rails.gem_version >= Gem::Version.new('6.1.0')) if @rails_gte_61.nil?
15
+ @rails_gte_61
16
+ end
17
+
18
+ if Rails.gem_version >= Gem::Version.new('7.0.0')
19
+ # @api private
20
+ def association_preloader(records:, associations:, scope:)
21
+ ActiveRecord::Associations::Preloader.new(
22
+ records: records, associations: associations, scope: scope
23
+ ).call
24
+ end
25
+ else
26
+ # @api private
27
+ def association_preloader(records:, associations:, scope:)
28
+ ActiveRecord::Associations::Preloader.new.preload(
29
+ records, associations, scope
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'factory_bot'
4
- require_relative '../../spec/support/features/fake_content'
4
+ require_relative '../../spec/support/system/fake_content'
5
5
 
6
6
  # rubocop:disable HandleExceptions
7
7
  begin
@@ -40,6 +40,7 @@ module Thredded
40
40
  end
41
41
 
42
42
  include LogTime
43
+ include Thredded::ApplicationHelper
43
44
 
44
45
  SKIP_CALLBACKS = [
45
46
  [Thredded::Post, :commit, :after, :update_parent_last_user_and_time_from_last_post, on: %i[create destroy]],
@@ -131,7 +132,13 @@ module Thredded
131
132
  end
132
133
 
133
134
  def fake_post_contents
134
- @fake_post_contents ? @fake_post_contents.sample : FakeContent.post_content
135
+ with_mentions(@fake_post_contents ? @fake_post_contents.sample : FakeContent.post_content)
136
+ end
137
+
138
+ def with_mentions(post, mentions_count: rand(3))
139
+ return post if mentions_count.zero?
140
+
141
+ ([post] + Array.new(mentions_count).map { user_mention(@users.sample) }).join(' ')
135
142
  end
136
143
 
137
144
  def first_user
@@ -255,9 +262,9 @@ module Thredded
255
262
 
256
263
  delegate :log, to: :seeder
257
264
 
258
- def find_or_create(*args)
265
+ def find_or_create(*args, **kwargs)
259
266
  return @stored if @stored
260
- @stored = (find || create(*args))
267
+ @stored = (find || create(*args, **kwargs))
261
268
  end
262
269
 
263
270
  def range_of_dates_in_order(up_to: Time.zone.now, count: 1)
@@ -10,12 +10,10 @@ module Thredded
10
10
  verbose_was = ActiveRecord::Migration.verbose
11
11
  ActiveRecord::Migration.verbose = !quiet
12
12
  migrate =
13
- if Rails.gem_version >= Gem::Version.new('6.0.0.rc2')
13
+ if Thredded::Compat.rails_gte_60?
14
14
  -> { ActiveRecord::MigrationContext.new(paths, ActiveRecord::SchemaMigration).migrate(nil, &filter) }
15
- elsif Rails::VERSION::STRING >= '5.2'
15
+ else # Rails 5.2
16
16
  -> { ActiveRecord::MigrationContext.new(paths).migrate(nil, &filter) }
17
- else
18
- -> { ActiveRecord::Migrator.migrate(paths, &filter) }
19
17
  end
20
18
  if quiet
21
19
  silence_active_record(&migrate)
@@ -17,8 +17,8 @@ module Thredded
17
17
  end
18
18
  @transformers = [Onebox, Spoiler]
19
19
 
20
- # @param doc [Nokogiri::HTML::Document]
21
- def self.call(doc)
20
+ # @param dom [Nokogiri::HTML::Document]
21
+ def self.call(doc, *)
22
22
  transformers.each { |transformer| transformer.call(doc) }
23
23
  end
24
24
  end
@@ -39,12 +39,10 @@ module Thredded
39
39
  # Regardless, always store the onebox in a file cache to enable offline development,
40
40
  # persistence between test runs, and to improve performance.
41
41
  attr_accessor :onebox_views_cache
42
- attr_accessor :onebox_data_cache
43
42
  end
44
43
 
45
44
  if Rails.env.development? || Rails.env.test?
46
45
  self.onebox_views_cache = ActiveSupport::Cache::FileStore.new('tmp/cache/onebox-views')
47
- self.onebox_data_cache = ActiveSupport::Cache::FileStore.new('tmp/cache/onebox-data')
48
46
  end
49
47
 
50
48
  def call
@@ -84,8 +82,7 @@ module Thredded
84
82
  end
85
83
 
86
84
  def onebox_options(_url)
87
- { cache: context[:onebox_data_cache] || self.class.onebox_data_cache || Rails.cache,
88
- sanitize_config: SANITIZE_CONFIG }
85
+ { sanitize_config: SANITIZE_CONFIG }
89
86
  end
90
87
 
91
88
  def onebox_views_cache
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thredded
4
- VERSION = '0.16.14'
4
+ VERSION = '1.0.0'
5
5
  end