actiontext 7.1.5.2 → 8.1.2.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -132
  3. data/app/assets/javascripts/actiontext.esm.js +114 -14
  4. data/app/assets/javascripts/actiontext.js +120 -17
  5. data/app/helpers/action_text/content_helper.rb +2 -0
  6. data/app/helpers/action_text/tag_helper.rb +63 -37
  7. data/app/javascript/actiontext/attachment_upload.js +78 -12
  8. data/app/javascript/actiontext/index.js +10 -1
  9. data/app/models/action_text/encrypted_rich_text.rb +2 -2
  10. data/app/models/action_text/record.rb +2 -0
  11. data/app/models/action_text/rich_text.rb +62 -27
  12. data/db/migrate/20180528164100_create_action_text_tables.rb +1 -1
  13. data/lib/action_text/attachable.rb +35 -33
  14. data/lib/action_text/attachables/content_attachment.rb +2 -0
  15. data/lib/action_text/attachables/missing_attachable.rb +2 -0
  16. data/lib/action_text/attachables/remote_image.rb +2 -0
  17. data/lib/action_text/attachment.rb +27 -25
  18. data/lib/action_text/attachment_gallery.rb +2 -0
  19. data/lib/action_text/attachments/caching.rb +2 -0
  20. data/lib/action_text/attachments/minification.rb +2 -0
  21. data/lib/action_text/attachments/trix_conversion.rb +2 -0
  22. data/lib/action_text/attribute.rb +61 -27
  23. data/lib/action_text/content.rb +49 -28
  24. data/lib/action_text/deprecator.rb +2 -0
  25. data/lib/action_text/encryption.rb +2 -0
  26. data/lib/action_text/engine.rb +7 -2
  27. data/lib/action_text/fixture_set.rb +35 -35
  28. data/lib/action_text/fragment.rb +4 -0
  29. data/lib/action_text/gem_version.rb +6 -4
  30. data/lib/action_text/html_conversion.rb +2 -0
  31. data/lib/action_text/plain_text_conversion.rb +65 -28
  32. data/lib/action_text/rendering.rb +2 -1
  33. data/lib/action_text/serialization.rb +2 -0
  34. data/lib/action_text/system_test_helper.rb +52 -33
  35. data/lib/action_text/trix_attachment.rb +2 -0
  36. data/lib/action_text/version.rb +3 -1
  37. data/lib/generators/action_text/install/install_generator.rb +4 -21
  38. data/lib/generators/action_text/install/templates/actiontext.css +414 -5
  39. data/lib/rails/generators/test_unit/install_generator.rb +2 -0
  40. data/package.json +3 -2
  41. metadata +28 -16
  42. data/app/assets/javascripts/trix.js +0 -13720
  43. data/app/assets/stylesheets/trix.css +0 -412
@@ -1,68 +1,76 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module PlainTextConversion
5
7
  extend self
6
8
 
7
9
  def node_to_plain_text(node)
8
- remove_trailing_newlines(plain_text_for_node(node))
10
+ BottomUpReducer.new(node).reduce do |n, child_values|
11
+ plain_text_for_node(n, child_values)
12
+ end.then(&method(:remove_trailing_newlines))
9
13
  end
10
14
 
11
15
  private
12
- def plain_text_for_node(node, index = 0)
16
+ def plain_text_for_node(node, child_values)
13
17
  if respond_to?(plain_text_method_for_node(node), true)
14
- send(plain_text_method_for_node(node), node, index)
18
+ send(plain_text_method_for_node(node), node, child_values)
15
19
  else
16
- plain_text_for_node_children(node)
20
+ plain_text_for_child_values(child_values)
17
21
  end
18
22
  end
19
23
 
20
- def plain_text_for_node_children(node)
21
- texts = []
22
- node.children.each_with_index do |child, index|
23
- texts << plain_text_for_node(child, index)
24
- end
25
- texts.join
26
- end
27
-
28
24
  def plain_text_method_for_node(node)
29
25
  :"plain_text_for_#{node.name}_node"
30
26
  end
31
27
 
