thredded 0.16.15 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -23
  3. data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -1
  4. data/app/commands/thredded/create_messageboard.rb +2 -2
  5. data/app/controllers/concerns/thredded/render_preview.rb +6 -0
  6. data/app/controllers/thredded/application_controller.rb +5 -10
  7. data/app/controllers/thredded/topics_controller.rb +0 -6
  8. data/app/forms/thredded/private_topic_form.rb +1 -1
  9. data/app/helpers/thredded/application_helper.rb +3 -3
  10. data/app/helpers/thredded/urls_helper.rb +1 -1
  11. data/app/mailer_previews/thredded/base_mailer_preview.rb +5 -5
  12. data/app/models/concerns/thredded/friendly_id_reserved_words_and_pagination.rb +1 -1
  13. data/app/models/concerns/thredded/notifier_preference.rb +1 -1
  14. data/app/models/concerns/thredded/post_common.rb +8 -7
  15. data/app/models/concerns/thredded/topic_common.rb +1 -1
  16. data/app/models/concerns/thredded/user_topic_read_state_common.rb +5 -5
  17. data/app/models/thredded/messageboard.rb +3 -4
  18. data/app/models/thredded/notifications_for_followed_topics.rb +1 -1
  19. data/app/models/thredded/post.rb +3 -2
  20. data/app/models/thredded/post_moderation_record.rb +12 -13
  21. data/app/models/thredded/private_post.rb +2 -2
  22. data/app/models/thredded/private_topic.rb +4 -2
  23. data/app/models/thredded/topic.rb +2 -2
  24. data/app/models/thredded/user_detail.rb +1 -1
  25. data/app/models/thredded/user_extender.rb +1 -1
  26. data/app/view_models/thredded/post_view.rb +1 -0
  27. data/app/view_models/thredded/posts_page_view.rb +10 -9
  28. data/app/view_models/thredded/private_topics_page_view.rb +10 -6
  29. data/app/view_models/thredded/topic_posts_page_view.rb +16 -3
  30. data/app/view_models/thredded/topics_page_view.rb +9 -6
  31. data/app/views/layouts/thredded/application.html.erb +2 -2
  32. data/app/views/thredded/messageboards/_form.html.erb +3 -3
  33. data/app/views/thredded/moderation/_post_moderation_record.html.erb +3 -3
  34. data/app/views/thredded/posts_common/_content.html.erb +1 -1
  35. data/app/views/thredded/posts_common/actions/_quote.html.erb +1 -1
  36. data/app/views/thredded/shared/_page.html.erb +1 -1
  37. data/app/views/thredded/shared/nav/_standalone.html.erb +2 -2
  38. data/bin/create_migration_fixture +23 -0
  39. data/bin/rubocop +11 -0
  40. data/config/i18n-tasks.yml +6 -1
  41. data/config/locales/de.yml +2 -0
  42. data/config/locales/en.yml +3 -1
  43. data/config/locales/es.yml +2 -0
  44. data/config/locales/fr.yml +2 -0
  45. data/config/locales/it.yml +4 -2
  46. data/config/locales/pl.yml +2 -0
  47. data/config/locales/pt-BR.yml +2 -0
  48. data/config/locales/ru.yml +2 -0
  49. data/config/locales/zh-CN.yml +2 -0
  50. data/db/upgrade_migrations/20161019150201_upgrade_v0_7_to_v0_8.rb +5 -5
  51. data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +1 -1
  52. data/lib/generators/thredded/install/templates/initializer.rb +3 -3
  53. data/lib/thredded/arel_compat.rb +1 -42
  54. data/lib/thredded/base_migration.rb +2 -2
  55. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +91 -28
  56. data/lib/thredded/compat.rb +35 -0
  57. data/lib/thredded/content_formatter.rb +30 -8
  58. data/lib/thredded/database_seeder.rb +11 -4
  59. data/lib/thredded/db_tools.rb +3 -5
  60. data/lib/thredded/email_transformer.rb +2 -2
  61. data/lib/thredded/engine.rb +1 -1
  62. data/lib/thredded/formatting_demo_content.rb +21 -21
  63. data/lib/thredded/html_pipeline/at_mention_filter.rb +1 -1
  64. data/lib/thredded/html_pipeline/onebox_filter.rb +3 -3
  65. data/lib/thredded/html_pipeline/utils.rb +1 -1
  66. data/lib/thredded/version.rb +1 -1
  67. data/lib/thredded.rb +3 -23
  68. 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, identifier: template.identifier, count: collection.size) do |instrumentation_payload|
