thredded 0.14.4 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -0
  4. data/app/assets/javascripts/thredded/components/spoilers.es6 +37 -0
  5. data/app/assets/stylesheets/thredded/_email.scss +25 -0
  6. data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
  7. data/app/assets/stylesheets/thredded/base/_typography.scss +1 -1
  8. data/app/assets/stylesheets/thredded/base/_variables.scss +2 -3
  9. data/app/assets/stylesheets/thredded/components/_post.scss +0 -11
  10. data/app/assets/stylesheets/thredded/components/_spoiler.scss +41 -0
  11. data/app/commands/thredded/mark_all_read.rb +1 -7
  12. data/app/controllers/concerns/thredded/new_post_params.rb +1 -1
  13. data/app/controllers/concerns/thredded/new_private_post_params.rb +1 -1
  14. data/app/controllers/concerns/thredded/new_private_topic_params.rb +1 -2
  15. data/app/controllers/concerns/thredded/new_topic_params.rb +0 -1
  16. data/app/controllers/thredded/application_controller.rb +10 -0
  17. data/app/controllers/thredded/posts_controller.rb +1 -2
  18. data/app/controllers/thredded/private_posts_controller.rb +1 -2
  19. data/app/controllers/thredded/private_topics_controller.rb +10 -12
  20. data/app/controllers/thredded/topics_controller.rb +14 -17
  21. data/app/forms/thredded/edit_topic_form.rb +2 -1
  22. data/app/forms/thredded/post_form.rb +3 -1
  23. data/app/forms/thredded/private_post_form.rb +3 -1
  24. data/app/forms/thredded/private_topic_form.rb +1 -0
  25. data/app/jobs/thredded/activity_updater_job.rb +18 -8
  26. data/app/jobs/thredded/auto_follow_and_notify_job.rb +2 -1
  27. data/app/models/concerns/thredded/post_common.rb +3 -4
  28. data/app/models/concerns/thredded/topic_common.rb +7 -0
  29. data/app/models/concerns/thredded/user_topic_read_state_common.rb +62 -5
  30. data/app/models/thredded/null_user_topic_read_state.rb +8 -0
  31. data/app/policies/thredded/private_post_policy.rb +16 -0
  32. data/app/view_hooks/thredded/all_view_hooks.rb +3 -0
  33. data/app/view_models/thredded/base_topic_view.rb +5 -1
  34. data/app/view_models/thredded/post_view.rb +13 -1
  35. data/app/view_models/thredded/posts_page_view.rb +1 -1
  36. data/app/view_models/thredded/topic_posts_page_view.rb +13 -1
  37. data/app/views/thredded/posts/_post.html.erb +1 -0
  38. data/app/views/thredded/posts/edit.html.erb +1 -0
  39. data/app/views/thredded/posts_common/_before_first_unread_post.html.erb +7 -0
  40. data/app/views/thredded/posts_common/form/_after_content.html.erb +8 -0
  41. data/app/views/thredded/posts_common/form/_before_content.html.erb +8 -0
  42. data/app/views/thredded/private_posts/_private_post.html.erb +2 -1
  43. data/app/views/thredded/private_posts/edit.html.erb +1 -0
  44. data/app/views/thredded/private_topics/_form.html.erb +1 -0
  45. data/app/views/thredded/private_topics/edit.html.erb +2 -1
  46. data/app/views/thredded/shared/_field_errors.html.erb +3 -0
  47. data/app/views/thredded/shared/_nav.html.erb +1 -1
  48. data/app/views/thredded/shared/_page.html.erb +1 -1
  49. data/app/views/thredded/topics/_form.html.erb +1 -0
  50. data/app/views/thredded/topics/edit.html.erb +2 -1
  51. data/config/locales/de.yml +2 -0
  52. data/config/locales/en.yml +2 -0
  53. data/config/locales/es.yml +2 -0
  54. data/config/locales/fr.yml +2 -0
  55. data/config/locales/it.yml +2 -0
  56. data/config/locales/pl.yml +2 -0
  57. data/config/locales/pt-BR.yml +2 -0
  58. data/config/locales/ru.yml +2 -0
  59. data/config/locales/zh-CN.yml +2 -0
  60. data/db/migrate/20160329231848_create_thredded.rb +37 -23
  61. data/db/upgrade_migrations/{20170811090735_upgrade_thredded_v0_13_to_v_014.rb → 20170811090735_upgrade_thredded_v0_13_to_v0_14.rb} +0 -0
  62. data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +91 -0
  63. data/lib/generators/thredded/install/templates/initializer.rb +16 -7
  64. data/lib/thredded.rb +143 -125
  65. data/lib/thredded/arel_compat.rb +57 -0
  66. data/lib/thredded/base_migration.rb +10 -0
  67. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +35 -9
  68. data/lib/thredded/content_formatter.rb +27 -18
  69. data/lib/thredded/database_seeder.rb +218 -64
  70. data/lib/thredded/email_transformer.rb +5 -2
  71. data/lib/thredded/email_transformer/spoiler.rb +25 -0
  72. data/lib/thredded/formatting_demo_content.rb +12 -0
  73. data/lib/thredded/html_pipeline/onebox_filter.rb +3 -38
  74. data/lib/thredded/html_pipeline/spoiler_tag_filter.rb +128 -0
  75. data/lib/thredded/html_pipeline/utils.rb +47 -0
  76. data/lib/thredded/rails_lt_5_2_arel_case_node.rb +119 -0
  77. data/lib/thredded/version.rb +1 -1
  78. metadata +17 -21
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thredded/email_transformer/onebox'
4
+ require 'thredded/email_transformer/spoiler'
4
5
 