32
- def plain_text_for_block(node, index = 0)
33
- "#{remove_trailing_newlines(plain_text_for_node_children(node))}\n\n"
28
+ def plain_text_for_child_values(child_values)
29
+ child_values.join
30
+ end
31
+
32
+ def plain_text_for_unsupported_node(node, _child_values)
33
+ ""
34
+ end
35
+
36
+ %i[ script style].each do |element|
37
+ alias_method :"plain_text_for_#{element}_node", :plain_text_for_unsupported_node
38
+ end
39
+
40
+ def plain_text_for_block(node, child_values)
41
+ "#{remove_trailing_newlines(plain_text_for_child_values(child_values))}\n\n"
34
42
  end
35
43
 
36
44
  %i[ h1 p ].each do |element|
37
45
  alias_method :"plain_text_for_#{element}_node", :plain_text_for_block
38
46
  end
39
47
 
40
- def plain_text_for_list(node, index)
41
- "#{break_if_nested_list(node, plain_text_for_block(node))}"
48
+ def plain_text_for_list(node, child_values)
49
+ "#{break_if_nested_list(node, plain_text_for_block(node, child_values))}"
42
50
  end
43
51
 
44
52
  %i[ ul ol ].each do |element|
45
53
  alias_method :"plain_text_for_#{element}_node", :plain_text_for_list
46
54
  end
47
55
 
48
- def plain_text_for_br_node(node, index)
56
+ def plain_text_for_br_node(node, _child_values)
49
57
  "\n"
50
58
  end
51
59
 
52
- def plain_text_for_text_node(node, index)
60
+ def plain_text_for_text_node(node, _child_values)
53
61
  remove_trailing_newlines(node.text)
54
62
  end
55
63
 
56
- def plain_text_for_div_node(node, index)
57
- "#{remove_trailing_newlines(plain_text_for_node_children(node))}\n"
64
+ def plain_text_for_div_node(node, child_values)
65
+ "#{remove_trailing_newlines(plain_text_for_child_values(child_values))}\n"
58
66
  end
59
67
 
60
- def plain_text_for_figcaption_node(node, index)
61
- "[#{remove_trailing_newlines(plain_text_for_node_children(node))}]"
68
+ def plain_text_for_figcaption_node(node, child_values)
69
+ "[#{remove_trailing_newlines(plain_text_for_child_values(child_values))}]"
62
70
  end
63
71
 
64
- def plain_text_for_blockquote_node(node, index)
65
- text = plain_text_for_block(node)
72
+ def plain_text_for_blockquote_node(node, child_values)
73
+ text = plain_text_for_block(node, child_values)
66
74
  return "“”" if text.blank?
67
75
 
68
76
  text = text.dup
@@ -71,9 +79,9 @@ module ActionText
71
79
  text
72
80
  end
73
81
 
74
- def plain_text_for_li_node(node, index)
75
- bullet = bullet_for_li_node(node, index)
76
- text = remove_trailing_newlines(plain_text_for_node_children(node))
82
+ def plain_text_for_li_node(node, child_values)
83
+ bullet = bullet_for_li_node(node)
84
+ text = remove_trailing_newlines(plain_text_for_child_values(child_values))
77
85
  indentation = indentation_for_li_node(node)
78
86
 
79
87
  "#{indentation}#{bullet} #{text}\n"
@@ -83,8 +91,9 @@ module ActionText
83
91
  text.chomp("")
84
92
  end
85
93
 
86
- def bullet_for_li_node(node, index)
94
+ def bullet_for_li_node(node)
87
95
  if list_node_name_for_li_node(node) == "ol"
96
+ index = node.parent.elements.index(node)
88
97
  "#{index + 1}."
89
98
  else
90
99
  "•"
@@ -113,5 +122,33 @@ module ActionText
113
122
  text
114
123
  end
115
124
  end
125
+
126
+ class BottomUpReducer # :nodoc:
127
+ def initialize(node)
128
+ @node = node
129
+ @values = {}
130
+ end
131
+
132
+ def reduce(&block)
133
+ traverse_bottom_up(@node) do |n|
134
+ child_values = @values.values_at(*n.children)
135
+ @values[n] = block.call(n, child_values)
136
+ end
137
+ @values[@node]
138
+ end
139
+
140
+ private
141
+ def traverse_bottom_up(node, &block)
142
+ call_stack, processing_stack = [ node ], []
143
+
144
+ until call_stack.empty?
145
+ node = call_stack.pop
146
+ processing_stack.push(node)
147
+ call_stack.concat node.children
148
+ end
149
+
150
+ processing_stack.reverse_each(&block)
151
+ end
152
+ end
116
153
  end
