actiontext 6.1.4 → 7.0.0.rc1

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.

Potentially problematic release.


This version of actiontext might be problematic. Click here for more details.

Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -77
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +4 -0
  5. data/app/assets/javascripts/actiontext.js +900 -0
  6. data/app/assets/javascripts/trix.js +5278 -0
  7. data/app/assets/stylesheets/trix.css +375 -0
  8. data/app/helpers/action_text/content_helper.rb +17 -3
  9. data/app/helpers/action_text/tag_helper.rb +19 -9
  10. data/app/javascript/actiontext/attachment_upload.js +8 -1
  11. data/app/models/action_text/encrypted_rich_text.rb +9 -0
  12. data/app/models/action_text/record.rb +1 -1
  13. data/app/models/action_text/rich_text.rb +4 -0
  14. data/app/views/action_text/contents/_content.html.erb +1 -0
  15. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  16. data/db/migrate/20180528164100_create_action_text_tables.rb +14 -2
  17. data/lib/action_text/attachable.rb +4 -0
  18. data/lib/action_text/attachment.rb +4 -4
  19. data/lib/action_text/attachment_gallery.rb +14 -9
  20. data/lib/action_text/attachments/caching.rb +1 -1
  21. data/lib/action_text/attachments/minification.rb +1 -1
  22. data/lib/action_text/attribute.rb +18 -2
  23. data/lib/action_text/content.rb +7 -3
  24. data/lib/action_text/encryption.rb +38 -0
  25. data/lib/action_text/engine.rb +14 -0
  26. data/lib/action_text/fixture_set.rb +55 -0
  27. data/lib/action_text/gem_version.rb +4 -4
  28. data/lib/action_text/plain_text_conversion.rb +31 -2
  29. data/lib/action_text/rendering.rb +1 -1
  30. data/lib/action_text/serialization.rb +2 -0
  31. data/lib/action_text/trix_attachment.rb +3 -3
  32. data/lib/action_text.rb +1 -0
  33. data/lib/generators/action_text/install/install_generator.rb +40 -35
  34. data/lib/generators/action_text/install/templates/actiontext.css +35 -0
  35. data/package.json +13 -3
  36. metadata +40 -19
  37. data/app/views/action_text/content/_layout.html.erb +0 -3
  38. data/lib/generators/action_text/install/templates/actiontext.scss +0 -36
@@ -7,7 +7,7 @@ module ActionText
7
7
 
8
8
  class_methods do
9
9
  def fragment_by_minifying_attachments(content)
10
- Fragment.wrap(content).replace(ActionText::Attachment::SELECTOR) do |node|
10
+ Fragment.wrap(content).replace(ActionText::Attachment.tag_name) do |node|
11
11
  node.tap { |n| n.inner_html = "" }
12
12
  end
13
13
  end
@@ -24,7 +24,13 @@ module ActionText
24
24
  #
25
25
  # Message.all.with_rich_text_content # Avoids N+1 queries when you just want the body, not the attachments.
26
26
  # Message.all.with_rich_text_content_and_embeds # Avoids N+1 queries when you just want the body and attachments.
27
- def has_rich_text(name)
27
+ # Message.all.with_all_rich_text # Loads all rich text associations.
28
+ #
29
+ # === Options
30
+ #
31
+ # * <tt>:encrypted</tt> - Pass true to encrypt the rich text attribute. The encryption will be non-deterministic. See
32
+ # +ActiveRecord::Encryption::EncryptableRecord.encrypts+. Default: false.
33
+ def has_rich_text(name, encrypted: false)
28
34
  class_eval <<-CODE, __FILE__, __LINE__ + 1
29
35
  def #{name}
30
36
  rich_text_#{name} || build_rich_text_#{name}
@@ -39,12 +45,22 @@ module ActionText
39
45
  end
40
46
  CODE
41
47
 
48
+ rich_text_class_name = encrypted ? "ActionText::EncryptedRichText" : "ActionText::RichText"
42
49
  has_one :"rich_text_#{name}", -> { where(name: name) },
43
- class_name: "ActionText::RichText", as: :record, inverse_of: :record, autosave: true, dependent: :destroy
50
+ class_name: rich_text_class_name, as: :record, inverse_of: :record, autosave: true, dependent: :destroy
44
51
 
45
52
  scope :"with_rich_text_#{name}", -> { includes("rich_text_#{name}") }
46
53
  scope :"with_rich_text_#{name}_and_embeds", -> { includes("rich_text_#{name}": { embeds_attachments: :blob }) }
47
54
  end
