thredded 0.10.0 → 0.10.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -17
  3. data/app/assets/images/thredded/three-dot-menu.svg +3 -0
  4. data/app/assets/stylesheets/thredded/base/_tables.scss +1 -0
  5. data/app/assets/stylesheets/thredded/base/_variables.scss +24 -1
  6. data/app/assets/stylesheets/thredded/components/_currently-online.scss +1 -0
  7. data/app/assets/stylesheets/thredded/components/_messageboard.scss +18 -10
  8. data/app/assets/stylesheets/thredded/components/_post.scss +84 -13
  9. data/app/assets/stylesheets/thredded/components/_topics.scss +7 -1
  10. data/app/assets/stylesheets/thredded/layout/_main-container.scss +1 -0
  11. data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +15 -6
  12. data/app/controllers/thredded/application_controller.rb +6 -3
  13. data/app/controllers/thredded/moderation_controller.rb +1 -1
  14. data/app/controllers/thredded/posts_controller.rb +19 -22
  15. data/app/controllers/thredded/preferences_controller.rb +1 -2
  16. data/app/controllers/thredded/private_posts_controller.rb +77 -0
  17. data/app/controllers/thredded/private_topics_controller.rb +1 -1
  18. data/app/controllers/thredded/read_states_controller.rb +1 -1
  19. data/app/controllers/thredded/topics_controller.rb +1 -1
  20. data/app/forms/thredded/private_topic_form.rb +3 -3
  21. data/app/forms/thredded/topic_form.rb +1 -1
  22. data/app/helpers/thredded/application_helper.rb +12 -1
  23. data/app/helpers/thredded/render_helper.rb +14 -0
  24. data/app/helpers/thredded/urls_helper.rb +8 -0
  25. data/app/models/concerns/thredded/post_common.rb +20 -0
  26. data/app/models/concerns/thredded/user_topic_read_state_common.rb +6 -0
  27. data/app/models/thredded/null_user_topic_read_state.rb +4 -0
  28. data/app/policies/thredded/post_policy.rb +4 -0
  29. data/app/policies/thredded/private_post_policy.rb +4 -0
  30. data/app/view_hooks/thredded/all_view_hooks.rb +15 -0
  31. data/app/view_models/thredded/base_topic_view.rb +1 -1
  32. data/app/view_models/thredded/post_view.rb +23 -21
  33. data/app/view_models/thredded/posts_page_view.rb +4 -2
  34. data/app/view_models/thredded/topic_posts_page_view.rb +1 -1
  35. data/app/view_models/thredded/topic_view.rb +1 -1
  36. data/app/view_models/thredded/topics_page_view.rb +1 -0
  37. data/app/views/thredded/moderation/_post.html.erb +2 -2
  38. data/app/views/thredded/moderation/_user_post.html.erb +2 -2
  39. data/app/views/thredded/moderation/activity.html.erb +3 -1
  40. data/app/views/thredded/moderation/pending.html.erb +3 -1
  41. data/app/views/thredded/moderation/user.html.erb +3 -1
  42. data/app/views/thredded/posts/_content.html.erb +1 -0
  43. data/app/views/thredded/posts/_post.html.erb +11 -12
  44. data/app/views/thredded/posts/edit.html.erb +3 -4
  45. data/app/views/thredded/posts_common/_actions.html.erb +21 -8
  46. data/app/views/thredded/posts_common/actions/_delete.html.erb +4 -0
  47. data/app/views/thredded/posts_common/actions/_edit.html.erb +2 -0
  48. data/app/views/thredded/posts_common/actions/_mark_as_unread.html.erb +2 -0
  49. data/app/views/thredded/private_posts/_content.html.erb +1 -0
  50. data/app/views/thredded/private_posts/_private_post.html.erb +5 -6
  51. data/app/views/thredded/private_posts/edit.html.erb +18 -0
  52. data/app/views/thredded/private_topics/show.html.erb +3 -1
  53. data/app/views/thredded/shared/_nav.html.erb +1 -1
  54. data/app/views/thredded/shared/nav/_standalone.html.erb +1 -1
  55. data/app/views/thredded/topics/_sticky_topics_divider.html.erb +1 -0
  56. data/app/views/thredded/topics/_topic.html.erb +4 -0
  57. data/app/views/thredded/topics/index.html.erb +1 -1
  58. data/app/views/thredded/topics/show.html.erb +1 -1
  59. data/app/views/thredded/users/_post.html.erb +2 -2
  60. data/app/views/thredded/users/_posts.html.erb +1 -1
  61. data/config/locales/en.yml +1 -0
  62. data/config/locales/es.yml +1 -0
  63. data/config/locales/pl.yml +1 -0
  64. data/config/locales/pt-BR.yml +1 -0
  65. data/config/routes.rb +9 -4
  66. data/lib/generators/thredded/install/templates/initializer.rb +7 -0
  67. data/lib/thredded.rb +4 -0
  68. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +62 -0
  69. data/lib/thredded/version.rb +1 -1
  70. metadata +15 -4
