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.
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