55
+
56
+ # Eager load all dependent RichText models in bulk.
57
+ def with_all_rich_text
58
+ eager_load(rich_text_association_names)
59
+ end
60
+
61
+ def rich_text_association_names
62
+ reflect_on_all_associations(:has_one).collect(&:name).select { |n| n.start_with?("rich_text_") }
63
+ end
48
64
  end
49
65
  end
50
66
  end
@@ -58,7 +58,7 @@ module ActionText
58
58
  end
59
59
 
60
60
  def render_attachments(**options, &block)
61
- content = fragment.replace(ActionText::Attachment::SELECTOR) do |node|
61
+ content = fragment.replace(ActionText::Attachment.tag_name) do |node|
62
62
  block.call(attachment_for_node(node, **options))
63
63
  end
64
64
  self.class.new(content, canonicalize: false)
@@ -84,7 +84,11 @@ module ActionText
84
84
  end
85
85
 
86
86
  def to_rendered_html_with_layout
87
- render partial: "action_text/content/layout", formats: :html, locals: { content: self }
87
+ render layout: "action_text/contents/content", partial: to_partial_path, formats: :html, locals: { content: self }
88
+ end
89
+
90
+ def to_partial_path
91
+ "action_text/contents/content"
88
92
  end
89
93
 
90
94
  def to_s
@@ -107,7 +111,7 @@ module ActionText
107
111
 
108
112
  private
109
113
  def attachment_nodes
110
- @attachment_nodes ||= fragment.find_all(ActionText::Attachment::SELECTOR)
114
+ @attachment_nodes ||= fragment.find_all(ActionText::Attachment.tag_name)
111
115
  end
112
116
 
113
117
  def attachment_gallery_nodes
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionText
4
+ module Encryption
5
+ def encrypt
6
+ transaction do
7
+ super
8
+ encrypt_rich_texts if has_encrypted_rich_texts?
9
+ end
10
+ end
11
+
12
+ def decrypt
13
+ transaction do
14
+ super
15
+ decrypt_rich_texts if has_encrypted_rich_texts?
16
+ end
17
+ end
18
+
19
+ private
20
+ def encrypt_rich_texts
21
+ encryptable_rich_texts.each(&:encrypt)
22
+ end
23
+
24
+ def decrypt_rich_texts
25
+ encryptable_rich_texts.each(&:decrypt)
26
+ end
27
+
28
+ def has_encrypted_rich_texts?
29
+ encryptable_rich_texts.present?
30
+ end
31
+
32
+ def encryptable_rich_texts
33
+ @encryptable_rich_texts ||= self.class.rich_text_association_names
34
+ .filter_map { |attribute_name| send(attribute_name) }
35
+ .find_all { |record| record.is_a?(ActionText::EncryptedRichText) }
36
+ end
37
+ end
38
+ end
@@ -11,6 +11,9 @@ module ActionText
11
11
  class Engine < Rails::Engine
12
12
  isolate_namespace ActionText
13
13
  config.eager_load_namespaces << ActionText