5
6
  module Thredded
6
7
  # This transformer should applied to emails so that they render correctly in the email clients.
@@ -11,8 +12,10 @@ module Thredded
11
12
  # Rails.application.config.roadie.before_transformation = Thredded::EmailTransformer
12
13
  #
13
14
  module EmailTransformer
14
- mattr_accessor :transformers
15
- self.transformers = [Onebox]
15
+ class << self
16
+ attr_accessor :transformers
17
+ end
18
+ @transformers = [Onebox, Spoiler]
16
19
 
17
20
  # @param doc [Nokogiri::HTML::Document]
18
21
  def self.call(doc)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thredded/email_transformer/base'
4
+ module Thredded
5
+ module EmailTransformer
6
+ # Changes the spoiler tags to work in emails.
7
+ class Spoiler < Base
8
+ def call
9
+ doc.css('.thredded--post--content--spoiler--summary').each do |node|
10
+ node.content = I18n.t('thredded.posts.spoiler_summary_for_email')
11
+ end
12
+ doc.css('.thredded--post--content--spoiler--contents img').each do |img|
13
+ text = "#{img['src']} #{" (#{img['alt']})" if img['alt'].present?}"
14
+ img.swap(
15
+ if img.parent.name == 'a' && img.parent.children.size == 1
16
+ doc.document.create_text_node(text)
17
+ else
18
+ doc.document.create_element('a', text, href: img['src'], target: '_blank')
19
+ end
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -8,6 +8,18 @@ module Thredded
8
8
  end
9
9
  self.parts = [
10
10
  <<-'MARKDOWN',
11
+ #### Spoilers
12
+
13
+ Use `<spoiler></spoiler>` tags to create spoiler boxes like this one:
14
+
15
+ <spoiler>
16
+ Harry Potter books are better than the movies.
17
+
18
+ ![nyancat](https://storage.googleapis.com/glebm-stuff/nyancat.gif)
19
+
20
+ https://www.youtube.com/watch?v=5lBBUPVuusM
21
+ </spoiler>
22
+
11
23
  #### Oneboxes
12
24
 
13
25
  URLs of supported resources are replaced with boxes like these:
@@ -5,6 +5,8 @@ require 'onebox'
5
5
  module Thredded
6
6
  module HtmlPipeline
7
7
  class OneboxFilter < ::HTML::Pipeline::Filter
8
+ include ::Thredded::HtmlPipeline::Utils
9
+
8
10
  SANITIZE_CONFIG = Sanitize::Config.merge(
9
11
  Sanitize::Config::ONEBOX,
10
12
  attributes: {
@@ -51,7 +53,7 @@ module Thredded
51
53
  next unless url.present? && url == element.content && on_its_own_line?(element)
52
54
  onebox_html = render_onebox_with_cache(url)
53
55
  next if onebox_html.empty?
54
- fixup_paragraph! element
56
+ extract_block_from_paragraph! element
55
57
  element.swap onebox_html
56
58
  end
57
59
  doc
@@ -101,43 +103,6 @@ module Thredded
101
103
  nodes.take_while { |node| !node_name?(node, 'br') }
102
104
  .all? { |node| node.text? && node.blank? }
103
105
  end
104
-
105
- def fixup_paragraph!(element)
106
- # Can't have a div inside a paragraph, so split the paragraph
107
- p = element.parent
108
- return unless node_name?(p, 'p')
109
- children_after = p.children[p.children.index(element) + 1..-1]
110
- remove_leading_blanks! children_after
111
- # Move the onebox out of and after the paragraph
112
- p.add_next_sibling element
113
- # Move all the elements after the onebox to a new paragraph
114
- unless children_after.empty?
115
- new_p = Nokogiri::XML::Node.new 'p', doc
116
- element.add_next_sibling new_p
117
- children_after.each { |child| new_p.add_child child }
118
- end
119
- # The original paragraph might have been split just after a <br> or whitespace, remove them if so:
120
- remove_leading_blanks! p.children.reverse
121
- p.remove if p.children.empty?
122
- end
123
-
124
- # @param children [Nokogiri::XML::NodeSet]
125
- def remove_leading_blanks!(children)
126
- to_remove = children.take_while do |c|
127
- if node_name?(c, 'br') || c.text? && c.content.blank?
128
- c.remove
129
- true
130
- else
131
- c.content = c.content.lstrip
132
- false
133
- end
134
- end
135
- to_remove.each { |c| children.delete(c) }
136
- end
137
-
138
- def node_name?(node, node_name)
139
- node && node.node_name && node.node_name.casecmp(node_name).zero?
140
- end
141
106
  end
142
107
  end
143
108
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module HtmlPipeline
5
+ module SpoilerTagFilter
6
+ PLACEHOLDER_START_CLASS = 'thredded-spoiler-placeholder-start-24uob7bajv5'
7
+ PLACEHOLDER_END_CLASS = 'thredded-spoiler-placeholder-end-24uob7bajv5'
8
+ PLACEHOLDER_START_BLOCK = "<p class='#{PLACEHOLDER_START_CLASS}' title='%s'></p>"
9
+ PLACEHOLDER_END_BLOCK = "<p class='#{PLACEHOLDER_END_CLASS}' title='%s'></p>"
10
+ PLACEHOLDER_START_SPAN = "<i class='#{PLACEHOLDER_START_CLASS}' title='%s'></i>"
11
+ PLACEHOLDER_END_SPAN = "<i class='#{PLACEHOLDER_END_CLASS}' title='%s'></i>"
12
+
13
+ class << self
14
+ # @return [[[String, String]]] Pairs of opening and closing spoiler tags.
15
+ attr_reader :spoiler_tags
16
+
17
+ # @api private
18
+ attr_reader :spoiler_tags_replacements
19
+
20
+ # @api private
21
+ attr_reader :spoiler_tags_inverse_replacements
22
+
23
+ # @param [[[String, String]]] spoiler_tags
24
+ def spoiler_tags=(spoiler_tags)
25
+ @spoiler_tags = spoiler_tags.freeze
26
+ @spoiler_tags_replacements = spoiler_tags.flat_map.with_index do |(open, close), i|
27
+ [[/((?:\A|\r?\n|>)[ ]*)#{Regexp.escape(open)}(?=[ \t]*\r?(?:\n|\z))/, "\\1#{PLACEHOLDER_START_BLOCK % i}"],
28
+ [/((?:\A|\r?\n|>)[ ]*)#{Regexp.escape(close)}(?=[ \t]*\r?(?:\n|\z))/, "\\1#{PLACEHOLDER_END_BLOCK % i}"],
29
+ [open, PLACEHOLDER_START_SPAN % i],
30
+ [close, PLACEHOLDER_END_SPAN % i]]
31
+ end
32
+ @spoiler_tags_inverse_replacements = spoiler_tags.flat_map.with_index do |(open, close), i|
33
+ [[PLACEHOLDER_START_BLOCK % i, open],
34
+ [PLACEHOLDER_END_BLOCK % i, close],
35
+ [PLACEHOLDER_START_SPAN % i, open],
36
+ [PLACEHOLDER_END_SPAN % i, close]]
37
+ end
38
+ end
39
+ end
40
+
41
+ self.spoiler_tags = [
42
+ %w[<spoiler> </spoiler>],
43
+ ].freeze
44
+
45
+ class BeforeMarkup < ::HTML::Pipeline::Filter
46
+ def call
47
+ @html = +html # Unfreeze
48
+ SpoilerTagFilter.spoiler_tags_replacements.each do |(pattern, replacement)|
49
+ html.gsub! pattern, replacement
50
+ end
51
+ html
52
+ end
53
+ end
54
+
55
+ class AfterMarkup < ::HTML::Pipeline::Filter
56
+ include ::Thredded::HtmlPipeline::Utils
57
+
58
+ SPOILER_START = [PLACEHOLDER_START_BLOCK, PLACEHOLDER_START_SPAN].freeze
59
+ SPOILER_END = [PLACEHOLDER_END_BLOCK, PLACEHOLDER_END_SPAN].freeze
60
+
61
+ def call
62
+ doc.css(".#{PLACEHOLDER_START_CLASS}").each do |placeholder_start|
63
+ process(placeholder_start)
64
+ end
65
+ doc.search('.//text()').each do |node|
66
+ content = +node.content # Unfreeze
67
+ SpoilerTagFilter.spoiler_tags_inverse_replacements.each do |(pattern, replacement)|
68
+ content.gsub! pattern, replacement
69
+ end
70
+ node.content = content
71
+ end
72
+ doc
73
+ end
74
+
75
+ protected
76
+
77
+ # @param [Nokogiri::XML::Document] document
78
+ # @param [Array<Nokogiri::XML::Node>] children
79
+ def build_spoiler_tag(document, children)
80
+ document.create_element(
81
+ 'div',
82
+ class: 'thredded--post--content--spoiler',
83
+ tabindex: 0,
84
+ role: 'figure',
85
+ 'aria-expanded' => 'false',
86
+ ) << document.create_element(
87
+ 'div',
88
+ I18n.t('thredded.posts.spoiler_summary'),
89
+ class: 'thredded--post--content--spoiler--summary',
90
+ 'aria-hidden' => 'false',
91
+ ) << document.create_element(
92
+ 'div',
93
+ class: 'thredded--post--content--spoiler--contents',
94
+ 'aria-hidden' => 'true',
95
+ ) { |contents_tag| children.each { |child| contents_tag.add_child child } }
96
+ end
97
+
98
+ private
99
+
100
+ def process(placeholder_start)
101
+ placeholder_end, children = find_placeholder_end(placeholder_start)
102
+ placeholder_start.remove
103
+ return if !placeholder_end || children.empty?
104
+ spoiler_tag = placeholder_end.replace(build_spoiler_tag(doc.document, children))
105
+ extract_block_from_paragraph! spoiler_tag
106
+ spoiler_tag
107
+ end
108
+
109
+ def find_placeholder_end(sibling)
110
+ children = []
111
+ placeholder_end = nil
112
+ loop do
113
+ sibling = sibling.next_sibling
114
+ break if sibling.nil?
115
+ if sibling[:class] == PLACEHOLDER_START_CLASS
116
+ sibling = process(sibling) || sibling
117
+ elsif sibling[:class] == PLACEHOLDER_END_CLASS
118
+ placeholder_end = sibling
119
+ break
120
+ end
121
+ children << sibling
122
+ end
123
+ [placeholder_end, children]
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module HtmlPipeline
5
+ module Utils
6
+ private
7
+
8
+ # If the given block element is contained in a paragraph, splits the paragraph and removes unnecessary whitespace.
9
+ # @param [Nokogiri::HTML::Node] element the block element that may be inside a paragraph.
10
+ def extract_block_from_paragraph!(element)
11
+ p = element.parent
12
+ return unless node_name?(p, 'p')
13
+ children_after = p.children[p.children.index(element) + 1..-1]
14
+ remove_leading_blanks! children_after
15
+ # Move the element out of and after the paragraph
16
+ p.add_next_sibling element
17
+ # Move all the elements after the onebox to a new paragraph
18
+ unless children_after.empty?
19
+ new_p = Nokogiri::XML::Node.new 'p', doc
20
+ element.add_next_sibling new_p
21
+ children_after.each { |child| new_p.add_child child }
22
+ end
23
+ # The original paragraph might have been split just after a <br> or whitespace, remove them if so:
24
+ remove_leading_blanks! p.children.reverse
25
+ p.remove if p.children.empty?
26
+ end
27
+
28
+ # @param children [Nokogiri::XML::NodeSet]
29
+ def remove_leading_blanks!(children)
30
+ to_remove = children.take_while do |c|
31
+ if node_name?(c, 'br') || c.text? && c.content.blank?
32
+ c.remove
33
+ true
34
+ else
35
+ c.content = c.content.lstrip
36
+ false
37
+ end
38
+ end
39
+ to_remove.each { |c| children.delete(c) }
40
+ end
41
+
42
+ def node_name?(node, node_name)
43
+ node && node.node_name && node.node_name.casecmp(node_name).zero?
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module Nodes
5
+ class Case < Arel::Nodes::Node
6
+ include Arel::OrderPredications
7
+ include Arel::Predications
8
+ include Arel::AliasPredication
9
+
10
+ attr_accessor :case, :conditions, :default
11
+
12
+ def initialize(expression = nil, default = nil)
13
+ @case = expression
14
+ @conditions = []
15
+ @default = default
16
+ end
17
+
18
+ def when(condition, expression = nil)
19
+ @conditions << When.new(Nodes.build_quoted(condition), expression)
20
+ self
21
+ end
22
+
23
+ def then(expression)
24
+ @conditions.last.right = Nodes.build_quoted(expression)
25
+ self
26
+ end
27
+
28
+ def else(expression)
29
+ @default = Else.new Nodes.build_quoted(expression)
30
+ self
31
+ end
32
+
33
+ def initialize_copy(other)
34
+ super
35
+ @case = @case.clone if @case
36
+ @conditions = @conditions.map(&:clone)
37
+ @default = @default.clone if @default
38
+ end
39
+
40
+ def hash
41
+ [@case, @conditions, @default].hash
42
+ end
43
+
44
+ def eql?(other)
45
+ self.class == other.class &&
46
+ self.case == other.case &&
47
+ conditions == other.conditions &&
48
+ default == other.default
49
+ end
50
+
51
+ alias == eql?
52
+ end
53
+
54
+ class When < Binary
55
+ end
56
+
57
+ class Else < Unary
58
+ end
59
+ end
60
+ end
61
+
62
+ module Arel
63
+ module Visitors
64
+ class DepthFirst < Arel::Visitors::Visitor
65
+ alias visit_Arel_Nodes_Else unary
66
+
67
+ def visit_Arel_Nodes_Case(o) # rubocop:disable Style/MethodName
68
+ visit o.case
69
+ visit o.conditions
70
+ visit o.default
71
+ end
72
+
73
+ alias visit_Arel_Nodes_When binary
74
+ end
75
+ end
76
+ end
77
+
78
+ module Arel
79
+ module Predications
80
+ def when(right)
81
+ Nodes::Case.new(self).when quoted_node(right)
82
+ end
83
+ end
84
+ end
85
+
86
+ module Arel
87
+ module Visitors
88
+ class ToSql < Arel::Visitors::Reduce
89
+ def visit_Arel_Nodes_Case(o, collector) # rubocop:disable Style/MethodName
90
+ collector << 'CASE '
91
+ if o.case
92
+ visit o.case, collector
93
+ collector << ' '
94
+ end
95
+ o.conditions.each do |condition|
96
+ visit condition, collector
97
+ collector << ' '
98
+ end
99
+ if o.default
100
+ visit o.default, collector
101
+ collector << ' '
102
+ end
103
+ collector << 'END'
104
+ end
105
+
106
+ def visit_Arel_Nodes_When(o, collector) # rubocop:disable Style/MethodName
107
+ collector << 'WHEN '
108
+ visit o.left, collector
109
+ collector << ' THEN '
110
+ visit o.right, collector
111
+ end
112
+
113
+ def visit_Arel_Nodes_Else(o, collector) # rubocop:disable Style/MethodName
114
+ collector << 'ELSE '
115
+ visit o.expr, collector
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thredded
4
- VERSION = '0.14.4'
4
+ VERSION = '0.15.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thredded
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.4
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Oliveira
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-04-29 00:00:00.000000000 Z
12
+ date: 2018-06-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pundit
@@ -157,14 +157,14 @@ dependencies:
157
157
  requirements:
158
158
  - - ">="
159
159
  - !ruby/object:Gem::Version
160
- version: '0'
160
+ version: 1.17.0
161
161
  type: :runtime
162
162
  prerelease: false
163
163
  version_requirements: !ruby/object:Gem::Requirement
164
164
  requirements:
165
165
  - - ">="
166
166
  - !ruby/object:Gem::Version
167
- version: '0'
167
+ version: 1.17.0
168
168
  - !ruby/object:Gem::Dependency
169
169
  name: onebox
170
170
  requirement: !ruby/object:Gem::Requirement
@@ -174,7 +174,7 @@ dependencies:
174
174
  version: '1.8'
175
175
  - - ">="
176
176
  - !ruby/object:Gem::Version
177
- version: 1.8.13
177
+ version: 1.8.48
178
178
  type: :runtime
179
179
  prerelease: false
180
180
  version_requirements: !ruby/object:Gem::Requirement
@@ -184,7 +184,7 @@ dependencies:
184
184
  version: '1.8'
185
185
  - - ">="
186
186
  - !ruby/object:Gem::Version
187
- version: 1.8.13
187
+ version: 1.8.48
188
188
  - !ruby/object:Gem::Dependency
189
189
  name: html-pipeline
190
190
  requirement: !ruby/object:Gem::Requirement
@@ -633,20 +633,6 @@ dependencies:
633
633
  - - ">="
634
634
  - !ruby/object:Gem::Version
635
635
  version: '0'
636
- - !ruby/object:Gem::Dependency
637
- name: coffee-rails
638
- requirement: !ruby/object:Gem::Requirement
639
- requirements:
640
- - - ">="
641
- - !ruby/object:Gem::Version
642
- version: '0'
643
- type: :development
644
- prerelease: false
645
- version_requirements: !ruby/object:Gem::Requirement
646
- requirements:
647
- - - ">="
648
- - !ruby/object:Gem::Version
649
- version: '0'
650
636
  description: |-
651
637
  The best Rails 4.2+ forums engine ever. Its goal is to be as simple and feature rich as possible.
652
638
  Thredded works with SQLite, MySQL (v5.6.4+), and PostgreSQL. See the demo at https://thredded.org/.
@@ -686,6 +672,7 @@ files:
686
672
  - app/assets/javascripts/thredded/components/post_form.es6
687
673
  - app/assets/javascripts/thredded/components/preview_area.es6
688
674
  - app/assets/javascripts/thredded/components/quote_post.es6
675
+ - app/assets/javascripts/thredded/components/spoilers.es6
689
676
  - app/assets/javascripts/thredded/components/time_stamps.es6
690
677
  - app/assets/javascripts/thredded/components/topic_form.es6
691
678
  - app/assets/javascripts/thredded/components/topics.es6
@@ -738,6 +725,7 @@ files:
738
725
  - app/assets/stylesheets/thredded/components/_post.scss
739
726
  - app/assets/stylesheets/thredded/components/_preferences.scss
740
727
  - app/assets/stylesheets/thredded/components/_preview_area.scss
728
+ - app/assets/stylesheets/thredded/components/_spoiler.scss
741
729
  - app/assets/stylesheets/thredded/components/_topic-delete.scss
742
730
  - app/assets/stylesheets/thredded/components/_topic-header.scss
743
731
  - app/assets/stylesheets/thredded/components/_topics.scss
@@ -900,6 +888,7 @@ files:
900
888
  - app/views/thredded/posts/edit.html.erb
901
889
  - app/views/thredded/posts/new.html.erb
902
890
  - app/views/thredded/posts_common/_actions.html.erb
891
+ - app/views/thredded/posts_common/_before_first_unread_post.html.erb
903
892
  - app/views/thredded/posts_common/_content.html.erb
904
893
  - app/views/thredded/posts_common/_form.html.erb
905
894
  - app/views/thredded/posts_common/_header.html.erb
@@ -943,6 +932,7 @@ files:
943
932
  - app/views/thredded/shared/_breadcrumbs.html.erb
944
933
  - app/views/thredded/shared/_content_moderation_blocked_state.html.erb
945
934
  - app/views/thredded/shared/_currently_online.html.erb
935
+ - app/views/thredded/shared/_field_errors.html.erb
946
936
  - app/views/thredded/shared/_flash_messages.html.erb
947
937
  - app/views/thredded/shared/_header.html.erb
948
938
  - app/views/thredded/shared/_nav.html.erb
@@ -995,12 +985,14 @@ files:
995
985
  - db/upgrade_migrations/20170125033319_upgrade_v0_9_to_v0_10.rb
996
986
  - db/upgrade_migrations/20170312131417_upgrade_thredded_v0_10_to_v0_11.rb
997
987
  - db/upgrade_migrations/20170420163138_upgrade_thredded_v0_11_to_v0_12.rb
998
- - db/upgrade_migrations/20170811090735_upgrade_thredded_v0_13_to_v_014.rb
988
+ - db/upgrade_migrations/20170811090735_upgrade_thredded_v0_13_to_v0_14.rb
989
+ - db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb
999
990
  - lib/generators/thredded/install/USAGE
1000
991
  - lib/generators/thredded/install/install_generator.rb
1001
992
  - lib/generators/thredded/install/templates/initializer.rb
1002
993
  - lib/tasks/thredded_tasks.rake
1003
994
  - lib/thredded.rb
995
+ - lib/thredded/arel_compat.rb
1004
996
  - lib/thredded/base_migration.rb
1005
997
  - lib/thredded/base_notifier.rb
1006
998
  - lib/thredded/collection_to_strings_with_cache_renderer.rb
@@ -1010,6 +1002,7 @@ files:
1010
1002
  - lib/thredded/email_transformer.rb
1011
1003
  - lib/thredded/email_transformer/base.rb
1012
1004
  - lib/thredded/email_transformer/onebox.rb
1005
+ - lib/thredded/email_transformer/spoiler.rb
1013
1006
  - lib/thredded/engine.rb
1014
1007
  - lib/thredded/errors.rb
1015
1008
  - lib/thredded/formatting_demo_content.rb
@@ -1017,7 +1010,10 @@ files:
1017
1010
  - lib/thredded/html_pipeline/autolink_filter.rb
1018
1011
  - lib/thredded/html_pipeline/kramdown_filter.rb
1019
1012
  - lib/thredded/html_pipeline/onebox_filter.rb
1013
+ - lib/thredded/html_pipeline/spoiler_tag_filter.rb
1014
+ - lib/thredded/html_pipeline/utils.rb
1020
1015
  - lib/thredded/html_pipeline/wrap_iframes_filter.rb
1016
+ - lib/thredded/rails_lt_5_2_arel_case_node.rb
1021
1017
  - lib/thredded/version.rb
1022
1018
  - lib/thredded/view_hooks/config.rb
1023
1019
  - lib/thredded/view_hooks/renderer.rb