thredded 0.11.0 → 0.12.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -23
  3. data/app/assets/javascripts/thredded/components/post_form.es6 +4 -4
  4. data/app/assets/javascripts/thredded/components/quote_post.es6 +45 -0
  5. data/app/assets/javascripts/thredded/components/topic_form.es6 +4 -4
  6. data/app/assets/javascripts/thredded/core/on_page_load.es6 +8 -0
  7. data/app/assets/javascripts/thredded/dependencies.js +1 -1
  8. data/app/assets/stylesheets/thredded/_base.scss +1 -0
  9. data/app/assets/stylesheets/thredded/_email.scss +2 -3
  10. data/app/assets/stylesheets/thredded/base/_dropdown-menu.scss +83 -0
  11. data/app/assets/stylesheets/thredded/components/_post.scss +5 -60
  12. data/app/assets/stylesheets/thredded/components/_preferences.scss +17 -4
  13. data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +4 -1
  14. data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +31 -6
  15. data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +1 -0
  16. data/app/controllers/concerns/thredded/new_post_params.rb +20 -0
  17. data/app/controllers/concerns/thredded/new_private_post_params.rb +20 -0
  18. data/app/controllers/thredded/application_controller.rb +6 -0
  19. data/app/controllers/thredded/posts_controller.rb +28 -13
  20. data/app/controllers/thredded/preferences_controller.rb +1 -0
  21. data/app/controllers/thredded/private_posts_controller.rb +30 -12
  22. data/app/controllers/thredded/private_topics_controller.rb +11 -1
  23. data/app/controllers/thredded/theme_previews_controller.rb +4 -2
  24. data/app/controllers/thredded/topics_controller.rb +36 -3
  25. data/app/forms/thredded/post_form.rb +52 -0
  26. data/app/forms/thredded/private_post_form.rb +48 -0
  27. data/app/forms/thredded/private_topic_form.rb +8 -0
  28. data/app/forms/thredded/topic_form.rb +8 -0
  29. data/app/forms/thredded/user_preferences_form.rb +7 -1
  30. data/app/helpers/thredded/urls_helper.rb +18 -0
  31. data/app/models/thredded/topic.rb +3 -3
  32. data/app/policies/thredded/post_policy.rb +1 -1
  33. data/app/policies/thredded/private_topic_policy.rb +1 -1
  34. data/app/view_hooks/thredded/all_view_hooks.rb +3 -0
  35. data/app/view_models/thredded/messageboard_group_view.rb +1 -1
  36. data/app/view_models/thredded/post_view.rb +19 -3
  37. data/app/view_models/thredded/private_topic_view.rb +0 -4
  38. data/app/view_models/thredded/topic_view.rb +0 -4
  39. data/app/views/thredded/moderation/_users_search_form.html.erb +1 -1
  40. data/app/views/thredded/moderation/user.html.erb +13 -11
  41. data/app/views/thredded/moderation/users.html.erb +1 -0
  42. data/app/views/thredded/posts/_form.html.erb +0 -2
  43. data/app/views/thredded/posts/edit.html.erb +2 -5
  44. data/app/views/thredded/posts/new.html.erb +15 -0
  45. data/app/views/thredded/posts_common/_actions.html.erb +3 -0
  46. data/app/views/thredded/posts_common/_form.html.erb +5 -3
  47. data/app/views/thredded/posts_common/_header.html.erb +3 -1
  48. data/app/views/thredded/posts_common/actions/_quote.html.erb +4 -0
  49. data/app/views/thredded/preferences/_form.html.erb +3 -5
  50. data/app/views/thredded/preferences/_messageboards_nav.html.erb +8 -0
  51. data/app/views/thredded/preferences/_messageboards_nav_item.html.erb +2 -0
  52. data/app/views/thredded/preferences/edit.html.erb +13 -3
  53. data/app/views/thredded/private_posts/_form.html.erb +0 -2
  54. data/app/views/thredded/private_posts/edit.html.erb +2 -4
  55. data/app/views/thredded/private_posts/new.html.erb +11 -0
  56. data/app/views/thredded/private_topics/_form.html.erb +2 -2
  57. data/app/views/thredded/private_topics/index.html.erb +1 -2
  58. data/app/views/thredded/private_topics/new.html.erb +0 -1
  59. data/app/views/thredded/private_topics/show.html.erb +5 -3
  60. data/app/views/thredded/shared/_nav.html.erb +1 -7
  61. data/app/views/thredded/shared/nav/_notification_preferences.html.erb +1 -1
  62. data/app/views/thredded/shared/nav/_standalone.html.erb +4 -4
  63. data/app/views/thredded/shared/nav/_standalone_profile.html.erb +3 -0
  64. data/app/views/thredded/theme_previews/show.html.erb +3 -17
  65. data/app/views/thredded/topics/_form.html.erb +3 -2
  66. data/app/views/thredded/topics/index.html.erb +0 -2
  67. data/app/views/thredded/topics/new.html.erb +0 -2
  68. data/app/views/thredded/topics/search.html.erb +20 -2
  69. data/app/views/thredded/topics/show.html.erb +1 -3
  70. data/config/locales/en.yml +9 -5
  71. data/config/locales/es.yml +10 -6
  72. data/config/locales/pl.yml +9 -5
  73. data/config/locales/pt-BR.yml +9 -5
  74. data/config/locales/ru.yml +18 -10
  75. data/config/routes.rb +2 -0
  76. data/db/migrate/20160329231848_create_thredded.rb +1 -1
  77. data/db/upgrade_migrations/20170420163138_upgrade_thredded_v0_11_to_v0_12.rb +25 -0
  78. data/lib/generators/thredded/install/templates/initializer.rb +4 -4
  79. data/lib/thredded/content_formatter.rb +17 -3
  80. data/lib/thredded/html_pipeline/kramdown_filter.rb +1 -1
  81. data/lib/thredded/version.rb +1 -1
  82. data/lib/thredded.rb +13 -10
  83. data/vendor/assets/javascripts/autosize.js +290 -0
  84. metadata +31 -18
  85. data/app/views/thredded/preferences/_header.html.erb +0 -1