@@ -1,4 +1,4 @@
1
- <%# @param post [Thredded::PostView] %>
1
+ <% post, content = post_and_content if local_assigns.key?(:post_and_content) %>
2
2
  <%= content_tag :article, id: dom_id(post), class: 'thredded--post thredded--post-moderation' do %>
3
3
  <%= render 'thredded/posts_common/header_with_user_and_topic',
4
4
  post: post,
@@ -8,7 +8,7 @@
8
8
  content_tag :em, t('thredded.null_user_name')
9
9
  end
10
10
  %>
11
- <%= render 'thredded/posts_common/content', post: post %>
11
+ <%= content || render('thredded/posts/content', post: post) %>
12
12
  <%= render 'thredded/posts_common/actions', post: post %>
13
13
  <% if post.blocked? %>
14
14
  <p class="thredded--alert thredded--alert-danger">
@@ -1,7 +1,7 @@
1
- <%# @param post [Thredded::PostView] %>
1
+ <% post, content = post_and_content if local_assigns.key?(:post_and_content) %>
2
2
  <%= content_tag :article, id: dom_id(post), class: 'thredded--post thredded--post-moderation' do %>
3
3
  <%= render 'thredded/posts_common/header_with_topic', post: post %>
4
- <%= render 'thredded/posts_common/content', post: post %>
4
+ <%= content || render('thredded/posts/content', post: post) %>
5
5
  <%= render 'thredded/posts_common/actions', post: post %>
6
6
  <% if post.blocked? %>
7
7
  <p class="thredded--alert thredded--alert-danger">
@@ -11,7 +11,9 @@
11
11
  </div>
12
12
  <% end %>
13
13
  <% if @posts.present? %>
14
- <%= render partial: 'post', collection: @posts %>
14
+ <%= render_posts @posts,
15
+ partial: 'thredded/moderation/post',
16
+ content_partial: 'thredded/posts/content' %>
15
17
  <%= paginate @posts %>
16
18
  <% end %>
17
19
  <% end %>
@@ -10,7 +10,9 @@
10
10
  </div>
11
11
  <% end %>
12
12
  <% if @posts.present? %>
13
- <%= render partial: 'post', collection: @posts %>
13
+ <%= render_posts @posts,
14
+ partial: 'thredded/moderation/post',
15
+ content_partial: 'thredded/posts/content' %>
14
16
  <%= paginate @posts %>
15
17
  <% else %>
16
18
  <div class="thredded--empty">
@@ -42,7 +42,9 @@
42
42
  <% end %>
43
43
  <% if @posts.present? %>
44
44
  <h2><%= t 'thredded.users.recent_activity' %></h2>
45
- <%= render partial: 'user_post', collection: @posts, as: :post %>
45
+ <%= render_posts @posts,
46
+ partial: 'thredded/moderation/user_post',
47
+ content_partial: 'thredded/posts/content' %>
46
48
  <%= paginate @posts %>
