actiontext 6.1.4.1 → 7.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -91
- data/MIT-LICENSE +1 -1
- data/README.md +4 -0
- data/app/assets/javascripts/actiontext.js +880 -0
- data/app/assets/javascripts/trix.js +5278 -0
- data/app/assets/stylesheets/trix.css +375 -0
- data/app/helpers/action_text/content_helper.rb +17 -3
- data/app/helpers/action_text/tag_helper.rb +5 -7
- data/app/models/action_text/encrypted_rich_text.rb +9 -0
- data/app/models/action_text/record.rb +1 -1
- data/app/models/action_text/rich_text.rb +4 -0
- data/app/views/action_text/contents/_content.html.erb +1 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/db/migrate/20180528164100_create_action_text_tables.rb +14 -2
- data/lib/action_text/attachable.rb +4 -0
- data/lib/action_text/attachment.rb +4 -4
- data/lib/action_text/attachment_gallery.rb +14 -9
- data/lib/action_text/attachments/caching.rb +1 -1
- data/lib/action_text/attachments/minification.rb +1 -1
- data/lib/action_text/attribute.rb +18 -2
- data/lib/action_text/content.rb +7 -3
- data/lib/action_text/encryption.rb +38 -0
- data/lib/action_text/engine.rb +14 -0
- data/lib/action_text/fixture_set.rb +49 -0
- data/lib/action_text/gem_version.rb +4 -4
- data/lib/action_text/rendering.rb +1 -1
- data/lib/action_text/trix_attachment.rb +3 -3
- data/lib/action_text.rb +1 -0
- data/lib/generators/action_text/install/install_generator.rb +28 -35
- data/lib/generators/action_text/install/templates/actiontext.css +35 -0
- data/package.json +13 -3
- metadata +28 -22
- data/app/views/action_text/content/_layout.html.erb +0 -3
- data/lib/generators/action_text/install/templates/actiontext.scss +0 -36
@@ -0,0 +1 @@
|
|
1
|
+
<%= render_action_text_content(content) %>
|
@@ -1,13 +1,25 @@
|
|
1
1
|
class CreateActionTextTables < ActiveRecord::Migration[6.0]
|
2
2
|
def change
|
3
|
-
|
3
|
+
# Use Active Record's configured type for primary and foreign keys
|
4
|
+
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
5
|
+
|
6
|
+
create_table :action_text_rich_texts, id: primary_key_type do |t|
|
4
7
|
t.string :name, null: false
|
5
8
|
t.text :body, size: :long
|
6
|
-
t.references :record, null: false, polymorphic: true, index: false
|
9
|
+
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
|
7
10
|
|
8
11
|
t.timestamps
|
9
12
|
|
10
13
|
t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
|
11
14
|
end
|
12
15
|
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def primary_and_foreign_key_types
|
19
|
+
config = Rails.configuration.generators
|
20
|
+
setting = config.options[config.orm][:primary_key_type]
|
21
|
+
primary_key_type = setting || :primary_key
|
22
|
+
foreign_key_type = setting || :bigint
|
23
|
+
[primary_key_type, foreign_key_type]
|
24
|
+
end
|
13
25
|
end
|
@@ -6,8 +6,8 @@ module ActionText
|
|
6
6
|
class Attachment
|
7
7
|
include Attachments::TrixConversion, Attachments::Minification, Attachments::Caching
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
mattr_accessor :tag_name, default: "action-text-attachment"
|
10
|
+
|
11
11
|
ATTRIBUTES = %w( sgid content-type url href filename filesize width height previewable presentation caption )
|
12
12
|
|
13
13
|
class << self
|
@@ -20,7 +20,7 @@ module ActionText
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def from_attachables(attachables)
|
23
|
-
Array(attachables).
|
23
|
+
Array(attachables).filter_map { |attachable| from_attachable(attachable) }
|
24
24
|
end
|
25
25
|
|
26
26
|
def from_attachable(attachable, attributes = {})
|
@@ -38,7 +38,7 @@ module ActionText
|
|
38
38
|
private
|
39
39
|
def node_from_attributes(attributes)
|
40
40
|
if attributes = process_attributes(attributes).presence
|
41
|
-
ActionText::HtmlConversion.create_element(
|
41
|
+
ActionText::HtmlConversion.create_element(tag_name, attributes)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -4,6 +4,9 @@ module ActionText
|
|
4
4
|
class AttachmentGallery
|
5
5
|
include ActiveModel::Model
|
6
6
|
|
7
|
+
TAG_NAME = "div"
|
8
|
+
private_constant :TAG_NAME
|
9
|
+
|
7
10
|
class << self
|
8
11
|
def fragment_by_canonicalizing_attachment_galleries(content)
|
9
12
|
fragment_by_replacing_attachment_gallery_nodes(content) do |node|
|
@@ -20,12 +23,12 @@ module ActionText
|
|
20
23
|
end
|
21
24
|
|
22
25
|
def find_attachment_gallery_nodes(content)
|
23
|
-
Fragment.wrap(content).find_all(
|
26
|
+
Fragment.wrap(content).find_all(selector).select do |node|
|
24
27
|
node.children.all? do |child|
|
25
28
|
if child.text?
|
26
29
|
/\A(\n|\ )*\z/.match?(child.text)
|
27
30
|
else
|
28
|
-
child.matches?
|
31
|
+
child.matches? attachment_selector
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
@@ -34,6 +37,14 @@ module ActionText
|
|
34
37
|
def from_node(node)
|
35
38
|
new(node)
|
36
39
|
end
|
40
|
+
|
41
|
+
def attachment_selector
|
42
|
+
"#{ActionText::Attachment.tag_name}[presentation=gallery]"
|
43
|
+
end
|
44
|
+
|
45
|
+
def selector
|
46
|
+
"#{TAG_NAME}:has(#{attachment_selector} + #{attachment_selector})"
|
47
|
+
end
|
37
48
|
end
|
38
49
|
|
39
50
|
attr_reader :node
|
@@ -43,7 +54,7 @@ module ActionText
|
|
43
54
|
end
|
44
55
|
|
45
56
|
def attachments
|
46
|
-
@attachments ||= node.css(
|
57
|
+
@attachments ||= node.css(ActionText::AttachmentGallery.attachment_selector).map do |node|
|
47
58
|
ActionText::Attachment.from_node(node).with_full_attributes
|
48
59
|
end
|
49
60
|
end
|
@@ -55,11 +66,5 @@ module ActionText
|
|
55
66
|
def inspect
|
56
67
|
"#<#{self.class.name} size=#{size.inspect}>"
|
57
68
|
end
|
58
|
-
|
59
|
-
TAG_NAME = "div"
|
60
|
-
ATTACHMENT_SELECTOR = "#{ActionText::Attachment::SELECTOR}[presentation=gallery]"
|
61
|
-
SELECTOR = "#{TAG_NAME}:has(#{ATTACHMENT_SELECTOR} + #{ATTACHMENT_SELECTOR})"
|
62
|
-
|
63
|
-
private_constant :TAG_NAME, :ATTACHMENT_SELECTOR, :SELECTOR
|
64
69
|
end
|
65
70
|
end
|
@@ -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
|
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
|
-
|
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:
|
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
|
data/lib/action_text/content.rb
CHANGED
@@ -58,7 +58,7 @@ module ActionText
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def render_attachments(**options, &block)
|
61
|
-
content = fragment.replace(ActionText::Attachment
|
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
|
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
|
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
|
data/lib/action_text/engine.rb
CHANGED
@@ -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,56 @@
|
|
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 therefore can be populated by
|
14
|
+
# fixtures.
|
15
|
+
#
|
16
|
+
# Consider a hypothetical <tt>Article</tt> model class, its related fixture
|
17
|
+
# data, as well as fixture data for related ActionText::RichText records:
|
18
|
+
#
|
19
|
+
# # app/models/article.rb
|
20
|
+
# class Article < ApplicationRecord
|
21
|
+
# has_rich_text :content
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # tests/fixtures/articles.yml
|
25
|
+
# first:
|
26
|
+
# title: An Article
|
27
|
+
#
|
28
|
+
# # tests/fixtures/action_text/rich_texts.yml
|
29
|
+
# first_content:
|
30
|
+
# record: first (Article)
|
31
|
+
# name: content
|
32
|
+
# body: <div>Hello, world.</div>
|
33
|
+
#
|
34
|
+
# When processed, Active Record will insert database records for each fixture
|
35
|
+
# entry and will ensure the Action Text relationship is intact.
|
4
36
|
class FixtureSet
|
37
|
+
# Fixtures support Action Text attachments as part of their <tt>body</tt>
|
38
|
+
# HTML.
|
39
|
+
#
|
40
|
+
# === Examples
|
41
|
+
#
|
42
|
+
# For example, consider a second <tt>Article</tt> record that mentions the
|
43
|
+
# first as part of its <tt>content</tt> HTML:
|
44
|
+
#
|
45
|
+
# # tests/fixtures/articles.yml
|
46
|
+
# second:
|
47
|
+
# title: Another Article
|
48
|
+
#
|
49
|
+
# # tests/fixtures/action_text/rich_texts.yml
|
50
|
+
# second_content:
|
51
|
+
# record: second (Article)
|
52
|
+
# name: content
|
53
|
+
# body: <div>Hello, <%= ActionText::FixtureSet.attachment("articles", :first) %></div>
|
5
54
|
def self.attachment(fixture_set_name, label, column_type: :integer)
|
6
55
|
signed_global_id = ActiveRecord::FixtureSet.signed_global_id fixture_set_name, label,
|
7
56
|
column_type: column_type, for: ActionText::Attachable::LOCATOR_NAME
|
@@ -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)
|
13
|
-
"width" => ->(value) { Integer(value.to_s
|
14
|
-
"height" => ->(value) { Integer(value.to_s
|
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
@@ -9,43 +9,45 @@ module ActionText
|
|
9
9
|
source_root File.expand_path("templates", __dir__)
|
10
10
|
|
11
11
|
def install_javascript_dependencies
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
20
|
-
|
21
|
-
js_dependencies.each_key do |dependency|
|
22
|
-
line = %[require("#{dependency}")]
|
18
|
+
def append_javascript_dependencies
|
19
|
+
destination = Pathname(destination_root)
|
23
20
|
|
24
|
-
|
25
|
-
|
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 <<~
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
39
|
-
|
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.
|
35
|
+
template "actiontext.css", "app/assets/stylesheets/actiontext.css"
|
36
|
+
|
37
|
+
gem_root = "#{__dir__}/../../../.."
|
46
38
|
|
47
|
-
copy_file "#{
|
39
|
+
copy_file "#{gem_root}/app/views/active_storage/blobs/_blob.html.erb",
|
48
40
|
"app/views/active_storage/blobs/_blob.html.erb"
|
41
|
+
|
42
|
+
copy_file "#{gem_root}/app/views/layouts/action_text/contents/_content.html.erb",
|
43
|
+
"app/views/layouts/action_text/contents/_content.html.erb"
|
44
|
+
end
|
45
|
+
|
46
|
+
def enable_image_processing_gem
|
47
|
+
if (gemfile_path = Pathname(destination_root).join("Gemfile")).exist?
|
48
|
+
say "Ensure image_processing gem has been enabled so image uploads will work (remember to bundle!)"
|
49
|
+
uncomment_lines gemfile_path, /gem "image_processing"/
|
50
|
+
end
|
49
51
|
end
|
50
52
|
|
51
53
|
def create_migrations
|
@@ -53,15 +55,6 @@ module ActionText
|
|
53
55
|
end
|
54
56
|
|
55
57
|
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
58
|
end
|
66
59
|
end
|
67
60
|
end
|