@@ -82,6 +82,7 @@ pl:
82
82
  update_btn: Zaktualizuj post
83
83
  update_btn_submitting: :thredded.form.update_btn_submitting
84
84
  pending_moderation_notice: Twój post zostanie zamieszczony po zweryfikowaniu go przez moderatora.
85
+ quote_btn: Zacytować
85
86
  preferences:
86
87
  edit:
87
88
  page_title: :thredded.nav.settings
@@ -93,7 +94,6 @@ pl:
93
94
  follow_topics_on_mention:
94
95
  hint: 'Gdy ktoś w temacie wspomni o Tobie (np.: @sam) zaczniesz obserwować ten temat.'
95
96
  label: Obserwuj tematy, w których zostałeś wspomiany
96
- global_preferences_label: Ustawienia powiadomień
97
97
  messageboard_auto_follow_topics:
98
98
  hint: Automatycznie śledzić wszystkie nowe tematy na tym messageboard. To zastępuje odpowiednie ustawienie
99
99
  powyżej.
@@ -103,14 +103,15 @@ pl:
103
103
  label: :thredded.preferences.form.follow_topics_on_mention.label
104
104
  messageboard_notifications_for_followed_topics:
105
105
  label: :thredded.preferences.form.notifications_for_followed_topics.label
106
- messageboard_preferences_label_html: Notification Settings for <em>%{messageboard}</em>
107
106
  notifications_for_followed_topics:
108
107
  label: Powiadomienia z obserwowanych tematów
109
108
  notifications_for_private_topics:
110
109
  label: Powiadomienia z prywatnych wiadomości
111
110
  submit_btn: Zaktualizuj ustawienia
112
- title: Ustawienia
113
111
  update_btn_submitting: :thredded.form.update_btn_submitting
112
+ global_preferences_title: Ustawienia powiadomień
113
+ messageboard_preferences_nav_title: Ustawienia na tablicę
114
+ messageboard_preferences_title_html: Notification Settings for <em>%{messageboard}</em>
114
115
  updated_notice: Twoje ustawienia zostały zaktualizowane.
115
116
  private_posts:
116
117
  form:
@@ -172,9 +173,12 @@ pl:
172
173
  mark_as_unread: Oznacz jako nieprzeczytane stąd
173
174
  not_following: Nie obserwujesz tego tematu.
174
175
  search:
175
- no_results_message: 'Nie odnaleziono żadnych wyników dla frazy: %{query}'
176
+ no_results_in_messageboard_message_html: Nie odnaleziono żadnych wyników dla frazy <q>%{query}</q> w %{messageboard}
177
+ no_results_message_html: 'Nie odnaleziono żadnych wyników dla frazy: <q>%{query}</q>'
176
178
  page_title: Wyniki wyszukiwania