27
+ ActiveSupport::Notifications.instrument(:collection,
28
+ identifier: template.identifier,
29
+ count: collection.size) do |instrumentation_payload|
28
30
  return [] if collection.blank?
29
- keyed_collection = collection.each_with_object({}) do |item, hash|
30
- key = ActiveSupport::Cache.expand_cache_key(
31
- view_context.cache_fragment_name(item, virtual_path: template.virtual_path), :views
32
- )
33
- # #read_multi & #write may require key mutability, Dalli 2.6.0.
34
- hash[key.frozen? ? key.dup : key] = item
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
- keyed_collection.map do |cache_key, item|
48
- [item, cached_partials[cache_key] || rendered_partials.next.tap do |rendered|
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 collection_cache
58
- if ActionView::PartialRenderer.respond_to?(:collection_cache)
59
- # Rails 5.0+
60
- ActionView::PartialRenderer.collection_cache
61
- else
62
- # Rails 4.2.x
63
- Rails.application.config.action_controller.cache_store
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 { render_partials_serial(view_context.dup, slice, opts) }
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
- # @param [Array<Object>] collection
81
- # @param [Hash] opts
82
- # @param view_context
83
- # @return [Array<String>]
84
- def render_partials_serial(view_context, collection, opts)
85
- partial_renderer = ActionView::PartialRenderer.new(@lookup_context)
86
- collection.map { |object| render_partial(partial_renderer, view_context, opts.merge(object: object)) }
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
- if Rails::VERSION::MAJOR >= 6
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 whitelist options.
8
- attr_accessor :whitelist
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.whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST.deep_merge(
32
- elements: HTML::Pipeline::SanitizationFilter::WHITELIST[:elements] + %w[abbr iframe span figure figcaption],
33
- transformers: HTML::Pipeline::SanitizationFilter::WHITELIST[: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 a_tag['href'] =~ %r{^(?:[a-z]+:)?//}
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 => HTML::Pipeline::SanitizationFilter::WHITELIST[:attributes][: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
- whitelist: ContentFormatter.whitelist
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/features/fake_content'
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)
@@ -10,12 +10,10 @@ module Thredded
10
10
  verbose_was = ActiveRecord::Migration.verbose
11
11
  ActiveRecord::Migration.verbose = !quiet
12
12
  migrate =
13
- if Rails.gem_version >= Gem::Version.new('6.0.0.rc2')
13
+ if Thredded::Compat.rails_gte_60?
14
14
  -> { ActiveRecord::MigrationContext.new(paths, ActiveRecord::SchemaMigration).migrate(nil, &filter) }
15
- elsif Rails::VERSION::STRING >= '5.2'
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 statement =~ /(BEGIN TRANSACTION|COMMIT)/
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 doc [Nokogiri::HTML::Document]
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
@@ -12,7 +12,7 @@ module Thredded
12
12
 
13
13
  config.to_prepare do
14
14
  Thredded::AllViewHooks.reset_instance!
15
- Thredded.user_class.send(:include, Thredded::UserExtender) if 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
- <<-'MARKDOWN',
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/master/lib/onebox/engine). Powered by the [onebox](https://github.com/discourse/onebox) library.
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 = name =~ /[., ()]/ ? %("#{name}") : 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 a_tag['href'] =~ %r{^(?:[a-z]+:)?//}
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
- "#{preview.placeholder_html}</a></p>"
73
+ "#{placeholder_html}</a></p>"
74
74
  else
75
75
  preview.to_s.strip
76
76
  end
@@ -40,7 +40,7 @@ module Thredded
40
40
  end
41
41
 
42
42
  def node_name?(node, node_name)
43
- node && node.node_name && node.node_name.casecmp(node_name).zero?
43
+ node&.node_name && node.node_name.casecmp(node_name).zero?
44
44
  end
45
45
  end
46
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thredded
4
- VERSION = '0.16.15'
4
+ VERSION = '1.0.1'
5
5
  end
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 'rb-gravatar'
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) { Gravatar.src(user.email, 156, 'mm') }
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