47
49
  <% end %>
48
50
  <% end %>
@@ -0,0 +1 @@
1
+ <%= render 'thredded/posts_common/content', post: post %>
@@ -1,14 +1,13 @@
1
- <% cache(post, expires_in: 1.week) do %>
2
- <%= content_tag :article, id: dom_id(post), class: 'thredded--post' do %>
3
- <%= render 'thredded/posts_common/header', post: post %>
4
- <%= render 'thredded/posts_common/content', post: post %>
5
- <%= render 'thredded/posts_common/actions', post: post %>
6
- <% if post.pending_moderation? && !Thredded.content_visible_while_pending_moderation %>
7
- <p class="thredded--alert thredded--alert-warning"><%= t 'thredded.posts.pending_moderation_notice' %></p>
8
- <% elsif post.blocked? && post.can_moderate? %>
9
- <p class="thredded--alert thredded--alert-danger">
10
- <%= render 'thredded/shared/content_moderation_blocked_state', moderation_record: post.last_moderation_record %>
11
- </p>
12
- <% end %>
1
+ <% post, content = post_and_content if local_assigns.key?(:post_and_content) %>
2
+ <%= content_tag :article, id: dom_id(post), class: "thredded--post thredded--#{post.read_state}--post" do %>
3
+ <%= render 'thredded/posts_common/actions', post: post %>
4
+ <%= render 'thredded/posts_common/header', post: post %>
5
+ <%= content || render('thredded/posts/content', post: post) %>
6
+ <% if post.pending_moderation? && !Thredded.content_visible_while_pending_moderation %>
7
+ <p class="thredded--alert thredded--alert-warning"><%= t 'thredded.posts.pending_moderation_notice' %></p>
8
+ <% elsif post.blocked? && post.can_moderate? %>
9
+ <p class="thredded--alert thredded--alert-danger">
10
+ <%= render 'thredded/shared/content_moderation_blocked_state', moderation_record: post.last_moderation_record %>
11
+ </p>
13
12
  <% end %>
14
13
  <% end %>
@@ -1,4 +1,4 @@
1
- <% content_for :thredded_page_title, 'Edit Post' %>
1
+ <% content_for :thredded_page_title, t('thredded.nav.edit_post') %>
2
2
  <% content_for :thredded_page_id, 'thredded--edit-post' %>
3
3
  <% content_for :thredded_breadcrumbs do %>
4
4
  <ul class="thredded--navigation-breadcrumbs">
@@ -9,11 +9,10 @@
9
9
  <%= thredded_page do %>
10
10
  <section class="thredded--main-section">
11
11
  <%= render 'thredded/posts/form',
12
- messageboard: (messageboard unless @post.private_topic_post?),
12
+ messageboard: messageboard,
13
13
  topic: topic,
14
14
  post: @post,
15
- preview_url: (@post.private_topic_post? ? private_topic_private_post_preview_path(@post.postable, @post)
16
- : messageboard_topic_post_preview_path(messageboard, @post.postable, @post)),
15
+ preview_url: messageboard_topic_post_preview_path(messageboard, @post.postable, @post),
17
16
  button_text: t('thredded.posts.form.update_btn'),
18
17
  button_submitting_text: t('thredded.posts.form.update_btn_submitting')%>
19
18
  </section>
@@ -1,11 +1,24 @@
1
- <% if post.can_update? %>
2
- <%= link_to t('thredded.posts.edit'), post.edit_path, class: 'thredded--post--edit' %>
1
+ <% actions = capture do %>
2
+ <%= view_hooks.post_common.actions.render self, post: post do %>
3
+ <% if post.can_update? %>
4
+ <%= render 'thredded/posts_common/actions/edit', post: post %>
5
+ <% end %>
6
+ <% if post.can_destroy? %>
7
+ <%= render 'thredded/posts_common/actions/delete', post: post %>
8
+ <% end %>
9
+ <% if post.read_state %>
10
+ <%= view_hooks.post_common.mark_as_unread.render self, post: post do %>
11
+ <%= render 'thredded/posts_common/actions/mark_as_unread', post: post %>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
3
15
  <% end %>