177
- results_message: 'Wyniki wyszukiwania dla: %{query}'
179
+ results_in_messageboard_message_html: Wyniki wyszukiwania dla <q>%{query}</q> w %{messageboard}
180
+ results_message_html: 'Wyniki wyszukiwania dla: <q>%{query}</q>'
181
+ search_in_all_messageboards_btn: Szukaj wszędzie
178
182
  started_by_html: Rozpoczęty %{time_ago} przez %{user}
179
183
  sticky:
180
184
  label: Przyklejony
@@ -82,6 +82,7 @@ pt-BR:
82
82
  update_btn: Atualizar Post
83
83
  update_btn_submitting: :thredded.form.update_btn_submitting
84
84
  pending_moderation_notice: O envio da mensagem será publicada quando foi revisado por um moderador.
85
+ quote_btn: Citar
85
86
  preferences:
86
87
  edit:
87
88
  page_title: :thredded.nav.settings
@@ -94,7 +95,6 @@ pt-BR:
94
95
  Quando alguém mencionar você através do seu usuário (ex.: @sam) você irá receber um e-mail com o conteúdo
95
96
  deste post.
96
97
  label: Siga os tópicos que são mencionados na
97
- global_preferences_label: Configurações Globais
98
98
  messageboard_auto_follow_topics:
99
99
  hint: siga automaticamente todos os novos tópicos neste messageboard. Isso substitui a respectiva definição
100
100
  acima.
@@ -106,14 +106,15 @@ pt-BR:
106
106
  label: :thredded.preferences.form.follow_topics_on_mention.label
107
107
  messageboard_notifications_for_followed_topics:
108
108
  label: :thredded.preferences.form.notifications_for_followed_topics.label
109
- messageboard_preferences_label_html: Configurações de Notificação para <em>%{messageboard}</em>
110
109
  notifications_for_followed_topics:
111
110
  label: Notificações para tópicos seguido
112
111
  notifications_for_private_topics:
113
112
  label: Notificações de mensagens privadas
114
113
  submit_btn: Atualizar Configurações
115
- title: Configurações
116
114
  update_btn_submitting: :thredded.form.update_btn_submitting
115
+ global_preferences_title: Configurações Globais
116
+ messageboard_preferences_nav_title: Configurações por Fórum de Mensagem
117
+ messageboard_preferences_title_html: Configurações de Notificação para <em>%{messageboard}</em>
117
118
  updated_notice: Suas configurações foram atualizadas.
118
119
  private_posts:
119
120
  form:
@@ -175,9 +176,12 @@ pt-BR:
175
176
  mark_as_unread: Marca não lida a partir daqui
176
177
  not_following: Você não está seguindo este tema.
177
178
  search:
178
- no_results_message: Nenhum resultado encontrado para sua busca - %{query}
179
+ no_results_in_messageboard_message_html: Não resultados para sua busca <q>%{query}</q> em %{messageboard}
180
+ no_results_message_html: Nenhum resultado encontrado para sua busca <q>%{query}</q>
179
181
  page_title: Tópicos dos Resultados da Busca
180
- results_message: Resultados de Busca para %{query}
182
+ results_in_messageboard_message_html: Resultados da pesquisa para <q>%{query}</q> em %{messageboard}
183
+ results_message_html: Resultados de busca para <q>%{query}</q>
184
+ search_in_all_messageboards_btn: Buscar em todos os lugares
181
185
  started_by_html: Iniciado %{time_ago} por %{user}
182
186
  sticky:
183
187
  label: Pegajoso
@@ -66,7 +66,7 @@ ru:
66
66
  moderation_pending: В ожидании
67
67
  moderation_users: Пользователи
68
68
  private_topics: Личное
69
- settings: Уведомления
69
+ settings: Настройки
70
70
  null_user_name: Пользователь удален
71
71
  posts:
72
72
  delete: Удалить пост
@@ -81,6 +81,7 @@ ru:
81
81
  update_btn: Обновить пост
82
82
  update_btn_submitting: :thredded.form.update_btn_submitting
83
83
  pending_moderation_notice: Ваш пост будет опубликован, когда будет одобрен модератором.
84
+ quote_btn: Цитировать
84
85
  preferences:
85
86
  edit:
86
87
  page_title: :thredded.nav.settings
@@ -92,7 +93,6 @@ ru:
92
93
  follow_topics_on_mention:
93
94
  hint: 'Когда кто-то упоминает вас на форуме (например: @sam), Вы будете наблюдать за этой темой.'
94
95
  label: Наблюдать за темой, в которой Вы упомянуты
