thredded 0.16.15 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +35 -23
- data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -1
- data/app/commands/thredded/create_messageboard.rb +2 -2
- data/app/controllers/concerns/thredded/render_preview.rb +6 -0
- data/app/controllers/thredded/application_controller.rb +5 -10
- data/app/controllers/thredded/topics_controller.rb +0 -6
- data/app/forms/thredded/private_topic_form.rb +1 -1
- data/app/helpers/thredded/application_helper.rb +3 -3
- data/app/helpers/thredded/urls_helper.rb +1 -1
- data/app/mailer_previews/thredded/base_mailer_preview.rb +5 -5
- data/app/models/concerns/thredded/friendly_id_reserved_words_and_pagination.rb +1 -1
- data/app/models/concerns/thredded/notifier_preference.rb +1 -1
- data/app/models/concerns/thredded/post_common.rb +8 -7
- data/app/models/concerns/thredded/topic_common.rb +1 -1
- data/app/models/concerns/thredded/user_topic_read_state_common.rb +5 -5
- data/app/models/thredded/messageboard.rb +3 -4
- data/app/models/thredded/notifications_for_followed_topics.rb +1 -1
- data/app/models/thredded/post.rb +3 -2
- data/app/models/thredded/post_moderation_record.rb +12 -13
- data/app/models/thredded/private_post.rb +2 -2
- data/app/models/thredded/private_topic.rb +4 -2
- data/app/models/thredded/topic.rb +2 -2
- data/app/models/thredded/user_detail.rb +1 -1
- data/app/models/thredded/user_extender.rb +1 -1
- data/app/view_models/thredded/post_view.rb +1 -0
- data/app/view_models/thredded/posts_page_view.rb +10 -9
- data/app/view_models/thredded/private_topics_page_view.rb +10 -6
- data/app/view_models/thredded/topic_posts_page_view.rb +16 -3
- data/app/view_models/thredded/topics_page_view.rb +9 -6
- data/app/views/layouts/thredded/application.html.erb +2 -2
- data/app/views/thredded/messageboards/_form.html.erb +3 -3
- data/app/views/thredded/moderation/_post_moderation_record.html.erb +3 -3
- data/app/views/thredded/posts_common/_content.html.erb +1 -1
- data/app/views/thredded/posts_common/actions/_quote.html.erb +1 -1
- data/app/views/thredded/shared/_page.html.erb +1 -1
- data/app/views/thredded/shared/nav/_standalone.html.erb +2 -2
- data/bin/create_migration_fixture +23 -0
- data/bin/rubocop +11 -0
- data/config/i18n-tasks.yml +6 -1
- data/config/locales/de.yml +2 -0
- data/config/locales/en.yml +3 -1
- data/config/locales/es.yml +2 -0
- data/config/locales/fr.yml +2 -0
- data/config/locales/it.yml +4 -2
- data/config/locales/pl.yml +2 -0
- data/config/locales/pt-BR.yml +2 -0
- data/config/locales/ru.yml +2 -0
- data/config/locales/zh-CN.yml +2 -0
- data/db/upgrade_migrations/20161019150201_upgrade_v0_7_to_v0_8.rb +5 -5
- data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +1 -1
- data/lib/generators/thredded/install/templates/initializer.rb +3 -3
- data/lib/thredded/arel_compat.rb +1 -42
- data/lib/thredded/base_migration.rb +2 -2
- data/lib/thredded/collection_to_strings_with_cache_renderer.rb +91 -28
- data/lib/thredded/compat.rb +35 -0
- data/lib/thredded/content_formatter.rb +30 -8
- data/lib/thredded/database_seeder.rb +11 -4
- data/lib/thredded/db_tools.rb +3 -5
- data/lib/thredded/email_transformer.rb +2 -2
- data/lib/thredded/engine.rb +1 -1
- data/lib/thredded/formatting_demo_content.rb +21 -21
- data/lib/thredded/html_pipeline/at_mention_filter.rb +1 -1
- data/lib/thredded/html_pipeline/onebox_filter.rb +3 -3
- data/lib/thredded/html_pipeline/utils.rb +1 -1
- data/lib/thredded/version.rb +1 -1
- data/lib/thredded.rb +3 -23
- metadata +56 -29
@@ -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,
|
27
|
+
ActiveSupport::Notifications.instrument(:collection,
|
28
|
+
identifier: template.identifier,
|
29
|
+
count: collection.size) do |instrumentation_payload|
|
28
30
|
return [] if collection.blank?
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
48
|
-
[
|
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
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
@@ -4,8 +4,11 @@ module Thredded
|
|
4
4
|
# Generates HTML from content source.
|
5
5
|
class ContentFormatter
|
6
6
|
class << self
|
7
|
-
# Sanitization
|
8
|
-
attr_accessor :
|
7
|
+
# Sanitization allowlist options.
|
8
|
+
attr_accessor :allowlist
|
9
|
+
|
10
|
+
# TODO: v2.0: drop alias and just use allowlist
|
11
|
+
alias_attribute :whitelist, :allowlist
|
9
12
|
|
10
13
|
# Filters that run before processing the markup.
|
11
14
|
# input: markup, output: markup.
|
@@ -26,16 +29,29 @@ module Thredded
|
|
26
29
|
# Filters that run after sanitization
|
27
30
|
# input: sanitized html, output: html
|
28
31
|
attr_accessor :after_sanitization_filters
|
32
|
+
|
33
|
+
# TODO: v1.1: only allow html-pipeline >= 2.14.1 and drop this
|
34
|
+
def sanitization_filter_uses_allowlist?
|
35
|
+
defined?(HTML::Pipeline::SanitizationFilter::ALLOWLIST)
|
36
|
+
end
|
37
|
+
|
38
|
+
def sanitization_filter_allowlist_config
|
39
|
+
if sanitization_filter_uses_allowlist?
|
40
|
+
HTML::Pipeline::SanitizationFilter::ALLOWLIST
|
41
|
+
else
|
42
|
+
HTML::Pipeline::SanitizationFilter::WHITELIST
|
43
|
+
end
|
44
|
+
end
|
29
45
|
end
|
30
46
|
|
31
|
-
self.
|
32
|
-
elements:
|
33
|
-
transformers:
|
47
|
+
self.allowlist = sanitization_filter_allowlist_config.deep_merge(
|
48
|
+
elements: sanitization_filter_allowlist_config[:elements] + %w[abbr iframe span figure figcaption],
|
49
|
+
transformers: sanitization_filter_allowlist_config[:transformers] + [
|
34
50
|
->(env) {
|
35
51
|
next unless env[:node_name] == 'a'
|
36
52
|
a_tag = env[:node]
|
37
53
|
a_tag['href'] ||= '#'
|
38
|
-
if
|
54
|
+
if %r{^(?:[a-z]+:)?//}.match?(a_tag['href'])
|
39
55
|
a_tag['target'] = '_blank'
|
40
56
|
a_tag['rel'] = 'nofollow noopener'
|
41
57
|
end
|
@@ -49,7 +65,7 @@ module Thredded
|
|
49
65
|
'img' => %w[src longdesc class],
|
50
66
|
'th' => %w[style],
|
51
67
|
'td' => %w[style],
|
52
|
-
:all =>
|
68
|
+
:all => sanitization_filter_allowlist_config[:attributes][:all] +
|
53
69
|
%w[aria-expanded aria-label aria-labelledby aria-live aria-hidden aria-pressed role],
|
54
70
|
},
|
55
71
|
css: {
|
@@ -134,9 +150,15 @@ module Thredded
|
|
134
150
|
|
135
151
|
# @return [Hash] options for HTML::Pipeline.new
|
136
152
|
def content_pipeline_options
|
153
|
+
option = if self.class.sanitization_filter_uses_allowlist?
|
154
|
+
:allowlist
|
155
|
+
else
|
156
|
+
# TODO: v1.1: only allow html-pipeline >= 2.14.1 and drop this
|
157
|
+
:whitelist
|
158
|
+
end
|
137
159
|
{
|
138
160
|
asset_root: Rails.application.config.action_controller.asset_host || '',
|
139
|
-
|
161
|
+
option => ContentFormatter.allowlist
|
140
162
|
}
|
141
163
|
end
|
142
164
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'factory_bot'
|
4
|
-
require_relative '../../spec/support/
|
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)
|
data/lib/thredded/db_tools.rb
CHANGED
@@ -10,12 +10,10 @@ module Thredded
|
|
10
10
|
verbose_was = ActiveRecord::Migration.verbose
|
11
11
|
ActiveRecord::Migration.verbose = !quiet
|
12
12
|
migrate =
|
13
|
-
if
|
13
|
+
if Thredded::Compat.rails_gte_60?
|
14
14
|
-> { ActiveRecord::MigrationContext.new(paths, ActiveRecord::SchemaMigration).migrate(nil, &filter) }
|
15
|
-
|
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)
|
@@ -61,7 +59,7 @@ module Thredded
|
|
61
59
|
silence_active_record do
|
62
60
|
ActiveRecord::Base.transaction do
|
63
61
|
statements.each do |statement|
|
64
|
-
connection.execute(statement) unless
|
62
|
+
connection.execute(statement) unless /(BEGIN TRANSACTION|COMMIT)/.match?(statement)
|
65
63
|
end
|
66
64
|
end
|
67
65
|
end
|
@@ -17,8 +17,8 @@ module Thredded
|
|
17
17
|
end
|
18
18
|
@transformers = [Onebox, Spoiler]
|
19
19
|
|
20
|
-
# @param
|
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
|
data/lib/thredded/engine.rb
CHANGED
@@ -12,7 +12,7 @@ module Thredded
|
|
12
12
|
|
13
13
|
config.to_prepare do
|
14
14
|
Thredded::AllViewHooks.reset_instance!
|
15
|
-
Thredded.user_class
|
15
|
+
Thredded.user_class&.send(:include, Thredded::UserExtender)
|
16
16
|
end
|
17
17
|
|
18
18
|
initializer 'thredded.setup_assets' do
|
@@ -7,35 +7,35 @@ module Thredded
|
|
7
7
|
attr_accessor :parts
|
8
8
|
end
|
9
9
|
self.parts = [
|
10
|
-
|
11
|
-
#### Spoilers
|
10
|
+
<<~'MARKDOWN',
|
11
|
+
#### Spoilers
|
12
12
|
|
13
|
-
Use `<spoiler></spoiler>` tags to create spoiler boxes like this one:
|
13
|
+
Use `<spoiler></spoiler>` tags to create spoiler boxes like this one:
|
14
14
|
|
15
|
-
<spoiler>
|
16
|
-
Harry Potter books are better than the movies.
|
15
|
+
<spoiler>
|
16
|
+
Harry Potter books are better than the movies.
|
17
17
|
|
18
|
-

|
18
|
+

|
19
19
|
|
20
|
-
https://www.youtube.com/watch?v=5lBBUPVuusM
|
21
|
-
</spoiler>
|
20
|
+
https://www.youtube.com/watch?v=5lBBUPVuusM
|
21
|
+
</spoiler>
|
22
22
|
|
23
|
-
#### Oneboxes
|
23
|
+
#### Oneboxes
|
24
24
|
|
25
|
-
URLs of supported resources are replaced with boxes like these:
|
25
|
+
URLs of supported resources are replaced with boxes like these:
|
26
26
|
|
27
|
-
**Twitter** `https://twitter.com/thredded/status/838824533477982209`:
|
28
|
-
https://twitter.com/thredded/status/838824533477982209
|
29
|
-
**StackExchange** `http://codegolf.stackexchange.com/questions/45701`:
|
30
|
-
http://codegolf.stackexchange.com/questions/45701
|
31
|
-
**Amazon** `https://www.amazon.co.uk/dp/0521797071`:
|
32
|
-
https://www.amazon.co.uk/dp/0521797071
|
33
|
-
**YouTube** `https://www.youtube.com/watch?v=1QP7elXwpLw`:
|
34
|
-
https://www.youtube.com/watch?v=1QP7elXwpLw
|
35
|
-
**Google Maps** `https://goo.gl/maps/R6nj3Qwf2LR2`:
|
36
|
-
https://goo.gl/maps/R6nj3Qwf2LR2
|
27
|
+
**Twitter** `https://twitter.com/thredded/status/838824533477982209`:
|
28
|
+
https://twitter.com/thredded/status/838824533477982209
|
29
|
+
**StackExchange** `http://codegolf.stackexchange.com/questions/45701`:
|
30
|
+
http://codegolf.stackexchange.com/questions/45701
|
31
|
+
**Amazon** `https://www.amazon.co.uk/dp/0521797071`:
|
32
|
+
https://www.amazon.co.uk/dp/0521797071
|
33
|
+
**YouTube** `https://www.youtube.com/watch?v=1QP7elXwpLw`:
|
34
|
+
https://www.youtube.com/watch?v=1QP7elXwpLw
|
35
|
+
**Google Maps** `https://goo.gl/maps/R6nj3Qwf2LR2`:
|
36
|
+
https://goo.gl/maps/R6nj3Qwf2LR2
|
37
37
|
|
38
|
-
Many more resources are [supported](https://github.com/discourse/onebox/tree/
|
38
|
+
Many more resources are [supported](https://github.com/discourse/onebox/tree/main/lib/onebox/engine). Powered by the [onebox](https://github.com/discourse/onebox) library.
|
39
39
|
MARKDOWN
|
40
40
|
]
|
41
41
|
end
|
@@ -47,7 +47,7 @@ module Thredded
|
|
47
47
|
return if names.blank? || @users_provider.nil?
|
48
48
|
@users_provider.call(names, @users_provider_scope).each do |user|
|
49
49
|
name = user.send(Thredded.user_name_column)
|
50
|
-
maybe_quoted_name =
|
50
|
+
maybe_quoted_name = /[., ()]/.match?(name) ? %("#{name}") : name
|
51
51
|
url = Thredded.user_path(@view_context, user)
|
52
52
|
text_node_html.gsub!(
|
53
53
|
/(^|[\s>])(@#{Regexp.escape maybe_quoted_name})([^a-z\d]|$)/i,
|
@@ -24,7 +24,7 @@ module Thredded
|
|
24
24
|
next unless env[:node_name] == 'a'
|
25
25
|
a_tag = env[:node]
|
26
26
|
a_tag['href'] ||= '#'
|
27
|
-
if
|
27
|
+
if %r{^(?:[a-z]+:)?//}.match?(a_tag['href'])
|
28
28
|
a_tag['target'] = '_blank'
|
29
29
|
a_tag['rel'] = 'nofollow noopener'
|
30
30
|
else
|
@@ -68,9 +68,9 @@ module Thredded
|
|
68
68
|
|
69
69
|
def render_onebox(url)
|
70
70
|
preview = Onebox.preview(url, onebox_options(url))
|
71
|
-
if context[:onebox_placeholders]
|
71
|
+
if context[:onebox_placeholders] && (placeholder_html = preview.placeholder_html.presence)
|
72
72
|
%(<p><a href="#{ERB::Util.html_escape(url)}" target="_blank" rel="nofollow noopener">) \
|
73
|
-
"#{
|
73
|
+
"#{placeholder_html}</a></p>"
|
74
74
|
else
|
75
75
|
preview.to_s.strip
|
76
76
|
end
|
data/lib/thredded/version.rb
CHANGED
data/lib/thredded.rb
CHANGED
@@ -9,7 +9,7 @@ require 'html/pipeline'
|
|
9
9
|
require 'html/pipeline/sanitization_filter'
|
10
10
|
require 'rinku'
|
11
11
|
require 'kaminari'
|
12
|
-
require '
|
12
|
+
require 'rails_gravatar'
|
13
13
|
require 'active_job'
|
14
14
|
require 'inline_svg'
|
15
15
|
|
@@ -32,6 +32,7 @@ require 'sprockets/es6'
|
|
32
32
|
require 'sassc-rails'
|
33
33
|
|
34
34
|
require 'thredded/version'
|
35
|
+
require 'thredded/compat'
|
35
36
|
require 'thredded/engine'
|
36
37
|
require 'thredded/errors'
|
37
38
|
|
@@ -48,15 +49,6 @@ require 'thredded/collection_to_strings_with_cache_renderer'
|
|
48
49
|
|
49
50
|
require 'thredded/webpack_assets'
|
50
51
|
|
51
|
-
if Rails::VERSION::MAJOR < 5
|
52
|
-
begin
|
53
|
-
require 'where-or'
|
54
|
-
rescue LoadError
|
55
|
-
$stderr.puts "\nthredded: Please add gem 'where-or' to your Gemfile"
|
56
|
-
exit 1 # rubocop:disable Rails/Exit
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
52
|
module Thredded # rubocop:disable Metrics/ModuleLength
|
61
53
|
class << self
|
62
54
|
#== User
|
@@ -252,22 +244,10 @@ module Thredded # rubocop:disable Metrics/ModuleLength
|
|
252
244
|
.includes(:postable)
|
253
245
|
)
|
254
246
|
end
|
255
|
-
|
256
|
-
# @api private
|
257
|
-
def rails_gte_51?
|
258
|
-
@rails_gte_51 = (Rails.gem_version >= Gem::Version.new('5.1.0')) if @rails_gte_51.nil?
|
259
|
-
@rails_gte_51
|
260
|
-
end
|
261
|
-
|
262
|
-
# @api private
|
263
|
-
def rails_supports_csp_nonce?
|
264
|
-
@rails_supports_csp_nonce = (Rails.gem_version >= Gem::Version.new('5.2.0')) if @rails_supports_csp_nonce.nil?
|
265
|
-
@rails_supports_csp_nonce
|
266
|
-
end
|
267
247
|
end
|
268
248
|
|
269
249
|
self.user_name_column = :name
|
270
|
-
self.avatar_url = ->(user) {
|
250
|
+
self.avatar_url = ->(user) { RailsGravatar.src(user.email, 156, 'mm') }
|
271
251
|
self.admin_column = :admin
|
272
252
|
|
273
253
|
self.content_visible_while_pending_moderation = true
|