117
154
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/concern"
3
+ # :markup: markdown
4
+
4
5
  require "active_support/core_ext/module/attribute_accessors_per_thread"
5
6
 
6
7
  module ActionText
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module Serialization
5
7
  extend ActiveSupport::Concern
@@ -1,55 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module SystemTestHelper
5
7
  # Locates a Trix editor and fills it in with the given HTML.
6
8
  #
7
9
  # The editor can be found by:
8
- # * its +id+
9
- # * its +placeholder+
10
- # * the text from its +label+ element
11
- # * its +aria-label+
12
- # * the +name+ of its input
10
+ #
11
+ # * its `id`
12
+ # * its `placeholder`
13
+ # * the text from its `label` element
14
+ # * its `aria-label`
15
+ # * the `name` of its input
16
+ #
17
+ # Additional options are forwarded to Capybara as filters
13
18
  #
14
19
  # Examples:
15
20
  #
16
- # # <trix-editor id="message_content" ...></trix-editor>
17
- # fill_in_rich_text_area "message_content", with: "Hello <em>world!</em>"
21
+ # # <trix-editor id="message_content" ...></trix-editor>
22
+ # fill_in_rich_textarea "message_content", with: "Hello <em>world!</em>"
18
23
  #
19
- # # <trix-editor placeholder="Your message here" ...></trix-editor>
20
- # fill_in_rich_text_area "Your message here", with: "Hello <em>world!</em>"
24
+ # # <trix-editor placeholder="Your message here" ...></trix-editor>
25
+ # fill_in_rich_textarea "Your message here", with: "Hello <em>world!</em>"
21
26
  #
22
- # # <label for="message_content">Message content</label>
23
- # # <trix-editor id="message_content" ...></trix-editor>
24
- # fill_in_rich_text_area "Message content", with: "Hello <em>world!</em>"
27
+ # # <label for="message_content">Message content</label>
28
+ # # <trix-editor id="message_content" ...></trix-editor>
29
+ # fill_in_rich_textarea "Message content", with: "Hello <em>world!</em>"
25
30
  #
26
- # # <trix-editor aria-label="Message content" ...></trix-editor>
27
- # fill_in_rich_text_area "Message content", with: "Hello <em>world!</em>"
31
+ # # <trix-editor aria-label="Message content" ...></trix-editor>
32
+ # fill_in_rich_textarea "Message content", with: "Hello <em>world!</em>"
28
33
  #
29
- # # <input id="trix_input_1" name="message[content]" type="hidden">
30
- # # <trix-editor input="trix_input_1"></trix-editor>
31
- # fill_in_rich_text_area "message[content]", with: "Hello <em>world!</em>"
32
- def fill_in_rich_text_area(locator = nil, with:)
33
- find(:rich_text_area, locator).execute_script("this.editor.loadHTML(arguments[0])", with.to_s)
34
+ # # <input id="trix_input_1" name="message[content]" type="hidden">
35
+ # # <trix-editor input="trix_input_1"></trix-editor>
36
+ # fill_in_rich_textarea "message[content]", with: "Hello <em>world!</em>"
37
+ def fill_in_rich_textarea(locator = nil, with:, **)
38
+ find(:rich_textarea, locator, **).execute_script(<<~JS, with.to_s)
39
+ if ("value" in this) {
40
+ this.value = arguments[0]
41
+ } else {
42
+ this.editor.loadHTML(arguments[0])
43
+ }
44
+ JS
34
45
  end
46
+ alias_method :fill_in_rich_text_area, :fill_in_rich_textarea
35
47
  end
36
48
  end
37
49
 