95
- global_preferences_label: Настройки форума
96
96
  messageboard_auto_follow_topics:
97
97
  hint: Автоматически следовать все новые темы в этом ОБЪЯВЛЕНИЯ. Это отменяет соответствующую настройку
98
98
  выше.
@@ -102,14 +102,15 @@ ru:
102
102
  label: :thredded.preferences.form.follow_topics_on_mention.label
103
103
  messageboard_notifications_for_followed_topics:
104
104
  label: :thredded.preferences.form.notifications_for_followed_topics.label
105
- messageboard_preferences_label_html: Настройки уведомлений для <em>%{messageboard}</em>
106
105
  notifications_for_followed_topics:
107
106
  label: Уведомления для отслеживаемых тем
108
107
  notifications_for_private_topics:
109
108
  label: Уведомления для личных сообщений
110
109
  submit_btn: Сохранить настройки
111
- title: Настройки
112
110
  update_btn_submitting: :thredded.form.update_btn_submitting
111
+ global_preferences_title: Общие настройки
112
+ messageboard_preferences_nav_title: Настройки форума
113
+ messageboard_preferences_title_html: Настройки уведомлений для <em>%{messageboard}</em>
113
114
  updated_notice: Ваши настройки были обновлены.
114
115
  private_posts:
115
116
  form:
@@ -171,9 +172,12 @@ ru:
171
172
  mark_as_unread: Пометить как непрочитанное
172
173
  not_following: не отслеживаю
173
174
  search:
174
- no_results_message: Там нет никаких результатов для этой категории - %{query}
175
- page_title: Темы Результаты поиска
176
- results_message: Результаты поиска для %{query}
175
+ no_results_in_messageboard_message_html: Не найдо результатов по запросу <q>%{query}</q> в %{messageboard}
176
+ no_results_message_html: Не найдо результатов по запросу <q>%{query}</q>
177
+ page_title: Результаты поиска
178
+ results_in_messageboard_message_html: Результаты поиска для <q>%{query}</q> в %{messageboard}
179
+ results_message_html: Результаты поиска для <q>%{query}</q>
180
+ search_in_all_messageboards_btn: Искать везде
177
181
  started_by_html: Начат %{user} %{time_ago}
178
182
  sticky:
179
183
  label: Закрепить
@@ -185,12 +189,16 @@ ru:
185
189
  last_active_html: Последняя активность %{time_ago}
186
190
  posted_in_topic_html: Сделан пост в %{topic_link}
187
191
  posts_count:
188
- one: Сделан пост
189
- other: Сделано постов %{count} раз
192
+ few: Сделано %{count} поста
193
+ many: Сделано %{count} постов
194
+ one: Сделан %{count} пост
195
+ other: Сделано %{count} постов
190
196
  recent_activity: :thredded.recent_activity
191
197
  started_topic_html: Начато %{topic_link}
192
198
  started_topics_count:
193
- one: Начата тема
199
+ few: Начато %{count} темы
200
+ many: Начато %{count} тем
201
+ one: Начата %{count} тема
194
202
  other: Начато %{count} тем
195
203
  user_posted_in_topic_html: "%{user_link} сделал запись в %{topic_link}"
196
204
  user_since_html: Пользователь был %{time_ago}
data/config/routes.rb CHANGED
@@ -17,6 +17,7 @@ Thredded::Engine.routes.draw do # rubocop:disable Metrics/BlockLength
17
17
  post :preview, on: :new, controller: 'private_post_previews'
18
18
  resource :preview, only: [:update], controller: 'private_post_previews'
19
19
  member do
20
+ get 'quote'
20
21
  post 'mark_as_unread'
21
22
  end
22
23
  end
@@ -74,6 +75,7 @@ Thredded::Engine.routes.draw do # rubocop:disable Metrics/BlockLength
74
75
  post :preview, on: :new, controller: 'post_previews'
75
76
  resource :preview, only: [:update], controller: 'post_previews'
76
77
  member do
78
+ get 'quote'
77
79
  post 'mark_as_unread'
78
80
  end
79
81
  end
@@ -118,7 +118,7 @@ class CreateThredded < ActiveRecord::Migration
118
118
  order: { sticky: :desc, updated_at: :desc },
119
119
  name: :index_thredded_topics_for_display
120
120
  t.index [:hash_id], name: :index_thredded_topics_on_hash_id
