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.
- checksums.yaml +4 -4
- data/README.md +69 -23
- data/app/assets/javascripts/thredded/components/post_form.es6 +4 -4
- data/app/assets/javascripts/thredded/components/quote_post.es6 +45 -0
- data/app/assets/javascripts/thredded/components/topic_form.es6 +4 -4
- data/app/assets/javascripts/thredded/core/on_page_load.es6 +8 -0
- data/app/assets/javascripts/thredded/dependencies.js +1 -1
- data/app/assets/stylesheets/thredded/_base.scss +1 -0
- data/app/assets/stylesheets/thredded/_email.scss +2 -3
- data/app/assets/stylesheets/thredded/base/_dropdown-menu.scss +83 -0
- data/app/assets/stylesheets/thredded/components/_post.scss +5 -60
- data/app/assets/stylesheets/thredded/components/_preferences.scss +17 -4
- data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +4 -1
- data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +31 -6
- data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +1 -0
- data/app/controllers/concerns/thredded/new_post_params.rb +20 -0
- data/app/controllers/concerns/thredded/new_private_post_params.rb +20 -0
- data/app/controllers/thredded/application_controller.rb +6 -0
- data/app/controllers/thredded/posts_controller.rb +28 -13
- data/app/controllers/thredded/preferences_controller.rb +1 -0
- data/app/controllers/thredded/private_posts_controller.rb +30 -12
- data/app/controllers/thredded/private_topics_controller.rb +11 -1
- data/app/controllers/thredded/theme_previews_controller.rb +4 -2
- data/app/controllers/thredded/topics_controller.rb +36 -3
- data/app/forms/thredded/post_form.rb +52 -0
- data/app/forms/thredded/private_post_form.rb +48 -0
- data/app/forms/thredded/private_topic_form.rb +8 -0
- data/app/forms/thredded/topic_form.rb +8 -0
- data/app/forms/thredded/user_preferences_form.rb +7 -1
- data/app/helpers/thredded/urls_helper.rb +18 -0
- data/app/models/thredded/topic.rb +3 -3
- data/app/policies/thredded/post_policy.rb +1 -1
- data/app/policies/thredded/private_topic_policy.rb +1 -1
- data/app/view_hooks/thredded/all_view_hooks.rb +3 -0
- data/app/view_models/thredded/messageboard_group_view.rb +1 -1
- data/app/view_models/thredded/post_view.rb +19 -3
- data/app/view_models/thredded/private_topic_view.rb +0 -4
- data/app/view_models/thredded/topic_view.rb +0 -4
- data/app/views/thredded/moderation/_users_search_form.html.erb +1 -1
- data/app/views/thredded/moderation/user.html.erb +13 -11
- data/app/views/thredded/moderation/users.html.erb +1 -0
- data/app/views/thredded/posts/_form.html.erb +0 -2
- data/app/views/thredded/posts/edit.html.erb +2 -5
- data/app/views/thredded/posts/new.html.erb +15 -0
- data/app/views/thredded/posts_common/_actions.html.erb +3 -0
- data/app/views/thredded/posts_common/_form.html.erb +5 -3
- data/app/views/thredded/posts_common/_header.html.erb +3 -1
- data/app/views/thredded/posts_common/actions/_quote.html.erb +4 -0
- data/app/views/thredded/preferences/_form.html.erb +3 -5
- data/app/views/thredded/preferences/_messageboards_nav.html.erb +8 -0
- data/app/views/thredded/preferences/_messageboards_nav_item.html.erb +2 -0
- data/app/views/thredded/preferences/edit.html.erb +13 -3
- data/app/views/thredded/private_posts/_form.html.erb +0 -2
- data/app/views/thredded/private_posts/edit.html.erb +2 -4
- data/app/views/thredded/private_posts/new.html.erb +11 -0
- data/app/views/thredded/private_topics/_form.html.erb +2 -2
- data/app/views/thredded/private_topics/index.html.erb +1 -2
- data/app/views/thredded/private_topics/new.html.erb +0 -1
- data/app/views/thredded/private_topics/show.html.erb +5 -3
- data/app/views/thredded/shared/_nav.html.erb +1 -7
- data/app/views/thredded/shared/nav/_notification_preferences.html.erb +1 -1
- data/app/views/thredded/shared/nav/_standalone.html.erb +4 -4
- data/app/views/thredded/shared/nav/_standalone_profile.html.erb +3 -0
- data/app/views/thredded/theme_previews/show.html.erb +3 -17
- data/app/views/thredded/topics/_form.html.erb +3 -2
- data/app/views/thredded/topics/index.html.erb +0 -2
- data/app/views/thredded/topics/new.html.erb +0 -2
- data/app/views/thredded/topics/search.html.erb +20 -2
- data/app/views/thredded/topics/show.html.erb +1 -3
- data/config/locales/en.yml +9 -5
- data/config/locales/es.yml +10 -6
- data/config/locales/pl.yml +9 -5
- data/config/locales/pt-BR.yml +9 -5
- data/config/locales/ru.yml +18 -10
- data/config/routes.rb +2 -0
- data/db/migrate/20160329231848_create_thredded.rb +1 -1
- data/db/upgrade_migrations/20170420163138_upgrade_thredded_v0_11_to_v0_12.rb +25 -0
- data/lib/generators/thredded/install/templates/initializer.rb +4 -4
- data/lib/thredded/content_formatter.rb +17 -3
- data/lib/thredded/html_pipeline/kramdown_filter.rb +1 -1
- data/lib/thredded/version.rb +1 -1
- data/lib/thredded.rb +13 -10
- data/vendor/assets/javascripts/autosize.js +290 -0
- metadata +31 -18
- data/app/views/thredded/preferences/_header.html.erb +0 -1
data/config/locales/pl.yml
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
data/config/locales/pt-BR.yml
CHANGED
|
@@ -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
|
-
|
|
179
|
+
no_results_in_messageboard_message_html: Não há 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
|
-
|
|
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
|
data/config/locales/ru.yml
CHANGED
|
@@ -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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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 [:
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
data/lib/thredded/version.rb
CHANGED
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 @@
|
|
145
|
-
|
|
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
|
+
});
|