4
16
 
5
- <% if post.can_destroy? %>
6
- <%= link_to t('thredded.posts.delete'), post.destroy_path,
7
- method: :delete,
8
- class: 'thredded--post--delete',
9
- data: { confirm: I18n.t('thredded.posts.delete_confirm') }
10
- %>
17
+ <%- if actions.present? %>
18
+ <div class='thredded--post--dropdown'>
19
+ <%= inline_svg 'thredded/three-dot-menu.svg', class: 'thredded--post--dropdown--toggle' %>
20
+ <div class='thredded--post--dropdown--actions'>
21
+ <%= actions %>
22
+ </div>
23
+ </div>
11
24
  <% end %>
@@ -0,0 +1,4 @@
1
+ <%= button_to t('thredded.posts.delete'), post.destroy_path,
2
+ method: :delete,
3
+ class: 'thredded--post--delete thredded--post--dropdown--actions--item',
4
+ data: {confirm: I18n.t('thredded.posts.delete_confirm')} %>
@@ -0,0 +1,2 @@
1
+ <%= link_to t('thredded.posts.edit'), post.edit_path,
2
+ class: 'thredded--post--edit thredded--post--dropdown--actions--item' %>
@@ -0,0 +1,2 @@
1
+ <%= button_to(t('thredded.topics.mark_as_unread'), post.mark_unread_path, method: :post, class:
2
+ 'thredded--post--mark-as-unread thredded--post--dropdown--actions--item') %>
@@ -0,0 +1 @@
1
+ <%= render 'thredded/posts_common/content', post: post %>
@@ -1,7 +1,6 @@
1
- <% cache(private_post, expires_in: 1.week) do %>
2
- <%= content_tag :article, id: dom_id(private_post), class: 'thredded--post' do %>
3
- <%= render 'thredded/posts_common/header', post: private_post %>
4
- <%= render 'thredded/posts_common/content', post: private_post %>
5
- <%= render 'thredded/posts_common/actions', post: private_post %>
6
- <% end %>
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 %>
3
+ <%= render 'thredded/posts_common/actions', post: private_post %>
4
+ <%= render 'thredded/posts_common/header', post: private_post %>
5
+ <%= content || render('thredded/private_posts/content', post: post) %>
7
6
  <% end %>
@@ -0,0 +1,18 @@
1
+ <% content_for :thredded_page_title, t('thredded.nav.edit_post') %>
2
+ <% content_for :thredded_page_id, 'thredded--edit-post' %>
3
+ <% content_for :thredded_breadcrumbs do %>
4
+ <ul class="thredded--navigation-breadcrumbs">
5
+ <li><%= link_to t('thredded.nav.edit_post'), edit_post_path(@post) %></li>
6
+ </ul>
7
+ <% end %>
8
+
9
+ <%= thredded_page do %>
10
+ <section class="thredded--main-section">
11
+ <%= render 'thredded/posts/form',
12
+ topic: topic,
13
+ post: @post,
14
+ preview_url: private_topic_private_post_preview_path(@post.postable, @post),
15
+ button_text: t('thredded.posts.form.update_btn'),
16
+ button_submitting_text: t('thredded.posts.form.update_btn_submitting')%>
17
+ </section>
18
+ <% end %>
@@ -11,7 +11,9 @@
11
11
  <%= view_hooks.posts_common.pagination_top.render(self, posts: @posts) do %>
12
12
  <footer class="thredded--pagination-top"><%= paginate @posts %></footer>
13
13
  <% end %>
14
- <%= render partial: 'thredded/private_posts/private_post', collection: @posts, cached: true %>
14
+ <%= render_posts @posts,
15
+ partial: 'thredded/private_posts/private_post',
16
+ content_partial: 'thredded/private_posts/content' %>
15
17
  <%= view_hooks.posts_common.pagination_bottom.render(self, posts: @posts) do %>