121
- t.index [:messageboard_id, :slug], name: :index_thredded_topics_on_messageboard_id_and_slug, unique: true
121
+ t.index [:slug], name: :index_thredded_topics_on_slug, unique: true
122
122
  t.index [:messageboard_id], name: :index_thredded_topics_on_messageboard_id
123
123
  t.index [:user_id], name: :index_thredded_topics_on_user_id
124
124
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ class UpgradeThreddedV011ToV012 < ActiveRecord::Migration
3
+ def up
4
+ FriendlyId::Slug.transaction do
5
+ FriendlyId::Slug.where(sluggable_type: 'Thredded::Topic').where(
6
+ slug: FriendlyId::Slug.group(:slug).having('count(id) > 1').select(:slug)
7
+ ).group_by(&:slug).each_value do |slugs|
8
+ slugs.from(1).each(&:delete)
9
+ end
10
+ FriendlyId::Slug.where(sluggable_type: 'Thredded::Topic')
11
+ .update_all(scope: nil)
12
+ end
13
+ Thredded::Topic.all.find_each do |topic|
14
+ # re-generate the slug
15
+ topic.title_will_change!
16
+ topic.save!
17
+ end
18
+ remove_index :thredded_topics, name: :index_thredded_topics_on_messageboard_id_and_slug
19
+ add_index :thredded_topics, [:slug], name: :index_thredded_topics_on_slug, unique: true
20
+ end
21
+
22
+ def down
23
+ fail ActiveRecord::MigrationError::IrreversibleMigration
24
+ end
25
+ end
@@ -7,7 +7,7 @@
7
7
  # for your user class - change it here.
8
8
  Thredded.user_class = 'User'
9
9
 
10
- # User name column, used in @mention syntax and should be unique.
10
+ # User name column, used in @mention syntax and *must* be unique.
11
11
  # This is the column used to search for users' names if/when someone is @ mentioned.
12
12
  Thredded.user_name_column = :name
13
13
 
@@ -19,12 +19,12 @@ Thredded.user_name_column = :name
19
19
  # When linking to a user, Thredded will use this lambda to spit out
20
20
  # the path or url to your user. This lambda is evaluated in the view context.
21
21
  Thredded.user_path = lambda do |user|
22
- user_path = :"#{Thredded.user_class.name.underscore}_path"
22
+ user_path = :"#{Thredded.user_class_name.underscore}_path"
23
23
  main_app.respond_to?(user_path) ? main_app.send(user_path, user) : "/users/#{user.to_param}"
24
24
  end
25
25
 
26
26
  # This method is used by Thredded controllers and views to fetch the currently signed-in user
27
- Thredded.current_user_method = :"current_#{Thredded.user_class.name.underscore}"
27
+ Thredded.current_user_method = :"current_#{Thredded.user_class_name.underscore}"
28
28
 
29
29
  # User avatar URL. rb-gravatar gem is used by default:
30
30
  Thredded.avatar_url = ->(user) { Gravatar.src(user.email, 128, 'mm') }
@@ -83,7 +83,7 @@ Thredded.layout = 'thredded/application'
83
83
  # Thredded.slugifier = ->(input) { input.parameterize }
84
84
 
85
85
  # If your forum is in a language other than English, you might want to use the babosa gem instead
86
- # Thredded.slugifier = ->(input) { Babosa::Identifier.new(input).normalize.to_s }
86
+ # Thredded.slugifier = ->(input) { Babosa::Identifier.new(input).normalize.transliterate(:russian).to_s }
87
87
 
88
88
  # ==> Post Content Formatting
89
89
  # Customize the way Thredded handles post formatting.
@@ -6,7 +6,7 @@ module Thredded
6
6
  mattr_accessor :whitelist
7
7
 
8
8
  self.whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST.deep_merge(
9
- elements: HTML::Pipeline::SanitizationFilter::WHITELIST[:elements] + %w(iframe span figure figcaption),
9
+ elements: HTML::Pipeline::SanitizationFilter::WHITELIST[:elements] + %w(abbr iframe span figure figcaption),
10
10
  transformers: HTML::Pipeline::SanitizationFilter::WHITELIST[:transformers] + [
11
11
  lambda do |env|
12
12
  next unless env[:node_name] == 'a'
@@ -20,8 +20,11 @@ module Thredded
20
20
  ],
21
21
  attributes: {
22
22
  'a' => %w(href rel),
23
+ 'abbr' => %w(title),
23
24
  'span' => %w(class),
24
- 'div' => %w(class)
25
+ 'div' => %w(class),
26
+ :all => HTML::Pipeline::SanitizationFilter::WHITELIST[:attributes][:all] +
27
+ %w(aria-label aria-labelledby aria-hidden),
25
28
  }
26
29
  )