38
- Capybara.add_selector :rich_text_area do
39
- label "rich-text area"
40
- xpath do |locator|
41
- if locator.nil?
42
- XPath.descendant(:"trix-editor")
43
- else
44
- input_located_by_name = XPath.anywhere(:input).where(XPath.attr(:name) == locator).attr(:id)
45
- input_located_by_label = XPath.anywhere(:label).where(XPath.string.n.is(locator)).attr(:for)
50
+ %i[rich_textarea rich_text_area].each do |rich_textarea|
51
+ Capybara.add_selector rich_textarea do
52
+ label "rich-text area"
53
+ xpath do |locator|
54
+ xpath = XPath.descendant[[
55
+ XPath.attribute(:role) == "textbox",
56
+ (XPath.attribute(:contenteditable) == "") | (XPath.attribute(:contenteditable) == "true")
57
+ ].reduce(:&)]
58
+
59
+ if locator.nil?
60
+ xpath
61
+ else
62
+ input_located_by_name = XPath.anywhere(:input).where(XPath.attr(:name) == locator).attr(:id)
63
+ input_located_by_label = XPath.anywhere(:label).where(XPath.string.n.is(locator)).attr(:for)
46
64
 
47
- XPath.descendant(:"trix-editor").where \
48
- XPath.attr(:id).equals(locator) |
49
- XPath.attr(:placeholder).equals(locator) |
50
- XPath.attr(:"aria-label").equals(locator) |
51
- XPath.attr(:input).equals(input_located_by_name) |
52
- XPath.attr(:id).equals(input_located_by_label)
65
+ xpath.where \
66
+ XPath.attr(:id).equals(locator) |
67
+ XPath.attr(:placeholder).equals(locator) |
68
+ XPath.attr(:"aria-label").equals(locator) |
69
+ XPath.attr(:input).equals(input_located_by_name) |
70
+ XPath.attr(:id).equals(input_located_by_label)
71
+ end
53
72
  end
54
73
  end
55
74
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  class TrixAttachment
5
7
  TAG_NAME = "figure"
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require_relative "gem_version"
4
6
 
5
7
  module ActionText
6
- # Returns the currently loaded version of Action Text as a +Gem::Version+.
8
+ # Returns the currently loaded version of Action Text as a `Gem::Version`.
7
9
  def self.version
8
10
  gem_version
9
11
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "pathname"
4
6
  require "json"
5
7
 
@@ -34,20 +36,8 @@ module ActionText
34
36
  end
35
37
 
36
38
  def create_actiontext_files
37
- destination = Pathname(destination_root)
38
-
39
39
  template "actiontext.css", "app/assets/stylesheets/actiontext.css"
40
40
 
41
- unless destination.join("app/assets/application.css").exist?
42
- if (stylesheets = Dir.glob "#{destination_root}/app/assets/stylesheets/application.*.{scss,css}").length > 0
43
- insert_into_file stylesheets.first.to_s, %(@import 'actiontext.css';)
44
- else
45
- say <<~INSTRUCTIONS, :green
46
- To use the Trix editor, you must require 'app/assets/stylesheets/actiontext.css' in your base stylesheet.
47
- INSTRUCTIONS
48
- end
49
- end
50
-
51
41
  gem_root = "#{__dir__}/../../../.."
52
42
 
53
43
  copy_file "#{gem_root}/app/views/active_storage/blobs/_blob.html.erb",
@@ -57,13 +47,6 @@ module ActionText
57
47
  "app/views/layouts/action_text/contents/_content.html.erb"
58
48
  end
59
49
 
60
- def enable_image_processing_gem
61
- if (gemfile_path = Pathname(destination_root).join("Gemfile")).exist?
62
- say "Ensure image_processing gem has been enabled so image uploads will work (remember to bundle!)"
63
- uncomment_lines gemfile_path, /gem "image_processing"/
64
- end
65
- end
66
-
67
50
  def create_migrations
68
51
  rails_command "railties:install:migrations FROM=active_storage,action_text", inline: true
69
52
  end
@@ -73,8 +56,8 @@ module ActionText
73
56
  end
74
57
 
75
58
  def using_bun?
76
- # Cannot assume yarn.lock has been generated yet so we look for
77
- # a file known to be generated by the jsbundling-rails gem
59
+ # Cannot assume yarn.lock has been generated yet so we look for a file known to
60
+ # be generated by the jsbundling-rails gem
78
61
  @using_bun ||= using_js_runtime? && Pathname(destination_root).join("bun.config.js").exist?
79
62
  end
80
63