16
18
  <footer class="thredded--pagination-bottom"><%= paginate @posts %></footer>
17
19
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <nav class="thredded--navigation">
2
2
  <ul class="thredded--user-navigation<%= ' thredded--user-navigation-standalone' if Thredded.standalone_layout? %>">
3
- <% if signed_in? && Thredded.standalone_layout? %>
3
+ <% if thredded_signed_in? && Thredded.standalone_layout? %>
4
4
  <li class="thredded--user-navigation--profile thredded--user-navigation--item">
5
5
  <%= link_to thredded_current_user.thredded_display_name, user_path(thredded_current_user) %>
6
6
  </li>
@@ -1,6 +1,6 @@
1
1
  <li class="thredded--user-navigation--standalone thredded--user-navigation--item">
2
2
  <% resource_name = Thredded.user_class.name.underscore %>
3
- <% if signed_in? %>
3
+ <% if thredded_signed_in? %>
4
4
  <%= link_to main_app.send(:"destroy_#{resource_name}_session_path"), method: :delete do %>
5
5
  <span>Sign Out</span>
6
6
  <% end %>
@@ -0,0 +1 @@
1
+ <hr class="thredded--topics--sticky-topics-divider">
@@ -40,3 +40,7 @@
40
40
  </span>
41
41
  <% end %>
42
42
  <% end %>
43
+
44
+ <% if !topic_iteration.last? && topic.sticky? && !topics[topic_counter + 1].sticky? %>
45
+ <%= render 'thredded/topics/sticky_topics_divider' %>
46
+ <% end %>
@@ -15,7 +15,7 @@
15
15
  css_class: 'thredded--is-compact',
16
16
  preview_url: preview_new_messageboard_topic_path(messageboard),
17
17
  placeholder: t('thredded.topics.form.title_placeholder_start') if @new_topic %>
18
- <%= render @topics %>
18
+ <%= render partial: 'thredded/topics/topic', collection: @topics, locals: {topics: @topics} %>
19
19
  <% end %>
20
20
 
21
21
  <footer class="thredded--pagination-bottom">
@@ -12,7 +12,7 @@
12
12
  <%= view_hooks.posts_common.pagination_top.render(self, posts: @posts) do %>
13
13
  <footer class="thredded--pagination-top"><%= paginate @posts %></footer>
14
14
  <% end %>
15
- <%= render partial: 'thredded/posts/post', collection: @posts, cached: true %>
15
+ <%= render_posts @posts, partial: 'thredded/posts/post', content_partial: 'thredded/posts/content' %>
16
16
  <%= view_hooks.posts_common.pagination_bottom.render(self, posts: @posts) do %>
17
17
  <footer class="thredded--pagination-bottom"><%= paginate @posts %></footer>
18
18
  <% end %>
@@ -1,6 +1,6 @@
1
- <%# @param post [Thredded::PostView] %>
1
+ <% post, content = post_and_content if local_assigns.key?(:post_and_content) %>
2
2
  <%= content_tag :article, id: dom_id(post), class: 'thredded--post' do %>
3
3
  <%= render 'thredded/posts_common/header_with_topic', post: post %>
4
- <%= render 'thredded/posts_common/content', post: post %>
4
+ <%= content || render('thredded/posts/content', post: post) %>
5
5
  <%= render 'thredded/posts_common/actions', post: post %>
6
6
  <% end %>
@@ -4,4 +4,4 @@
4
4
  TODO: Use a Cell instead. https://github.com/apotonick/cells
5
5
  %>
6
6
  <%# @param posts [Thredded::PostsPageView] %>
7
- <%= render partial: 'thredded/users/post', collection: posts %>
7
+ <%= render_posts posts, partial: 'thredded/users/post', content_partial: 'thredded/posts/content' %>
@@ -157,6 +157,7 @@ en:
157
157
  update_btn: Update Topic
158
158
  locked:
159
159
  label: Locked
160
+ mark_as_unread: Mark unread from here
160
161
  not_following: You are not following this topic.