14
+
15
+ config.action_text = ActiveSupport::OrderedOptions.new
16
+ config.action_text.attachment_tag_name = "action-text-attachment"
14
17
  config.autoload_once_paths = %W(
15
18
  #{root}/app/helpers
16
19
  #{root}/app/models
@@ -19,6 +22,13 @@ module ActionText
19
22
  initializer "action_text.attribute" do
20
23
  ActiveSupport.on_load(:active_record) do
21
24
  include ActionText::Attribute
25
+ prepend ActionText::Encryption
26
+ end
27
+ end
28
+
29
+ initializer "action_text.asset" do
30
+ if Rails.application.config.respond_to?(:assets)
31
+ Rails.application.config.assets.precompile += %w( actiontext.js trix.js trix.css )
22
32
  end
23
33
  end
24
34
 
@@ -68,5 +78,9 @@ module ActionText
68
78
  include ActionText::SystemTestHelper
69
79
  end
70
80
  end
81
+
82
+ initializer "action_text.configure" do |app|
83
+ ActionText::Attachment.tag_name = app.config.action_text.attachment_tag_name
84
+ end
71
85
  end
72
86
  end
@@ -1,7 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionText
4
+ # Fixtures are a way of organizing data that you want to test against; in
5
+ # short, sample data.
6
+ #
7
+ # To learn more about fixtures, read the
8
+ # {ActiveRecord::FixtureSet}[rdoc-ref:ActiveRecord::FixtureSet] documentation.
9
+ #
10
+ # === YAML
11
+ #
12
+ # Like other Active Record-backed models, ActionText::RichText records inherit
13
+ # from ActiveRecord::Base instances and can therefore be populated by
14
+ # fixtures.
15
+ #
16
+ # Consider an <tt>Article</tt> class:
17
+ #
18
+ # class Article < ApplicationRecord
19
+ # has_rich_text :content
20
+ # end
21
+ #
22
+ # To declare fixture data for the related <tt>content</tt>, first declare fixture
23
+ # data for <tt>Article</tt> instances in <tt>test/fixtures/articles.yml</tt>:
24
+ #
25
+ # first:
26
+ # title: An Article
27
+ #
28
+ # Then declare the <tt>ActionText::RichText</tt> fixture data in
29
+ # <tt>test/fixtures/action_text/rich_texts.yml</tt>, making sure to declare
30
+ # each entry's <tt>record:</tt> key as a polymorphic relationship:
31
+ #
32
+ # first:
33
+ # record: first (Article)
34
+ # name: content
35
+ # body: <div>Hello, world.</div>
36
+ #
37
+ # When processed, Active Record will insert database records for each fixture
38
+ # entry and will ensure the Action Text relationship is intact.
4
39
  class FixtureSet
40
+ # Fixtures support Action Text attachments as part of their <tt>body</tt>
41
+ # HTML.
42
+ #
43
+ # === Examples
44
+ #
45
+ # For example, consider a second <tt>Article</tt> fixture declared in
46
+ # <tt>test/fixtures/articles.yml</tt>:
47
+ #
48
+ # second:
49
+ # title: Another Article
50
+ #
51
+ # You can attach a mention of <tt>articles(:first)</tt> to <tt>second</tt>'s
52
+ # <tt>content</tt> by embedding a call to <tt>ActionText::FixtureSet.attachment</tt>
53
+ # in the <tt>body:</tt> value in <tt>test/fixtures/action_text/rich_texts.yml</tt>:
54
+ #
55
+ # second:
56
+ # record: second (Article)
57
+ # name: content
58
+ # body: <div>Hello, <%= ActionText::FixtureSet.attachment("articles", :first) %></div>
59
+ #
5
60
  def self.attachment(fixture_set_name, label, column_type: :integer)
6
61
  signed_global_id = ActiveRecord::FixtureSet.signed_global_id fixture_set_name, label,
7
62
  column_type: column_type, for: ActionText::Attachable::LOCATOR_NAME
@@ -7,10 +7,10 @@ module ActionText
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 6
11
- MINOR = 1
12
- TINY = 4
13
- PRE = nil
10
+ MAJOR = 7
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -33,10 +33,18 @@ module ActionText
33
33
  "#{remove_trailing_newlines(plain_text_for_node_children(node))}\n\n"
34
34
  end
35
35
 
36
- %i[ h1 p ul ol ].each do |element|
36
+ %i[ h1 p ].each do |element|
37
37
  alias_method :"plain_text_for_#{element}_node", :plain_text_for_block
38
38
  end
39
39
 
40
+ def plain_text_for_list(node, index)
41
+ "#{break_if_nested_list(node, plain_text_for_block(node))}"
42
+ end
43
+
44
+ %i[ ul ol ].each do |element|
45
+ alias_method :"plain_text_for_#{element}_node", :plain_text_for_list
46
+ end
47
+
40
48
  def plain_text_for_br_node(node, index)
41
49
  "\n"
42
50
  end
@@ -61,7 +69,9 @@ module ActionText
61
69
  def plain_text_for_li_node(node, index)
62
70
  bullet = bullet_for_li_node(node, index)
63
71
  text = remove_trailing_newlines(plain_text_for_node_children(node))
64
- "#{bullet} #{text}\n"
72
+ indentation = indentation_for_li_node(node)
73
+
74
+ "#{indentation}#{bullet} #{text}\n"
65
75
  end
66
76
 
67
77
  def remove_trailing_newlines(text)
@@ -79,5 +89,24 @@ module ActionText
79
89
  def list_node_name_for_li_node(node)
80
90
  node.ancestors.lazy.map(&:name).grep(/^[uo]l$/).first
81
91
  end
92
+
93
+ def indentation_for_li_node(node)
94
+ depth = list_node_depth_for_node(node)
95
+ if depth > 1
96
+ " " * (depth - 1)
97
+ end
98
+ end
99
+
100
+ def list_node_depth_for_node(node)
101
+ node.ancestors.map(&:name).grep(/^[uo]l$/).count
102
+ end
103
+
104
+ def break_if_nested_list(node, text)
105
+ if list_node_depth_for_node(node) > 0
106
+ "\n#{text}"
107
+ else
108
+ text
109
+ end
110
+ end
82
111
  end
83
112
  end
@@ -4,7 +4,7 @@ require "active_support/concern"
4
4
  require "active_support/core_ext/module/attribute_accessors_per_thread"
5
5
 
6
6
  module ActionText
7
- module Rendering #:nodoc:
7
+ module Rendering # :nodoc:
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  included do
@@ -15,6 +15,8 @@ module ActionText
15
15
  nil
16
16
  when self
17
17
  content.to_html
18
+ when ActionText::RichText
19
+ content.body.to_html
18
20
  else
19
21
  new(content).to_html
20
22
  end
@@ -9,9 +9,9 @@ module ActionText
9
9
  ATTRIBUTES = %w( sgid contentType url href filename filesize width height previewable content ) + COMPOSED_ATTRIBUTES
10
10
  ATTRIBUTE_TYPES = {
11
11
  "previewable" => ->(value) { value.to_s == "true" },
12
- "filesize" => ->(value) { Integer(value.to_s) rescue value },
13
- "width" => ->(value) { Integer(value.to_s) rescue nil },
14
- "height" => ->(value) { Integer(value.to_s) rescue nil },
12
+ "filesize" => ->(value) { Integer(value.to_s, exception: false) || value },
13
+ "width" => ->(value) { Integer(value.to_s, exception: false) },
14
+ "height" => ->(value) { Integer(value.to_s, exception: false) },
15
15
  :default => ->(value) { value.to_s }
16
16
  }
17
17
 
data/lib/action_text.rb CHANGED
@@ -13,6 +13,7 @@ module ActionText
13
13
  autoload :Attachment
14
14
  autoload :Attribute
15
15
  autoload :Content
16
+ autoload :Encryption
16
17
  autoload :Fragment
17
18
  autoload :FixtureSet
18
19
  autoload :HtmlConversion
@@ -9,43 +9,57 @@ module ActionText
9
9
  source_root File.expand_path("templates", __dir__)
10
10
 
11
11
  def install_javascript_dependencies
12
- rails_command "app:binstub:yarn", inline: true
13
-
14
- say "Installing JavaScript dependencies", :green
15
- run "#{Thor::Util.ruby_command} bin/yarn add #{js_dependencies.map { |name, version| "#{name}@#{version}" }.join(" ")}",
16
- abort_on_failure: true, capture: true
12
+ if Pathname(destination_root).join("package.json").exist?
13
+ say "Installing JavaScript dependencies", :green
14
+ run "yarn add @rails/actiontext trix"
15
+ end
17
16
  end
18
17
 
19
- def append_dependencies_to_package_file
20
- if (app_javascript_pack_path = Pathname.new("app/javascript/packs/application.js")).exist?
21
- js_dependencies.each_key do |dependency|
22
- line = %[require("#{dependency}")]
18
+ def append_javascript_dependencies
19
+ destination = Pathname(destination_root)
23
20
 
24
- unless app_javascript_pack_path.read.include? line
25
- say "Adding #{dependency} to #{app_javascript_pack_path}", :green
26
- append_to_file app_javascript_pack_path, "\n#{line}"
27
- end
28
- end
21
+ if (application_javascript_path = destination.join("app/javascript/application.js")).exist?
22
+ insert_into_file application_javascript_path.to_s, %(import "trix"\nimport "@rails/actiontext"\n)
29
23
  else
30
- say <<~WARNING, :red
31
- WARNING: Action Text can't locate your JavaScript bundle to add its package dependencies.
32
-
33
- Add these lines to any bundles:
34
-
35
- require("trix")
36
- require("@rails/actiontext")
24
+ say <<~INSTRUCTIONS, :green
25
+ You must import the @rails/actiontext and trix JavaScript modules in your application entrypoint.
26
+ INSTRUCTIONS
27
+ end
37
28
 
38
- Alternatively, install and setup the webpacker gem then rerun `bin/rails action_text:install`
39
- to have these dependencies added automatically.
40
- WARNING
29
+ if (importmap_path = destination.join("config/importmap.rb")).exist?
30
+ append_to_file importmap_path.to_s, %(pin "trix"\npin "@rails/actiontext", to: "actiontext.js"\n)
41
31
  end
42
32
  end
43
33
 
44
34
  def create_actiontext_files
45
- template "actiontext.scss", "app/assets/stylesheets/actiontext.scss"
35
+ destination = Pathname(destination_root)
36
+
37
+ template "actiontext.css", "app/assets/stylesheets/actiontext.css"
38
+
39
+ unless destination.join("app/assets/application.css").exist?
40
+ if (stylesheets = Dir.glob "#{destination_root}/app/assets/stylesheets/application.*.{scss,css}").length > 0
41
+ insert_into_file stylesheets.first.to_s, %(@import 'actiontext.css';)
42
+ else
43
+ say <<~INSTRUCTIONS, :green
44
+ To use the Trix editor, you must require 'app/assets/stylesheets/actiontext.css' in your base stylesheet.
45
+ INSTRUCTIONS
46
+ end
47
+ end
48
+
49
+ gem_root = "#{__dir__}/../../../.."
46
50
 
47
- copy_file "#{GEM_ROOT}/app/views/active_storage/blobs/_blob.html.erb",
51
+ copy_file "#{gem_root}/app/views/active_storage/blobs/_blob.html.erb",
48
52
  "app/views/active_storage/blobs/_blob.html.erb"
53
+
54
+ copy_file "#{gem_root}/app/views/layouts/action_text/contents/_content.html.erb",
55
+ "app/views/layouts/action_text/contents/_content.html.erb"
56
+ end
57
+
58
+ def enable_image_processing_gem
59
+ if (gemfile_path = Pathname(destination_root).join("Gemfile")).exist?
60
+ say "Ensure image_processing gem has been enabled so image uploads will work (remember to bundle!)"
61
+ uncomment_lines gemfile_path, /gem "image_processing"/
62
+ end
49
63
  end
50
64
 
51
65
  def create_migrations
@@ -53,15 +67,6 @@ module ActionText
53
67
  end
54
68
 
55
69
  hook_for :test_framework
56
-
57
- private
58
- GEM_ROOT = "#{__dir__}/../../../.."
59
-
60
- def js_dependencies
61
- js_package = JSON.load(Pathname.new("#{GEM_ROOT}/package.json"))
62
- js_package["peerDependencies"].merge \
63
- js_package["name"] => "^#{js_package["version"]}"
64
- end
65
70
  end
66
71
  end
67
72
  end
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
3
+ * the trix-editor content (whether displayed or under editing). Feel free to incorporate this
4
+ * inclusion directly in any other asset bundle and remove this file.
5
+ *
6
+ <%- if defined?(Webpacker::Engine) -%>
7
+ *= require trix/dist/trix
8
+ <%- else -%>
9
+ *= require trix
10
+ <% end -%>
11
+ */
12
+
13
+ /*
14
+ * We need to override trix.css’s image gallery styles to accommodate the
15
+ * <action-text-attachment> element we wrap around attachments. Otherwise,
16
+ * images in galleries will be squished by the max-width: 33%; rule.
17
+ */
18
+ .trix-content .attachment-gallery > action-text-attachment,
19
+ .trix-content .attachment-gallery > .attachment {
20
+ flex: 1 0 33%;
21
+ padding: 0 0.5em;
22
+ max-width: 33%;
23
+ }
24
+
25
+ .trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
26
+ .trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
27
+ .trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
28
+ flex-basis: 50%;
29
+ max-width: 50%;
30
+ }
31
+
32
+ .trix-content action-text-attachment .attachment {
33
+ padding: 0 !important;
34
+ max-width: 100% !important;
35
+ }
data/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@rails/actiontext",
3
- "version": "6.1.4",
3
+ "version": "7.0.0-rc1",
4
4
  "description": "Edit and display rich text in Rails applications",
5
5
  "main": "app/javascript/actiontext/index.js",
6
+ "type": "module",
6
7
  "files": [
7
8
  "app/javascript/actiontext/*.js"
8
9
  ],
@@ -21,9 +22,18 @@
21
22
  ],
22
23
  "license": "MIT",
23
24
  "dependencies": {
24
- "@rails/activestorage": "^6.0.0"
25
+ "@rails/activestorage": ">= 7.0.0-alpha1"
25
26
  },
26
27
  "peerDependencies": {
27
- "trix": "^1.2.0"
28
+ "trix": "^1.3.1"
29
+ },
30
+ "devDependencies": {
31
+ "@rollup/plugin-node-resolve": "^11.0.1",
32
+ "@rollup/plugin-commonjs": "^19.0.1",
33
+ "rollup": "^2.35.1",
34
+ "trix": "^1.3.1"
35
+ },
36
+ "scripts": {
37
+ "build": "rollup --config rollup.config.js"
28
38
  }
29
39
  }