27
30
 
@@ -89,13 +92,24 @@ module Thredded
89
92
  # @param content [String]
90
93
  # @return [String] formatted and sanitized html-safe content.
91
94
  def format_content(content)
92
- pipeline = HTML::Pipeline.new(content_pipeline_filters, content_pipeline_options.merge(@pipeline_options))
95
+ pipeline = HTML::Pipeline.new(content_pipeline_filters, content_pipeline_options.deep_merge(@pipeline_options))
93
96
  result = pipeline.call(content, view_context: @view_context)
94
97
  # rubocop:disable Rails/OutputSafety
95
98
  result[:output].to_s.html_safe
96
99
  # rubocop:enable Rails/OutputSafety
97
100
  end
98
101
 
102
+ # @param content [String]
103
+ # @return [String] a quote containing the formatted content
104
+ def self.quote_content(content)
105
+ result = String.new(content)
106
+ result.gsub!(/^(?!$)/, '> ')
107
+ result.gsub!(/^$/, '>')
108
+ result << "\n" unless result.end_with?("\n")
109
+ result << "\n"
110
+ result
111
+ end
112
+
99
113
  protected
100
114
 
101
115
  # @return [Array<HTML::Pipeline::Filter]>]
@@ -29,7 +29,7 @@ module Thredded
29
29
  # Convert Markdown to HTML using the best available implementation
30
30
  # and convert into a DocumentFragment.
31
31
  def call
32
- result = Kramdown::Document.new(@text, self.class.options).to_html
32
+ result = Kramdown::Document.new(@text, self.class.options.deep_merge(context[:kramdown_options] || {})).to_html
33
33
  result.rstrip!
34
34
  result
35
35
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
- VERSION = '0.11.0'
3
+ VERSION = '0.12.0'
4
4
  end
data/lib/thredded.rb CHANGED
@@ -23,7 +23,6 @@ require 'thredded/html_pipeline/wrap_iframes_filter'
23
23
 
24
24
  # Asset compilation
25
25
  require 'autoprefixer-rails'
26
- require 'autosize/rails'
27
26
  require 'jquery/rails'
28
27
  require 'rails-timeago'
29
28
  require 'select2-rails'
@@ -52,7 +51,6 @@ module Thredded
52
51
  :layout,
53
52
  :messageboards_order,
54
53
  :routes_id_constraint,
55
- :user_class,
56
54
  :user_display_name_method,
57
55
  :user_name_column,
58
56
  :user_path
@@ -94,6 +92,9 @@ module Thredded
94
92
  mattr_accessor :auto_follow_when_posting_in_topic
95
93
  self.auto_follow_when_posting_in_topic = true
96
94
 
95
+ # @return [String] The name of the user class
96
+ mattr_reader :user_class_name
97
+
97
98
  self.active_user_threshold = 5.minutes
98
99
  self.admin_column = :admin
99
100
  self.avatar_url = ->(user) { Gravatar.src(user.email, 128, 'mm') }
@@ -138,17 +139,19 @@ module Thredded
138
139
  end
139
140
  end
140
141
 
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}"
146
+ end
147
+ @@user_class_name = user_class_name # rubocop:disable Style/ClassVars
148
+ end
149
+
141
150
  # @return [Class<Thredded::UserExtender>] the user class from the host application.
142
151
  def self.user_class
143
152
  # This is nil before the initializer is installed.
144
- return nil if @@user_class.nil?
145
- fail 'Please use a String instead of a Class' if @@user_class.is_a?(Class)
146
- fail "user_class must be a String, got #{@@user_class.inspect}" unless @@user_class.is_a?(String)
147
- begin
148
- Object.const_get(@@user_class)
149
- rescue NameError
150
- @@user_class.constantize
151
- end
153
+ return nil if @@user_class_name.nil?
154
+ @@user_class_name.constantize
152
155
  end
153
156
 
154
157
  # @param view_context [Object] context to execute the lambda in.