161
162
  search:
162
163
  no_results_message: There are no results for your search - %{query}
@@ -157,6 +157,7 @@ es:
157
157
  update_btn: Actualizar Tema
158
158
  locked:
159
159
  label: Bloqueado
160
+ mark_as_unread: Marcar sin leer desde aquí
160
161
  not_following: No estás siguiendo este tema.
161
162
  search:
162
163
  no_results_message: No hay resultados para tu búsqueda - %{query}
@@ -156,6 +156,7 @@ pl:
156
156
  update_btn: Zaktualizuj temat
157
157
  locked:
158
158
  label: Zablokowany
159
+ mark_as_unread: Oznacz jako nieprzeczytane stąd
159
160
  not_following: Nie obserwujesz tego tematu.
160
161
  search:
161
162
  no_results_message: 'Nie odnaleziono żadnych wyników dla frazy: %{query}'
@@ -160,6 +160,7 @@ pt-BR:
160
160
  update_btn: Atualizar Tópico
161
161
  locked:
162
162
  label: Trancado
163
+ mark_as_unread: Marca não lida a partir daqui
163
164
  not_following: Você não está seguindo este tema.
164
165
  search:
165
166
  no_results_message: Nenhum resultado encontrado para sua busca - %{query}
data/config/routes.rb CHANGED
@@ -2,8 +2,7 @@
2
2
  Thredded::Engine.routes.draw do # rubocop:disable Metrics/BlockLength
3
3
  resource :theme_preview, only: [:show], path: 'theme-preview' if %w(development test).include? Rails.env
4
4
 
5
- positive_int = /[1-9]\d*/
6
- page_constraint = { page: positive_int }
5
+ page_constraint = { page: /[1-9]\d*/ }
7
6
 
8
7
  scope path: 'private-topics' do
9
8
  resource :read_state, only: [:update], as: :mark_all_private_topics_read
@@ -14,14 +13,17 @@ Thredded::Engine.routes.draw do # rubocop:disable Metrics/BlockLength
14
13
  member do
15
14
  get '(page-:page)', action: :show, as: '', constraints: page_constraint
16
15
  end
17
- resources :private_posts, path: '', except: [:index, :show], controller: 'posts' do
16
+ resources :private_posts, path: '', except: [:index, :show] do
18
17
  post :preview, on: :new, controller: 'private_post_previews'
19
18
  resource :preview, only: [:update], controller: 'private_post_previews'
19
+ member do
20
+ post 'mark_as_unread'
21
+ end
20
22
  end
21
23
  end
22
24
  end
23
25
 
24
- scope only: [:show], constraints: { id: positive_int } do
26
+ scope only: [:show], constraints: { id: Thredded.routes_id_constraint } do
25
27
  resources :private_post_permalinks, path: 'private-posts'
26
28
  resources :post_permalinks, path: 'posts'
27
29
  end
@@ -71,6 +73,9 @@ Thredded::Engine.routes.draw do # rubocop:disable Metrics/BlockLength
71
73
  resources :posts, except: [:index, :show], path: '' do
72
74
  post :preview, on: :new, controller: 'post_previews'
73
75
  resource :preview, only: [:update], controller: 'post_previews'
76
+ member do
77
+ post 'mark_as_unread'
78
+ end
74
79
  end
75
80
  end
76
81
  end
@@ -29,6 +29,13 @@ Thredded.current_user_method = :"current_#{Thredded.user_class.name.underscore}"
29
29
  # User avatar URL. rb-gravatar gem is used by default:
30
30
  Thredded.avatar_url = ->(user) { Gravatar.src(user.email, 128, 'mm') }
31
31
 
32
+ # ==> Database Configuration
33
+ # By default, thredded uses integers for record ID route constraints.
34
+ # For integer based IDs (default):
35
+ # Thredded.routes_id_constraint = /[1-9]\d*/
36
+ # For UUID based IDs (example):
37
+ # Thredded.routes_id_constraint = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
38
+
32
39
  # ==> Permissions Configuration
