thredded 0.16.15 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
![nyancat](https://storage.googleapis.com/glebm-stuff/nyancat.gif)
|
18
|
+
![nyancat](https://storage.googleapis.com/glebm-stuff/nyancat.gif)
|
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
|