@@ -0,0 +1,290 @@
1
+ /*!
2
+ Autosize 3.0.20
3
+ license: MIT
4
+ http://www.jacklmoore.com/autosize
5
+ */
6
+ (function (global, factory) {
7
+ if (typeof define === 'function' && define.amd) {
8
+ define(['exports', 'module'], factory);
9
+ } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
10
+ factory(exports, module);
11
+ } else {
12
+ var mod = {
13
+ exports: {}
14
+ };
15
+ factory(mod.exports, mod);
16
+ global.autosize = mod.exports;
17
+ }
18
+ })(this, function (exports, module) {
19
+ 'use strict';
20
+
21
+ var map = typeof Map === "function" ? new Map() : (function () {
22
+ var keys = [];
23
+ var values = [];
24
+
25
+ return {
26
+ has: function has(key) {
27
+ return keys.indexOf(key) > -1;
28
+ },
29
+ get: function get(key) {
30
+ return values[keys.indexOf(key)];
31
+ },
32
+ set: function set(key, value) {
33
+ if (keys.indexOf(key) === -1) {
34
+ keys.push(key);
35
+ values.push(value);
36
+ }
37
+ },
38
+ 'delete': function _delete(key) {
39
+ var index = keys.indexOf(key);
40
+ if (index > -1) {
41
+ keys.splice(index, 1);
42
+ values.splice(index, 1);
43
+ }
44
+ }
45
+ };
46
+ })();
47
+
48
+ var createEvent = function createEvent(name) {
49
+ return new Event(name, { bubbles: true });
50
+ };
51
+ try {
52
+ new Event('test');
53
+ } catch (e) {
54
+ // IE does not support `new Event()`
55
+ createEvent = function (name) {
56
+ var evt = document.createEvent('Event');
57
+ evt.initEvent(name, true, false);
58
+ return evt;
59
+ };
60
+ }
61
+
62
+ function assign(ta) {
63
+ if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return;
64
+
65
+ var heightOffset = null;
66
+ var clientWidth = ta.clientWidth;
67
+ var cachedHeight = null;
68
+
69
+ function init() {
70
+ var style = window.getComputedStyle(ta, null);
71
+
72
+ if (style.resize === 'vertical') {
73
+ ta.style.resize = 'none';
74
+ } else if (style.resize === 'both') {
75
+ ta.style.resize = 'horizontal';
76
+ }
77
+
78
+ if (style.boxSizing === 'content-box') {
79
+ heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
80
+ } else {
81
+ heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
82
+ }
83
+ // Fix when a textarea is not on document body and heightOffset is Not a Number
84
+ if (isNaN(heightOffset)) {
85
+ heightOffset = 0;
86
+ }
87
+
88
+ update();
89
+ }
90
+
91
+ function changeOverflow(value) {
92
+ {
93
+ // Chrome/Safari-specific fix:
94
+ // When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
95
+ // made available by removing the scrollbar. The following forces the necessary text reflow.
96
+ var width = ta.style.width;
97
+ ta.style.width = '0px';
98
+ // Force reflow:
99
+ /* jshint ignore:start */
100
+ ta.offsetWidth;
101
+ /* jshint ignore:end */
102
+ ta.style.width = width;
103
+ }
104
+
105
+ ta.style.overflowY = value;
106
+ }
107
+
108
+ function getParentOverflows(el) {
109
+ var arr = [];
110
+
111
+ while (el && el.parentNode && el.parentNode instanceof Element) {
112
+ if (el.parentNode.scrollTop) {
113
+ arr.push({
114
+ node: el.parentNode,
115
+ scrollTop: el.parentNode.scrollTop
116
+ });
117
+ }
118
+ el = el.parentNode;
119
+ }
120
+
121
+ return arr;
122
+ }
123
+
124
+ function resize() {
125
+ var originalHeight = ta.style.height;
126
+ var overflows = getParentOverflows(ta);
127
+ var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240)
128
+
129
+ ta.style.height = 'auto';
130
+
131
+ var endHeight = ta.scrollHeight + heightOffset;
132
+
133
+ if (ta.scrollHeight === 0) {
134
+ // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
135
+ ta.style.height = originalHeight;
136
+ return;
137
+ }
138
+
139
+ ta.style.height = endHeight + 'px';
140
+
141
+ // used to check if an update is actually necessary on window.resize
142
+ clientWidth = ta.clientWidth;
143
+
144
+ // prevents scroll-position jumping
145
+ overflows.forEach(function (el) {
146
+ el.node.scrollTop = el.scrollTop;
147
+ });
148
+
149
+ if (docTop) {
150
+ document.documentElement.scrollTop = docTop;
151
+ }
152
+ }
153
+
154
+ function update() {
155
+ resize();
156
+
157
+ var styleHeight = Math.round(parseFloat(ta.style.height));
158
+ var computed = window.getComputedStyle(ta, null);
159
+ var actualHeight = Math.round(parseFloat(computed.height));
160
+
161
+ // The actual height not matching the style height (set via the resize method) indicates that
162
+ // the max-height has been exceeded, in which case the overflow should be set to visible.
163
+ if (actualHeight !== styleHeight) {
164
+ if (computed.overflowY !== 'visible') {
165
+ changeOverflow('visible');
166
+ resize();
167
+ actualHeight = Math.round(parseFloat(window.getComputedStyle(ta, null).height));
168
+ }
169
+ } else {
170
+ // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands.
171
+ if (computed.overflowY !== 'hidden') {
172
+ changeOverflow('hidden');
173
+ resize();
174
+ actualHeight = Math.round(parseFloat(window.getComputedStyle(ta, null).height));
175
+ }
176
+ }
177
+
178
+ if (cachedHeight !== actualHeight) {
179
+ cachedHeight = actualHeight;
180
+ var evt = createEvent('autosize:resized');
181
+ try {
182
+ ta.dispatchEvent(evt);
183
+ } catch (err) {
184
+ // Firefox will throw an error on dispatchEvent for a detached element
185
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=889376
186
+ }
187
+ }
188
+ }
189
+
190
+ var pageResize = function pageResize() {
191
+ if (ta.clientWidth !== clientWidth) {
192
+ update();
193
+ }
194
+ };
195
+
196
+ var destroy = (function (style) {
197
+ window.removeEventListener('resize', pageResize, false);
198
+ ta.removeEventListener('input', update, false);
199
+ ta.removeEventListener('keyup', update, false);
200
+ ta.removeEventListener('autosize:destroy', destroy, false);
201
+ ta.removeEventListener('autosize:update', update, false);
202
+
203
+ Object.keys(style).forEach(function (key) {
204
+ ta.style[key] = style[key];
205
+ });
206
+
207
+ map['delete'](ta);
208
+ }).bind(ta, {
209
+ height: ta.style.height,
210
+ resize: ta.style.resize,
211
+ overflowY: ta.style.overflowY,
212
+ overflowX: ta.style.overflowX,
213
+ wordWrap: ta.style.wordWrap
214
+ });
215
+
216
+ ta.addEventListener('autosize:destroy', destroy, false);
217
+
218
+ // IE9 does not fire onpropertychange or oninput for deletions,
219
+ // so binding to onkeyup to catch most of those events.
220
+ // There is no way that I know of to detect something like 'cut' in IE9.
221
+ if ('onpropertychange' in ta && 'oninput' in ta) {
222
+ ta.addEventListener('keyup', update, false);
223
+ }
224
+
225
+ window.addEventListener('resize', pageResize, false);
226
+ ta.addEventListener('input', update, false);
227
+ ta.addEventListener('autosize:update', update, false);
228
+ ta.style.overflowX = 'hidden';
229
+ ta.style.wordWrap = 'break-word';
230
+
231
+ map.set(ta, {
232
+ destroy: destroy,
233
+ update: update
234
+ });
235
+
236
+ init();
237
+ }
238
+
239
+ function destroy(ta) {
240
+ var methods = map.get(ta);
241
+ if (methods) {
242
+ methods.destroy();
243
+ }
244
+ }
245
+
246
+ function update(ta) {
247
+ var methods = map.get(ta);
248
+ if (methods) {
249
+ methods.update();
250
+ }
251
+ }
252
+
253
+ var autosize = null;
254
+
255
+ // Do nothing in Node.js environment and IE8 (or lower)
256
+ if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
257
+ autosize = function (el) {
258
+ return el;
259
+ };
260
+ autosize.destroy = function (el) {
261
+ return el;
262
+ };
263
+ autosize.update = function (el) {
264
+ return el;
265
+ };
266
+ } else {
267
+ autosize = function (el, options) {
268
+ if (el) {
269
+ Array.prototype.forEach.call(el.length ? el : [el], function (x) {
270
+ return assign(x, options);
271
+ });
272
+ }
273
+ return el;
274
+ };
275
+ autosize.destroy = function (el) {
276
+ if (el) {
277
+ Array.prototype.forEach.call(el.length ? el : [el], destroy);
278
+ }
279
+ return el;
280
+ };
281
+ autosize.update = function (el) {
282
+ if (el) {
283
+ Array.prototype.forEach.call(el.length ? el : [el], update);
284
+ }
285
+ return el;
286
+ };
287
+ }
288
+
289
+ module.exports = autosize;
290
+ });