33
40
  # By default, thredded uses a simple permission model, where all the users can post to all message boards,
34
41
  # and admins and moderators are determined by a flag on the users table.
data/lib/thredded.rb CHANGED
@@ -39,6 +39,8 @@ require 'thredded/view_hooks/renderer'
39
39
  # Require Thredded::ContentFormatter explicitly so that it doesn't need to be required if used in the initializer.
40
40
  require 'thredded/content_formatter'
41
41
 
42
+ require 'thredded/collection_to_strings_with_cache_renderer'
43
+
42
44
  module Thredded
43
45
  mattr_accessor \
44
46
  :autocomplete_min_length,
@@ -48,6 +50,7 @@ module Thredded
48
50
  :email_outgoing_prefix,
49
51
  :layout,
50
52
  :messageboards_order,
53
+ :routes_id_constraint,
51
54
  :user_class,
52
55
  :user_display_name_method,
53
56
  :user_name_column,
@@ -92,6 +95,7 @@ module Thredded
92
95
  self.show_topic_followers = false
93
96
  self.messageboards_order = :position
94
97
  self.autocomplete_min_length = 2
98
+ self.routes_id_constraint = /[1-9]\d*/
95
99
 
96
100
  # @return [Thredded::AllViewHooks] View hooks configuration.
97
101
  def self.view_hooks
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ require 'action_view/renderer/abstract_renderer'
3
+ module Thredded
4
+ class CollectionToStringsWithCacheRenderer < ActionView::AbstractRenderer
5
+ # @param view_context
6
+ # @param collection [Array<T>]
7
+ # @param partial [String]
8
+ # @param expires_in [ActiveSupport::Duration]
9
+ # @return Array<[T, String]>
10
+ def render_collection_to_strings_with_cache( # rubocop:disable Metrics/ParameterLists
11
+ view_context, collection:, partial:, expires_in:, locals: {}, **opts
12
+ )
13
+ template = @lookup_context.find_template(partial, [], true, locals, {})
14
+ collection = collection.to_a
15
+ instrument(:collection, count: collection.size) do |instrumentation_payload|
16
+ return [] if collection.blank?
17
+ keyed_collection = collection.each_with_object({}) do |item, hash|
18
+ key = ActiveSupport::Cache.expand_cache_key(
19
+ view_context.cache_fragment_name(item, virtual_path: template.virtual_path), :views
20
+ )
21
+ # #read_multi & #write may require key mutability, Dalli 2.6.0.
22
+ hash[key.frozen? ? key.dup : key] = item
23
+ end
24
+ cache = collection_cache
25
+ cached_partials = cache.read_multi(*keyed_collection.keys)
26
+ instrumentation_payload[:cache_hits] = cached_partials.size if instrumentation_payload
27
+
28
+ collection_to_render = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
29
+ rendered_partials = render_partials(
30
+ view_context, collection: collection_to_render, partial: partial, locals: locals, **opts
31
+ ).each
32
+
33
+ keyed_collection.map do |cache_key, item|
34
+ [item, cached_partials[cache_key] || rendered_partials.next.tap do |rendered|
35
+ cache.write(cache_key, rendered, expires_in: expires_in)
36
+ end]
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def collection_cache
44
+ if ActionView::PartialRenderer.respond_to?(:collection_cache)
45
+ # Rails 5.0+
46
+ ActionView::PartialRenderer.collection_cache
47
+ else
48
+ # Rails 4.2.x
49
+ Rails.application.config.action_controller.cache_store
50
+ end
51
+ end
52
+
53
+ # @return [Array<String>]
54
+ def render_partials(view_context, collection:, **opts)
55
+ return [] if collection.empty?
56
+ partial_renderer = ActionView::PartialRenderer.new(@lookup_context)
57
+ collection.map do |item|
58
+ partial_renderer.render(view_context, opts.merge(object: item), nil)
59
+ end
60
+ end
61
+ end
62
+ end