actiontext 6.1.4 → 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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
  }