omg-actiontext 8.0.0.alpha3
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +42 -0
- data/MIT-LICENSE +21 -0
- data/README.md +13 -0
- data/app/assets/javascripts/actiontext.esm.js +911 -0
- data/app/assets/javascripts/actiontext.js +884 -0
- data/app/assets/javascripts/trix.js +12165 -0
- data/app/assets/stylesheets/trix.css +412 -0
- data/app/helpers/action_text/content_helper.rb +76 -0
- data/app/helpers/action_text/tag_helper.rb +106 -0
- data/app/javascript/actiontext/attachment_upload.js +62 -0
- data/app/javascript/actiontext/index.js +10 -0
- data/app/models/action_text/encrypted_rich_text.rb +11 -0
- data/app/models/action_text/record.rb +11 -0
- data/app/models/action_text/rich_text.rb +93 -0
- data/app/views/action_text/attachables/_content_attachment.html.erb +3 -0
- data/app/views/action_text/attachables/_missing_attachable.html.erb +1 -0
- data/app/views/action_text/attachables/_remote_image.html.erb +8 -0
- data/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb +3 -0
- data/app/views/action_text/contents/_content.html.erb +1 -0
- data/app/views/active_storage/blobs/_blob.html.erb +14 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/db/migrate/20180528164100_create_action_text_tables.rb +25 -0
- data/lib/action_text/attachable.rb +156 -0
- data/lib/action_text/attachables/content_attachment.rb +42 -0
- data/lib/action_text/attachables/missing_attachable.rb +29 -0
- data/lib/action_text/attachables/remote_image.rb +48 -0
- data/lib/action_text/attachment.rb +148 -0
- data/lib/action_text/attachment_gallery.rb +72 -0
- data/lib/action_text/attachments/caching.rb +18 -0
- data/lib/action_text/attachments/minification.rb +19 -0
- data/lib/action_text/attachments/trix_conversion.rb +38 -0
- data/lib/action_text/attribute.rb +105 -0
- data/lib/action_text/content.rb +197 -0
- data/lib/action_text/deprecator.rb +9 -0
- data/lib/action_text/encryption.rb +40 -0
- data/lib/action_text/engine.rb +94 -0
- data/lib/action_text/fixture_set.rb +68 -0
- data/lib/action_text/fragment.rb +62 -0
- data/lib/action_text/gem_version.rb +19 -0
- data/lib/action_text/html_conversion.rb +26 -0
- data/lib/action_text/plain_text_conversion.rb +114 -0
- data/lib/action_text/rendering.rb +35 -0
- data/lib/action_text/serialization.rb +38 -0
- data/lib/action_text/system_test_helper.rb +61 -0
- data/lib/action_text/trix_attachment.rb +94 -0
- data/lib/action_text/version.rb +12 -0
- data/lib/action_text.rb +59 -0
- data/lib/generators/action_text/install/install_generator.rb +84 -0
- data/lib/generators/action_text/install/templates/actiontext.css +440 -0
- data/lib/rails/generators/test_unit/install_generator.rb +15 -0
- data/lib/rails/generators/test_unit/templates/fixtures.yml +4 -0
- data/lib/tasks/actiontext.rake +6 -0
- data/package.json +39 -0
- metadata +190 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
module Attachments
|
7
|
+
module Minification
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def fragment_by_minifying_attachments(content)
|
12
|
+
Fragment.wrap(content).replace(ActionText::Attachment.tag_name) do |node|
|
13
|
+
node.tap { |n| n.inner_html = "" }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/core_ext/object/try"
|
6
|
+
|
7
|
+
module ActionText
|
8
|
+
module Attachments
|
9
|
+
module TrixConversion
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def fragment_by_converting_trix_attachments(content)
|
14
|
+
Fragment.wrap(content).replace(TrixAttachment::SELECTOR) do |node|
|
15
|
+
from_trix_attachment(TrixAttachment.new(node))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_trix_attachment(trix_attachment)
|
20
|
+
from_attributes(trix_attachment.attributes)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_trix_attachment(content = trix_attachment_content)
|
25
|
+
attributes = full_attributes.dup
|
26
|
+
attributes["content"] = content if content
|
27
|
+
TrixAttachment.from_attributes(attributes)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def trix_attachment_content
|
32
|
+
if partial_path = attachable.try(:to_trix_content_attachment_partial_path)
|
33
|
+
ActionText::Content.render(partial: partial_path, formats: :html, object: self, as: model_name.element)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
module Attribute
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
# Provides access to a dependent RichText model that holds the body and
|
11
|
+
# attachments for a single named rich text attribute. This dependent attribute
|
12
|
+
# is lazily instantiated and will be auto-saved when it's been changed. Example:
|
13
|
+
#
|
14
|
+
# class Message < ActiveRecord::Base
|
15
|
+
# has_rich_text :content
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# message = Message.create!(content: "<h1>Funny times!</h1>")
|
19
|
+
# message.content? #=> true
|
20
|
+
# message.content.to_s # => "<h1>Funny times!</h1>"
|
21
|
+
# message.content.to_plain_text # => "Funny times!"
|
22
|
+
#
|
23
|
+
# The dependent RichText model will also automatically process attachments links
|
24
|
+
# as sent via the Trix-powered editor. These attachments are associated with the
|
25
|
+
# RichText model using Active Storage.
|
26
|
+
#
|
27
|
+
# If you wish to preload the dependent RichText model, you can use the named
|
28
|
+
# scope:
|
29
|
+
#
|
30
|
+
# Message.all.with_rich_text_content # Avoids N+1 queries when you just want the body, not the attachments.
|
31
|
+
# Message.all.with_rich_text_content_and_embeds # Avoids N+1 queries when you just want the body and attachments.
|
32
|
+
# Message.all.with_all_rich_text # Loads all rich text associations.
|
33
|
+
#
|
34
|
+
# #### Options
|
35
|
+
#
|
36
|
+
# * `:encrypted` - Pass true to encrypt the rich text attribute. The
|
37
|
+
# encryption will be non-deterministic. See
|
38
|
+
# `ActiveRecord::Encryption::EncryptableRecord.encrypts`. Default: false.
|
39
|
+
#
|
40
|
+
# * `:strict_loading` - Pass true to force strict loading. When omitted,
|
41
|
+
# `strict_loading:` will be set to the value of the
|
42
|
+
# `strict_loading_by_default` class attribute (false by default).
|
43
|
+
#
|
44
|
+
# * `:store_if_blank` - Pass false to not create RichText records with empty values,
|
45
|
+
# if a blank value is provided. Default: true.
|
46
|
+
#
|
47
|
+
#
|
48
|
+
# Note: Action Text relies on polymorphic associations, which in turn store
|
49
|
+
# class names in the database. When renaming classes that use `has_rich_text`,
|
50
|
+
# make sure to also update the class names in the
|
51
|
+
# `action_text_rich_texts.record_type` polymorphic type column of the
|
52
|
+
# corresponding rows.
|
53
|
+
def has_rich_text(name, encrypted: false, strict_loading: strict_loading_by_default, store_if_blank: true)
|
54
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
55
|
+
def #{name}
|
56
|
+
rich_text_#{name} || build_rich_text_#{name}
|
57
|
+
end
|
58
|
+
|
59
|
+
def #{name}?
|
60
|
+
rich_text_#{name}.present?
|
61
|
+
end
|
62
|
+
CODE
|
63
|
+
|
64
|
+
if store_if_blank
|
65
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
66
|
+
def #{name}=(body)
|
67
|
+
self.#{name}.body = body
|
68
|
+
end
|
69
|
+
CODE
|
70
|
+
else
|
71
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
72
|
+
def #{name}=(body)
|
73
|
+
if body.present?
|
74
|
+
self.#{name}.body = body
|
75
|
+
else
|
76
|
+
if #{name}?
|
77
|
+
self.#{name}.body = body
|
78
|
+
self.#{name}.mark_for_destruction
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
CODE
|
83
|
+
end
|
84
|
+
|
85
|
+
rich_text_class_name = encrypted ? "ActionText::EncryptedRichText" : "ActionText::RichText"
|
86
|
+
has_one :"rich_text_#{name}", -> { where(name: name) },
|
87
|
+
class_name: rich_text_class_name, as: :record, inverse_of: :record, autosave: true, dependent: :destroy,
|
88
|
+
strict_loading: strict_loading
|
89
|
+
|
90
|
+
scope :"with_rich_text_#{name}", -> { includes("rich_text_#{name}") }
|
91
|
+
scope :"with_rich_text_#{name}_and_embeds", -> { includes("rich_text_#{name}": { embeds_attachments: :blob }) }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Eager load all dependent RichText models in bulk.
|
95
|
+
def with_all_rich_text
|
96
|
+
includes(rich_text_association_names)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the names of all rich text associations.
|
100
|
+
def rich_text_association_names
|
101
|
+
reflect_on_all_associations(:has_one).collect(&:name).select { |n| n.start_with?("rich_text_") }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
# # Action Text Content
|
7
|
+
#
|
8
|
+
# The `ActionText::Content` class wraps an HTML fragment to add support for
|
9
|
+
# parsing, rendering and serialization. It can be used to extract links and
|
10
|
+
# attachments, convert the fragment to plain text, or serialize the fragment to
|
11
|
+
# the database.
|
12
|
+
#
|
13
|
+
# The ActionText::RichText record serializes the `body` attribute as
|
14
|
+
# `ActionText::Content`.
|
15
|
+
#
|
16
|
+
# class Message < ActiveRecord::Base
|
17
|
+
# has_rich_text :content
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# message = Message.create!(content: "<h1>Funny times!</h1>")
|
21
|
+
# body = message.content.body # => #<ActionText::Content "<div class=\"trix-conte...">
|
22
|
+
# body.to_s # => "<h1>Funny times!</h1>"
|
23
|
+
# body.to_plain_text # => "Funny times!"
|
24
|
+
class Content
|
25
|
+
include Rendering, Serialization, ContentHelper
|
26
|
+
|
27
|
+
attr_reader :fragment
|
28
|
+
|
29
|
+
delegate :deconstruct, to: :fragment
|
30
|
+
delegate :blank?, :empty?, :html_safe, :present?, to: :to_html # Delegating to to_html to avoid including the layout
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def fragment_by_canonicalizing_content(content)
|
34
|
+
fragment = ActionText::Attachment.fragment_by_canonicalizing_attachments(content)
|
35
|
+
fragment = ActionText::AttachmentGallery.fragment_by_canonicalizing_attachment_galleries(fragment)
|
36
|
+
fragment
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(content = nil, options = {})
|
41
|
+
options.with_defaults! canonicalize: true
|
42
|
+
|
43
|
+
if options[:canonicalize]
|
44
|
+
@fragment = self.class.fragment_by_canonicalizing_content(content)
|
45
|
+
else
|
46
|
+
@fragment = ActionText::Fragment.wrap(content)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Extracts links from the HTML fragment:
|
51
|
+
#
|
52
|
+
# html = '<a href="http://example.com/">Example</a>'
|
53
|
+
# content = ActionText::Content.new(html)
|
54
|
+
# content.links # => ["http://example.com/"]
|
55
|
+
def links
|
56
|
+
@links ||= fragment.find_all("a[href]").map { |a| a["href"] }.uniq
|
57
|
+
end
|
58
|
+
|
59
|
+
# Extracts +ActionText::Attachment+s from the HTML fragment:
|
60
|
+
#
|
61
|
+
# attachable = ActiveStorage::Blob.first
|
62
|
+
# html = %Q(<action-text-attachment sgid="#{attachable.attachable_sgid}" caption="Captioned"></action-text-attachment>)
|
63
|
+
# content = ActionText::Content.new(html)
|
64
|
+
# content.attachments # => [#<ActionText::Attachment attachable=#<ActiveStorage::Blob...
|
65
|
+
def attachments
|
66
|
+
@attachments ||= attachment_nodes.map do |node|
|
67
|
+
attachment_for_node(node)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def attachment_galleries
|
72
|
+
@attachment_galleries ||= attachment_gallery_nodes.map do |node|
|
73
|
+
attachment_gallery_for_node(node)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def gallery_attachments
|
78
|
+
@gallery_attachments ||= attachment_galleries.flat_map(&:attachments)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Extracts +ActionText::Attachable+s from the HTML fragment:
|
82
|
+
#
|
83
|
+
# attachable = ActiveStorage::Blob.first
|
84
|
+
# html = %Q(<action-text-attachment sgid="#{attachable.attachable_sgid}" caption="Captioned"></action-text-attachment>)
|
85
|
+
# content = ActionText::Content.new(html)
|
86
|
+
# content.attachables # => [attachable]
|
87
|
+
def attachables
|
88
|
+
@attachables ||= attachment_nodes.map do |node|
|
89
|
+
ActionText::Attachable.from_node(node)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def append_attachables(attachables)
|
94
|
+
attachments = ActionText::Attachment.from_attachables(attachables)
|
95
|
+
self.class.new([self.to_s.presence, *attachments].compact.join("\n"))
|
96
|
+
end
|
97
|
+
|
98
|
+
def render_attachments(**options, &block)
|
99
|
+
content = fragment.replace(ActionText::Attachment.tag_name) do |node|
|
100
|
+
if node.key?("content")
|
101
|
+
sanitized_content = sanitize_content_attachment(node.remove_attribute("content").to_s)
|
102
|
+
node["content"] = sanitized_content if sanitized_content.present?
|
103
|
+
end
|
104
|
+
block.call(attachment_for_node(node, **options))
|
105
|
+
end
|
106
|
+
self.class.new(content, canonicalize: false)
|
107
|
+
end
|
108
|
+
|
109
|
+
def render_attachment_galleries(&block)
|
110
|
+
content = ActionText::AttachmentGallery.fragment_by_replacing_attachment_gallery_nodes(fragment) do |node|
|
111
|
+
block.call(attachment_gallery_for_node(node))
|
112
|
+
end
|
113
|
+
self.class.new(content, canonicalize: false)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns a plain-text version of the markup contained by the content, with tags
|
117
|
+
# removed but HTML entities encoded.
|
118
|
+
#
|
119
|
+
# content = ActionText::Content.new("<h1>Funny times!</h1>")
|
120
|
+
# content.to_plain_text # => "Funny times!"
|
121
|
+
#
|
122
|
+
# content = ActionText::Content.new("<div onclick='action()'>safe<script>unsafe</script></div>")
|
123
|
+
# content.to_plain_text # => "safeunsafe"
|
124
|
+
#
|
125
|
+
# NOTE: that the returned string is not HTML safe and should not be rendered in
|
126
|
+
# browsers.
|
127
|
+
#
|
128
|
+
# content = ActionText::Content.new("<script>alert()</script>")
|
129
|
+
# content.to_plain_text # => "<script>alert()</script>"
|
130
|
+
def to_plain_text
|
131
|
+
render_attachments(with_full_attributes: false, &:to_plain_text).fragment.to_plain_text
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_trix_html
|
135
|
+
render_attachments(&:to_trix_attachment).to_html
|
136
|
+
end
|
137
|
+
|
138
|
+
def to_html
|
139
|
+
fragment.to_html
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_rendered_html_with_layout
|
143
|
+
render layout: "action_text/contents/content", partial: to_partial_path, formats: :html, locals: { content: self }
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_partial_path
|
147
|
+
"action_text/contents/content"
|
148
|
+
end
|
149
|
+
|
150
|
+
# Safely transforms Content into an HTML String.
|
151
|
+
#
|
152
|
+
# content = ActionText::Content.new(content: "<h1>Funny times!</h1>")
|
153
|
+
# content.to_s # => "<h1>Funny times!</h1>"
|
154
|
+
#
|
155
|
+
# content = ActionText::Content.new("<div onclick='action()'>safe<script>unsafe</script></div>")
|
156
|
+
# content.to_s # => "<div>safeunsafe</div>"
|
157
|
+
def to_s
|
158
|
+
to_rendered_html_with_layout
|
159
|
+
end
|
160
|
+
|
161
|
+
def as_json(*)
|
162
|
+
to_html
|
163
|
+
end
|
164
|
+
|
165
|
+
def inspect
|
166
|
+
"#<#{self.class.name} #{to_html.truncate(25).inspect}>"
|
167
|
+
end
|
168
|
+
|
169
|
+
def ==(other)
|
170
|
+
if self.class == other.class
|
171
|
+
to_html == other.to_html
|
172
|
+
elsif other.is_a?(self.class)
|
173
|
+
to_s == other.to_s
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
def attachment_nodes
|
179
|
+
@attachment_nodes ||= fragment.find_all(ActionText::Attachment.tag_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
def attachment_gallery_nodes
|
183
|
+
@attachment_gallery_nodes ||= ActionText::AttachmentGallery.find_attachment_gallery_nodes(fragment)
|
184
|
+
end
|
185
|
+
|
186
|
+
def attachment_for_node(node, with_full_attributes: true)
|
187
|
+
attachment = ActionText::Attachment.from_node(node)
|
188
|
+
with_full_attributes ? attachment.with_full_attributes : attachment
|
189
|
+
end
|
190
|
+
|
191
|
+
def attachment_gallery_for_node(node)
|
192
|
+
ActionText::AttachmentGallery.from_node(node)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
ActiveSupport.run_load_hooks :action_text_content, ActionText::Content
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
module Encryption
|
7
|
+
def encrypt
|
8
|
+
transaction do
|
9
|
+
super
|
10
|
+
encrypt_rich_texts if has_encrypted_rich_texts?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def decrypt
|
15
|
+
transaction do
|
16
|
+
super
|
17
|
+
decrypt_rich_texts if has_encrypted_rich_texts?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def encrypt_rich_texts
|
23
|
+
encryptable_rich_texts.each(&:encrypt)
|
24
|
+
end
|
25
|
+
|
26
|
+
def decrypt_rich_texts
|
27
|
+
encryptable_rich_texts.each(&:decrypt)
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_encrypted_rich_texts?
|
31
|
+
encryptable_rich_texts.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
def encryptable_rich_texts
|
35
|
+
@encryptable_rich_texts ||= self.class.rich_text_association_names
|
36
|
+
.filter_map { |attribute_name| send(attribute_name) }
|
37
|
+
.find_all { |record| record.is_a?(ActionText::EncryptedRichText) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "rails"
|
6
|
+
require "action_controller/railtie"
|
7
|
+
require "active_record/railtie"
|
8
|
+
require "active_storage/engine"
|
9
|
+
|
10
|
+
require "action_text"
|
11
|
+
|
12
|
+
module ActionText
|
13
|
+
class Engine < Rails::Engine
|
14
|
+
isolate_namespace ActionText
|
15
|
+
config.eager_load_namespaces << ActionText
|
16
|
+
|
17
|
+
config.action_text = ActiveSupport::OrderedOptions.new
|
18
|
+
config.action_text.attachment_tag_name = "action-text-attachment"
|
19
|
+
config.autoload_once_paths = %W(
|
20
|
+
#{root}/app/helpers
|
21
|
+
#{root}/app/models
|
22
|
+
)
|
23
|
+
|
24
|
+
initializer "action_text.deprecator", before: :load_environment_config do |app|
|
25
|
+
app.deprecators[:action_text] = ActionText.deprecator
|
26
|
+
end
|
27
|
+
|
28
|
+
initializer "action_text.attribute" do
|
29
|
+
ActiveSupport.on_load(:active_record) do
|
30
|
+
include ActionText::Attribute
|
31
|
+
prepend ActionText::Encryption
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
initializer "action_text.asset" do
|
36
|
+
if Rails.application.config.respond_to?(:assets)
|
37
|
+
Rails.application.config.assets.precompile += %w( actiontext.js actiontext.esm.js trix.js trix.css )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
initializer "action_text.attachable" do
|
42
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
43
|
+
include ActionText::Attachable
|
44
|
+
|
45
|
+
def previewable_attachable?
|
46
|
+
representable?
|
47
|
+
end
|
48
|
+
|
49
|
+
def attachable_plain_text_representation(caption = nil)
|
50
|
+
"[#{caption || filename}]"
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_trix_content_attachment_partial_path
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
initializer "action_text.helper" do
|
60
|
+
%i[action_controller_base action_mailer].each do |base|
|
61
|
+
ActiveSupport.on_load(base) do
|
62
|
+
helper ActionText::Engine.helpers
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
initializer "action_text.renderer" do
|
68
|
+
%i[action_controller_base action_mailer].each do |base|
|
69
|
+
ActiveSupport.on_load(base) do
|
70
|
+
around_action do |controller, action|
|
71
|
+
ActionText::Content.with_renderer(controller, &action)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
initializer "action_text.system_test_helper" do
|
78
|
+
ActiveSupport.on_load(:action_dispatch_system_test_case) do
|
79
|
+
require "action_text/system_test_helper"
|
80
|
+
include ActionText::SystemTestHelper
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
initializer "action_text.configure" do |app|
|
85
|
+
ActionText::Attachment.tag_name = app.config.action_text.attachment_tag_name
|
86
|
+
end
|
87
|
+
|
88
|
+
config.after_initialize do |app|
|
89
|
+
if klass = app.config.action_text.sanitizer_vendor
|
90
|
+
ActionText::ContentHelper.sanitizer = klass.safe_list_sanitizer.new
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
# # Action Text FixtureSet
|
7
|
+
#
|
8
|
+
# Fixtures are a way of organizing data that you want to test against; in short,
|
9
|
+
# sample data.
|
10
|
+
#
|
11
|
+
# To learn more about fixtures, read the ActiveRecord::FixtureSet documentation.
|
12
|
+
#
|
13
|
+
# ### YAML
|
14
|
+
#
|
15
|
+
# Like other Active Record-backed models, ActionText::RichText records inherit
|
16
|
+
# from ActiveRecord::Base instances and can therefore be populated by fixtures.
|
17
|
+
#
|
18
|
+
# Consider an `Article` class:
|
19
|
+
#
|
20
|
+
# class Article < ApplicationRecord
|
21
|
+
# has_rich_text :content
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# To declare fixture data for the related `content`, first declare fixture data
|
25
|
+
# for `Article` instances in `test/fixtures/articles.yml`:
|
26
|
+
#
|
27
|
+
# first:
|
28
|
+
# title: An Article
|
29
|
+
#
|
30
|
+
# Then declare the ActionText::RichText fixture data in
|
31
|
+
# `test/fixtures/action_text/rich_texts.yml`, making sure to declare each
|
32
|
+
# entry's `record:` key as a polymorphic relationship:
|
33
|
+
#
|
34
|
+
# first:
|
35
|
+
# record: first (Article)
|
36
|
+
# name: content
|
37
|
+
# body: <div>Hello, world.</div>
|
38
|
+
#
|
39
|
+
# When processed, Active Record will insert database records for each fixture
|
40
|
+
# entry and will ensure the Action Text relationship is intact.
|
41
|
+
class FixtureSet
|
42
|
+
# Fixtures support Action Text attachments as part of their `body` HTML.
|
43
|
+
#
|
44
|
+
# ### Examples
|
45
|
+
#
|
46
|
+
# For example, consider a second `Article` fixture declared in
|
47
|
+
# `test/fixtures/articles.yml`:
|
48
|
+
#
|
49
|
+
# second:
|
50
|
+
# title: Another Article
|
51
|
+
#
|
52
|
+
# You can attach a mention of `articles(:first)` to `second`'s `content` by
|
53
|
+
# embedding a call to `ActionText::FixtureSet.attachment` in the `body:` value
|
54
|
+
# in `test/fixtures/action_text/rich_texts.yml`:
|
55
|
+
#
|
56
|
+
# second:
|
57
|
+
# record: second (Article)
|
58
|
+
# name: content
|
59
|
+
# body: <div>Hello, <%= ActionText::FixtureSet.attachment("articles", :first) %></div>
|
60
|
+
#
|
61
|
+
def self.attachment(fixture_set_name, label, column_type: :integer)
|
62
|
+
signed_global_id = ActiveRecord::FixtureSet.signed_global_id fixture_set_name, label,
|
63
|
+
column_type: column_type, for: ActionText::Attachable::LOCATOR_NAME
|
64
|
+
|
65
|
+
%(<action-text-attachment sgid="#{signed_global_id}"></action-text-attachment>)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
class Fragment
|
7
|
+
class << self
|
8
|
+
def wrap(fragment_or_html)
|
9
|
+
case fragment_or_html
|
10
|
+
when self
|
11
|
+
fragment_or_html
|
12
|
+
when Nokogiri::XML::DocumentFragment # base class for all fragments
|
13
|
+
new(fragment_or_html)
|
14
|
+
else
|
15
|
+
from_html(fragment_or_html)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_html(html)
|
20
|
+
new(ActionText::HtmlConversion.fragment_for_html(html.to_s.strip))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :source
|
25
|
+
|
26
|
+
delegate :deconstruct, to: "source.elements"
|
27
|
+
|
28
|
+
def initialize(source)
|
29
|
+
@source = source
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_all(selector)
|
33
|
+
source.css(selector)
|
34
|
+
end
|
35
|
+
|
36
|
+
def update
|
37
|
+
yield source = self.source.dup
|
38
|
+
self.class.new(source)
|
39
|
+
end
|
40
|
+
|
41
|
+
def replace(selector)
|
42
|
+
update do |source|
|
43
|
+
source.css(selector).each do |node|
|
44
|
+
replacement_node = yield(node)
|
45
|
+
node.replace(replacement_node.to_s) if node != replacement_node
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_plain_text
|
51
|
+
@plain_text ||= PlainTextConversion.node_to_plain_text(source)
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_html
|
55
|
+
@html ||= HtmlConversion.node_to_html(source)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
to_html
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
# Returns the currently loaded version of Action Text as a `Gem::Version`.
|
7
|
+
def self.gem_version
|
8
|
+
Gem::Version.new VERSION::STRING
|
9
|
+
end
|
10
|
+
|
11
|
+
module VERSION
|
12
|
+
MAJOR = 8
|
13
|
+
MINOR = 0
|
14
|
+
TINY = 0
|
15
|
+
PRE = "alpha3"
|
16
|
+
|
17
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
module HtmlConversion
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def node_to_html(node)
|
10
|
+
node.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_HTML)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fragment_for_html(html)
|
14
|
+
document.fragment(html)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_element(tag_name, attributes = {})
|
18
|
+
document.create_element(tag_name, attributes)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def document
|
23
|
+
ActionText.html_document_class.new.tap { |doc| doc.encoding = "UTF-8" }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|