actiontext 7.0.8.2 → 7.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actiontext might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -151
- data/MIT-LICENSE +1 -1
- data/README.md +2 -2
- data/app/assets/javascripts/actiontext.js +781 -766
- data/app/assets/javascripts/trix.js +13077 -19
- data/app/assets/stylesheets/trix.css +410 -21
- data/app/helpers/action_text/content_helper.rb +17 -4
- data/app/helpers/action_text/tag_helper.rb +9 -1
- data/app/models/action_text/encrypted_rich_text.rb +2 -0
- data/app/models/action_text/rich_text.rb +29 -1
- data/app/views/action_text/attachables/_content_attachment.html.erb +3 -0
- data/lib/action_text/attachable.rb +57 -1
- data/lib/action_text/attachables/content_attachment.rb +20 -18
- data/lib/action_text/attachables/missing_attachable.rb +17 -3
- data/lib/action_text/attachment.rb +43 -2
- data/lib/action_text/attribute.rb +10 -5
- data/lib/action_text/content.rb +43 -2
- data/lib/action_text/deprecator.rb +7 -0
- data/lib/action_text/engine.rb +14 -8
- data/lib/action_text/fixture_set.rb +2 -0
- data/lib/action_text/fragment.rb +4 -3
- data/lib/action_text/gem_version.rb +4 -4
- data/lib/action_text/html_conversion.rb +1 -1
- data/lib/action_text/rendering.rb +5 -2
- data/lib/action_text/trix_attachment.rb +2 -2
- data/lib/action_text/version.rb +1 -1
- data/lib/action_text.rb +19 -0
- data/lib/generators/action_text/install/install_generator.rb +20 -3
- data/lib/generators/action_text/install/templates/actiontext.css +0 -4
- data/package.json +5 -4
- metadata +18 -16
@@ -4,9 +4,9 @@ require "rails-html-sanitizer"
|
|
4
4
|
|
5
5
|
module ActionText
|
6
6
|
module ContentHelper
|
7
|
-
mattr_accessor(:sanitizer
|
8
|
-
mattr_accessor(:allowed_tags)
|
9
|
-
mattr_accessor(:allowed_attributes)
|
7
|
+
mattr_accessor(:sanitizer, default: Rails::HTML4::Sanitizer.safe_list_sanitizer.new)
|
8
|
+
mattr_accessor(:allowed_tags)
|
9
|
+
mattr_accessor(:allowed_attributes)
|
10
10
|
mattr_accessor(:scrubber)
|
11
11
|
|
12
12
|
def render_action_text_content(content)
|
@@ -15,7 +15,12 @@ module ActionText
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def sanitize_action_text_content(content)
|
18
|
-
sanitizer.sanitize(
|
18
|
+
sanitizer.sanitize(
|
19
|
+
content.to_html,
|
20
|
+
tags: sanitizer_allowed_tags,
|
21
|
+
attributes: sanitizer_allowed_attributes,
|
22
|
+
scrubber: scrubber,
|
23
|
+
).html_safe
|
19
24
|
end
|
20
25
|
|
21
26
|
def render_action_text_attachments(content)
|
@@ -48,5 +53,13 @@ module ActionText
|
|
48
53
|
|
49
54
|
render(**options).chomp
|
50
55
|
end
|
56
|
+
|
57
|
+
def sanitizer_allowed_tags
|
58
|
+
allowed_tags || (sanitizer.class.allowed_tags + [ ActionText::Attachment.tag_name, "figure", "figcaption" ])
|
59
|
+
end
|
60
|
+
|
61
|
+
def sanitizer_allowed_attributes
|
62
|
+
allowed_attributes || (sanitizer.class.allowed_attributes + ActionText::Attachment::ATTRIBUTES)
|
63
|
+
end
|
51
64
|
end
|
52
65
|
end
|
@@ -50,7 +50,8 @@ module ActionView::Helpers
|
|
50
50
|
options = @options.stringify_keys
|
51
51
|
add_default_name_and_id(options)
|
52
52
|
options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) if object
|
53
|
-
@template_object.rich_text_area_tag(options.delete("name"), options.fetch("value") { value }, options.except("value"))
|
53
|
+
html_tag = @template_object.rich_text_area_tag(options.delete("name"), options.fetch("value") { value }, options.except("value"))
|
54
|
+
error_wrapping(html_tag)
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
@@ -82,6 +83,13 @@ module ActionView::Helpers
|
|
82
83
|
end
|
83
84
|
|
84
85
|
class FormBuilder
|
86
|
+
# Wraps ActionView::Helpers::FormHelper#rich_text_area for form builders:
|
87
|
+
#
|
88
|
+
# <%= form_with model: @message do |f| %>
|
89
|
+
# <%= f.rich_text_area :content %>
|
90
|
+
# <% end %>
|
91
|
+
#
|
92
|
+
# Please refer to the documentation of the base helper for details.
|
85
93
|
def rich_text_area(method, options = {})
|
86
94
|
@template.rich_text_area(@object_name, method, objectify_options(options))
|
87
95
|
end
|
@@ -1,14 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionText
|
4
|
+
# = Action Text \RichText
|
5
|
+
#
|
4
6
|
# The RichText record holds the content produced by the Trix editor in a serialized +body+ attribute.
|
5
7
|
# It also holds all the references to the embedded files, which are stored using Active Storage.
|
6
8
|
# This record is then associated with the Active Record model the application desires to have
|
7
9
|
# rich text content using the +has_rich_text+ class method.
|
10
|
+
#
|
11
|
+
# class Message < ActiveRecord::Base
|
12
|
+
# has_rich_text :content
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# message = Message.create!(content: "<h1>Funny times!</h1>")
|
16
|
+
# message.content #=> #<ActionText::RichText....
|
17
|
+
# message.content.to_s # => "<h1>Funny times!</h1>"
|
18
|
+
# message.content.to_plain_text # => "Funny times!"
|
19
|
+
#
|
8
20
|
class RichText < Record
|
9
21
|
self.table_name = "action_text_rich_texts"
|
10
22
|
|
11
|
-
serialize :body, ActionText::Content
|
23
|
+
serialize :body, coder: ActionText::Content
|
12
24
|
delegate :to_s, :nil?, to: :body
|
13
25
|
|
14
26
|
belongs_to :record, polymorphic: true, touch: true
|
@@ -18,10 +30,26 @@ module ActionText
|
|
18
30
|
self.embeds = body.attachables.grep(ActiveStorage::Blob).uniq if body.present?
|
19
31
|
end
|
20
32
|
|
33
|
+
# Returns the +body+ attribute as plain text with all HTML tags removed.
|
34
|
+
#
|
35
|
+
# message = Message.create!(content: "<h1>Funny times!</h1>")
|
36
|
+
# message.content.to_plain_text # => "Funny times!"
|
21
37
|
def to_plain_text
|
22
38
|
body&.to_plain_text.to_s
|
23
39
|
end
|
24
40
|
|
41
|
+
# Returns the +body+ attribute in a format that makes it editable in the Trix
|
42
|
+
# editor. Previews of attachments are rendered inline.
|
43
|
+
#
|
44
|
+
# content = "<h1>Funny Times!</h1><figure data-trix-attachment='{\"sgid\":\"..."\}'></figure>"
|
45
|
+
# message = Message.create!(content: content)
|
46
|
+
# message.content.to_trix_html # =>
|
47
|
+
# # <div class="trix-content">
|
48
|
+
# # <h1>Funny times!</h1>
|
49
|
+
# # <figure data-trix-attachment='{\"sgid\":\"..."\}'>
|
50
|
+
# # <img src="http://example.org/rails/active_storage/.../funny.jpg">
|
51
|
+
# # </figure>
|
52
|
+
# # </div>
|
25
53
|
def to_trix_html
|
26
54
|
body&.to_trix_html
|
27
55
|
end
|
@@ -1,12 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionText
|
4
|
+
# = Action Text \Attachable
|
5
|
+
#
|
6
|
+
# Include this module to make a record attachable to an ActionText::Content.
|
7
|
+
#
|
8
|
+
# class Person < ApplicationRecord
|
9
|
+
# include ActionText::Attachable
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# person = Person.create! name: "Javan"
|
13
|
+
# html = %Q(<action-text-attachment sgid="#{person.attachable_sgid}"></action-text-attachment>)
|
14
|
+
# content = ActionText::Content.new(html)
|
15
|
+
# content.attachables # => [person]
|
4
16
|
module Attachable
|
5
17
|
extend ActiveSupport::Concern
|
6
18
|
|
7
19
|
LOCATOR_NAME = "attachable"
|
8
20
|
|
9
21
|
class << self
|
22
|
+
# Extracts the +ActionText::Attachable+ from the attachment HTML node:
|
23
|
+
#
|
24
|
+
# person = Person.create! name: "Javan"
|
25
|
+
# html = %Q(<action-text-attachment sgid="#{person.attachable_sgid}"></action-text-attachment>)
|
26
|
+
# fragment = ActionText::Fragment.wrap(html)
|
27
|
+
# attachment_node = fragment.find_all(ActionText::Attachment.tag_name).first
|
28
|
+
# ActionText::Attachable.from_node(attachment_node) # => person
|
10
29
|
def from_node(node)
|
11
30
|
if attachable = attachable_from_sgid(node["sgid"])
|
12
31
|
attachable
|
@@ -15,7 +34,7 @@ module ActionText
|
|
15
34
|
elsif attachable = ActionText::Attachables::RemoteImage.from_node(node)
|
16
35
|
attachable
|
17
36
|
else
|
18
|
-
ActionText::Attachables::MissingAttachable
|
37
|
+
ActionText::Attachables::MissingAttachable.new(node["sgid"])
|
19
38
|
end
|
20
39
|
end
|
21
40
|
|
@@ -37,8 +56,24 @@ module ActionText
|
|
37
56
|
def from_attachable_sgid(sgid)
|
38
57
|
ActionText::Attachable.from_attachable_sgid(sgid, only: self)
|
39
58
|
end
|
59
|
+
|
60
|
+
# Returns the path to the partial that is used for rendering missing attachables.
|
61
|
+
# Defaults to "action_text/attachables/missing_attachable".
|
62
|
+
#
|
63
|
+
# Override to render a different partial:
|
64
|
+
#
|
65
|
+
# class User < ApplicationRecord
|
66
|
+
# def self.to_missing_attachable_partial_path
|
67
|
+
# "users/missing_attachable"
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
def to_missing_attachable_partial_path
|
71
|
+
ActionText::Attachables::MissingAttachable::DEFAULT_PARTIAL_PATH
|
72
|
+
end
|
40
73
|
end
|
41
74
|
|
75
|
+
# Returns the Signed Global ID for the attachable. The purpose of the ID is
|
76
|
+
# set to 'attachable' so it can't be reused for other purposes.
|
42
77
|
def attachable_sgid
|
43
78
|
to_sgid(expires_in: nil, for: LOCATOR_NAME).to_s
|
44
79
|
end
|
@@ -63,14 +98,35 @@ module ActionText
|
|
63
98
|
false
|
64
99
|
end
|
65
100
|
|
101
|
+
# Returns the attachable as JSON with the +attachable_sgid+ included.
|
66
102
|
def as_json(*)
|
67
103
|
super.merge("attachable_sgid" => persisted? ? attachable_sgid : nil)
|
68
104
|
end
|
69
105
|
|
106
|
+
# Returns the path to the partial that is used for rendering the attachable
|
107
|
+
# in Trix. Defaults to +to_partial_path+.
|
108
|
+
#
|
109
|
+
# Override to render a different partial:
|
110
|
+
#
|
111
|
+
# class User < ApplicationRecord
|
112
|
+
# def to_trix_content_attachment_partial_path
|
113
|
+
# "users/trix_content_attachment"
|
114
|
+
# end
|
115
|
+
# end
|
70
116
|
def to_trix_content_attachment_partial_path
|
71
117
|
to_partial_path
|
72
118
|
end
|
73
119
|
|
120
|
+
# Returns the path to the partial that is used for rendering the attachable.
|
121
|
+
# Defaults to +to_partial_path+.
|
122
|
+
#
|
123
|
+
# Override to render a different partial:
|
124
|
+
#
|
125
|
+
# class User < ApplicationRecord
|
126
|
+
# def to_attachable_partial_path
|
127
|
+
# "users/attachable"
|
128
|
+
# end
|
129
|
+
# end
|
74
130
|
def to_attachable_partial_path
|
75
131
|
to_partial_path
|
76
132
|
end
|
@@ -2,37 +2,39 @@
|
|
2
2
|
|
3
3
|
module ActionText
|
4
4
|
module Attachables
|
5
|
-
class ContentAttachment
|
5
|
+
class ContentAttachment # :nodoc:
|
6
6
|
include ActiveModel::Model
|
7
7
|
|
8
8
|
def self.from_node(node)
|
9
|
-
|
10
|
-
|
11
|
-
attachment = new(name: matches[1])
|
12
|
-
attachment if attachment.valid?
|
13
|
-
end
|
14
|
-
end
|
9
|
+
attachment = new(content_type: node["content-type"], content: node["content"])
|
10
|
+
attachment if attachment.valid?
|
15
11
|
end
|
16
12
|
|
17
|
-
attr_accessor :
|
18
|
-
|
13
|
+
attr_accessor :content_type, :content
|
14
|
+
|
15
|
+
validates_format_of :content_type, with: /html/
|
16
|
+
validates_presence_of :content
|
19
17
|
|
20
18
|
def attachable_plain_text_representation(caption)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
content_instance.fragment.source
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_html
|
23
|
+
@to_html ||= content_instance.render(content_instance)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
to_html
|
27
28
|
end
|
28
29
|
|
29
30
|
def to_partial_path
|
30
31
|
"action_text/attachables/content_attachment"
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
private
|
35
|
+
def content_instance
|
36
|
+
@content_instance ||= ActionText::Content.new(content)
|
37
|
+
end
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
@@ -2,11 +2,25 @@
|
|
2
2
|
|
3
3
|
module ActionText
|
4
4
|
module Attachables
|
5
|
-
|
5
|
+
class MissingAttachable
|
6
6
|
extend ActiveModel::Naming
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
DEFAULT_PARTIAL_PATH = "action_text/attachables/missing_attachable"
|
9
|
+
|
10
|
+
def initialize(sgid)
|
11
|
+
@sgid = SignedGlobalID.parse(sgid, for: ActionText::Attachable::LOCATOR_NAME)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_partial_path
|
15
|
+
if model
|
16
|
+
model.to_missing_attachable_partial_path
|
17
|
+
else
|
18
|
+
DEFAULT_PARTIAL_PATH
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def model
|
23
|
+
@sgid&.model_name.to_s.safe_constantize
|
10
24
|
end
|
11
25
|
end
|
12
26
|
end
|
@@ -3,12 +3,23 @@
|
|
3
3
|
require "active_support/core_ext/object/try"
|
4
4
|
|
5
5
|
module ActionText
|
6
|
+
# = Action Text \Attachment
|
7
|
+
#
|
8
|
+
# Attachments serialize attachables to HTML or plain text.
|
9
|
+
#
|
10
|
+
# class Person < ApplicationRecord
|
11
|
+
# include ActionText::Attachable
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# attachable = Person.create! name: "Javan"
|
15
|
+
# attachment = ActionText::Attachment.from_attachable(attachable)
|
16
|
+
# attachment.to_html # => "<action-text-attachment sgid=\"BAh7CEk..."
|
6
17
|
class Attachment
|
7
18
|
include Attachments::TrixConversion, Attachments::Minification, Attachments::Caching
|
8
19
|
|
9
20
|
mattr_accessor :tag_name, default: "action-text-attachment"
|
10
21
|
|
11
|
-
ATTRIBUTES = %w( sgid content-type url href filename filesize width height previewable presentation caption )
|
22
|
+
ATTRIBUTES = %w( sgid content-type url href filename filesize width height previewable presentation caption content )
|
12
23
|
|
13
24
|
class << self
|
14
25
|
def fragment_by_canonicalizing_attachments(content)
|
@@ -69,6 +80,31 @@ module ActionText
|
|
69
80
|
self.class.from_attributes(full_attributes, attachable)
|
70
81
|
end
|
71
82
|
|
83
|
+
# Converts the attachment to plain text.
|
84
|
+
#
|
85
|
+
# attachable = ActiveStorage::Blob.find_by filename: "racecar.jpg"
|
86
|
+
# attachment = ActionText::Attachment.from_attachable(attachable)
|
87
|
+
# attachment.to_plain_text # => "[racecar.jpg]"
|
88
|
+
#
|
89
|
+
# Use the +caption+ when set:
|
90
|
+
#
|
91
|
+
# attachment = ActionText::Attachment.from_attachable(attachable, caption: "Vroom vroom")
|
92
|
+
# attachment.to_plain_text # => "[Vroom vroom]"
|
93
|
+
#
|
94
|
+
# The presentation can be overridden by implementing the
|
95
|
+
# +attachable_plain_text_representation+ method:
|
96
|
+
#
|
97
|
+
# class Person < ApplicationRecord
|
98
|
+
# include ActionText::Attachable
|
99
|
+
#
|
100
|
+
# def attachable_plain_text_representation
|
101
|
+
# "[#{name}]"
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# attachable = Person.create! name: "Javan"
|
106
|
+
# attachment = ActionText::Attachment.from_attachable(attachable)
|
107
|
+
# attachment.to_plain_text # => "[Javan]"
|
72
108
|
def to_plain_text
|
73
109
|
if respond_to?(:attachable_plain_text_representation)
|
74
110
|
attachable_plain_text_representation(caption)
|
@@ -77,6 +113,11 @@ module ActionText
|
|
77
113
|
end
|
78
114
|
end
|
79
115
|
|
116
|
+
# Converts the attachment to HTML.
|
117
|
+
#
|
118
|
+
# attachable = Person.create! name: "Javan"
|
119
|
+
# attachment = ActionText::Attachment.from_attachable(attachable)
|
120
|
+
# attachment.to_html # => "<action-text-attachment sgid=\"BAh7CEk...
|
80
121
|
def to_html
|
81
122
|
HtmlConversion.node_to_html(node)
|
82
123
|
end
|
@@ -91,7 +132,7 @@ module ActionText
|
|
91
132
|
|
92
133
|
private
|
93
134
|
def node_attributes
|
94
|
-
@node_attributes ||= ATTRIBUTES.
|
135
|
+
@node_attributes ||= ATTRIBUTES.to_h { |name| [ name.underscore, node[name] ] }.compact
|
95
136
|
end
|
96
137
|
|
97
138
|
def attachable_attributes
|
@@ -26,11 +26,15 @@ module ActionText
|
|
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
28
|
#
|
29
|
-
#
|
29
|
+
# ==== Options
|
30
30
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
|
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
|
+
#
|
34
|
+
# * <tt>:strict_loading</tt> - Pass true to force strict loading. When
|
35
|
+
# omitted, <tt>strict_loading:</tt> will be set to the value of the
|
36
|
+
# <tt>strict_loading_by_default</tt> class attribute (false by default).
|
37
|
+
def has_rich_text(name, encrypted: false, strict_loading: strict_loading_by_default)
|
34
38
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
35
39
|
def #{name}
|
36
40
|
rich_text_#{name} || build_rich_text_#{name}
|
@@ -47,7 +51,8 @@ module ActionText
|
|
47
51
|
|
48
52
|
rich_text_class_name = encrypted ? "ActionText::EncryptedRichText" : "ActionText::RichText"
|
49
53
|
has_one :"rich_text_#{name}", -> { where(name: name) },
|
50
|
-
class_name: rich_text_class_name, as: :record, inverse_of: :record, autosave: true, dependent: :destroy
|
54
|
+
class_name: rich_text_class_name, as: :record, inverse_of: :record, autosave: true, dependent: :destroy,
|
55
|
+
strict_loading: strict_loading
|
51
56
|
|
52
57
|
scope :"with_rich_text_#{name}", -> { includes("rich_text_#{name}") }
|
53
58
|
scope :"with_rich_text_#{name}_and_embeds", -> { includes("rich_text_#{name}": { embeds_attachments: :blob }) }
|
data/lib/action_text/content.rb
CHANGED
@@ -1,6 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionText
|
4
|
+
# = Action Text \Content
|
5
|
+
#
|
6
|
+
# The +ActionText::Content+ class wraps an HTML fragment to add support for
|
7
|
+
# parsing, rendering and serialization. It can be used to extract links and
|
8
|
+
# attachments, convert the fragment to plain text, or serialize the fragment
|
9
|
+
# to the database.
|
10
|
+
#
|
11
|
+
# The ActionText::RichText record serializes the `body` attribute as
|
12
|
+
# +ActionText::Content+.
|
13
|
+
#
|
14
|
+
# class Message < ActiveRecord::Base
|
15
|
+
# has_rich_text :content
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# message = Message.create!(content: "<h1>Funny times!</h1>")
|
19
|
+
# body = message.content.body # => #<ActionText::Content "<div class=\"trix-conte...">
|
20
|
+
# body.to_s # => "<h1>Funny times!</h1>"
|
21
|
+
# body.to_plain_text # => "Funny times!"
|
4
22
|
class Content
|
5
23
|
include Rendering, Serialization
|
6
24
|
|
@@ -26,10 +44,21 @@ module ActionText
|
|
26
44
|
end
|
27
45
|
end
|
28
46
|
|
47
|
+
# Extracts links from the HTML fragment:
|
48
|
+
#
|
49
|
+
# html = '<a href="http://example.com/">Example</a>'
|
50
|
+
# content = ActionText::Content.new(html)
|
51
|
+
# content.links # => ["http://example.com/"]
|
29
52
|
def links
|
30
53
|
@links ||= fragment.find_all("a[href]").map { |a| a["href"] }.uniq
|
31
54
|
end
|
32
55
|
|
56
|
+
# Extracts +ActionText::Attachment+s from the HTML fragment:
|
57
|
+
#
|
58
|
+
# attachable = ActiveStorage::Blob.first
|
59
|
+
# html = %Q(<action-text-attachment sgid="#{attachable.attachable_sgid}" caption="Captioned"></action-text-attachment>)
|
60
|
+
# content = ActionText::Content.new(html)
|
61
|
+
# content.attachments # => [#<ActionText::Attachment attachable=#<ActiveStorage::Blob...
|
33
62
|
def attachments
|
34
63
|
@attachments ||= attachment_nodes.map do |node|
|
35
64
|
attachment_for_node(node)
|
@@ -46,6 +75,12 @@ module ActionText
|
|
46
75
|
@gallery_attachments ||= attachment_galleries.flat_map(&:attachments)
|
47
76
|
end
|
48
77
|
|
78
|
+
# Extracts +ActionText::Attachable+s from the HTML fragment:
|
79
|
+
#
|
80
|
+
# attachable = ActiveStorage::Blob.first
|
81
|
+
# html = %Q(<action-text-attachment sgid="#{attachable.attachable_sgid}" caption="Captioned"></action-text-attachment>)
|
82
|
+
# content = ActionText::Content.new(html)
|
83
|
+
# content.attachables # => [attachable]
|
49
84
|
def attachables
|
50
85
|
@attachables ||= attachment_nodes.map do |node|
|
51
86
|
ActionText::Attachable.from_node(node)
|
@@ -71,6 +106,10 @@ module ActionText
|
|
71
106
|
self.class.new(content, canonicalize: false)
|
72
107
|
end
|
73
108
|
|
109
|
+
# Returns the content as plain text with all HTML tags removed.
|
110
|
+
#
|
111
|
+
# content = ActionText::Content.new("<h1>Funny times!</h1>")
|
112
|
+
# content.to_plain_text # => "Funny times!"
|
74
113
|
def to_plain_text
|
75
114
|
render_attachments(with_full_attributes: false, &:to_plain_text).fragment.to_plain_text
|
76
115
|
end
|
@@ -100,11 +139,13 @@ module ActionText
|
|
100
139
|
end
|
101
140
|
|
102
141
|
def inspect
|
103
|
-
"#<#{self.class.name} #{
|
142
|
+
"#<#{self.class.name} #{to_html.truncate(25).inspect}>"
|
104
143
|
end
|
105
144
|
|
106
145
|
def ==(other)
|
107
|
-
if
|
146
|
+
if self.class == other.class
|
147
|
+
to_html == other.to_html
|
148
|
+
elsif other.is_a?(self.class)
|
108
149
|
to_s == other.to_s
|
109
150
|
end
|
110
151
|
end
|
data/lib/action_text/engine.rb
CHANGED
@@ -19,6 +19,10 @@ module ActionText
|
|
19
19
|
#{root}/app/models
|
20
20
|
)
|
21
21
|
|
22
|
+
initializer "action_text.deprecator", before: :load_environment_config do |app|
|
23
|
+
app.deprecators[:action_text] = ActionText.deprecator
|
24
|
+
end
|
25
|
+
|
22
26
|
initializer "action_text.attribute" do
|
23
27
|
ActiveSupport.on_load(:active_record) do
|
24
28
|
include ActionText::Attribute
|
@@ -51,20 +55,16 @@ module ActionText
|
|
51
55
|
end
|
52
56
|
|
53
57
|
initializer "action_text.helper" do
|
54
|
-
%i[action_controller_base action_mailer].each do |
|
55
|
-
ActiveSupport.on_load(
|
58
|
+
%i[action_controller_base action_mailer].each do |base|
|
59
|
+
ActiveSupport.on_load(base) do
|
56
60
|
helper ActionText::Engine.helpers
|
57
61
|
end
|
58
62
|
end
|
59
63
|
end
|
60
64
|
|
61
65
|
initializer "action_text.renderer" do
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
%i[action_controller_base action_mailer].each do |abstract_controller|
|
67
|
-
ActiveSupport.on_load(abstract_controller) do
|
66
|
+
%i[action_controller_base action_mailer].each do |base|
|
67
|
+
ActiveSupport.on_load(base) do
|
68
68
|
around_action do |controller, action|
|
69
69
|
ActionText::Content.with_renderer(controller, &action)
|
70
70
|
end
|
@@ -82,5 +82,11 @@ module ActionText
|
|
82
82
|
initializer "action_text.configure" do |app|
|
83
83
|
ActionText::Attachment.tag_name = app.config.action_text.attachment_tag_name
|
84
84
|
end
|
85
|
+
|
86
|
+
config.after_initialize do |app|
|
87
|
+
if klass = app.config.action_text.sanitizer_vendor
|
88
|
+
ActionText::ContentHelper.sanitizer = klass.safe_list_sanitizer.new
|
89
|
+
end
|
90
|
+
end
|
85
91
|
end
|
86
92
|
end
|
data/lib/action_text/fragment.rb
CHANGED
@@ -7,7 +7,7 @@ module ActionText
|
|
7
7
|
case fragment_or_html
|
8
8
|
when self
|
9
9
|
fragment_or_html
|
10
|
-
when Nokogiri::
|
10
|
+
when Nokogiri::XML::DocumentFragment # base class for all fragments
|
11
11
|
new(fragment_or_html)
|
12
12
|
else
|
13
13
|
from_html(fragment_or_html)
|
@@ -30,14 +30,15 @@ module ActionText
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def update
|
33
|
-
yield source = self.source.
|
33
|
+
yield source = self.source.dup
|
34
34
|
self.class.new(source)
|
35
35
|
end
|
36
36
|
|
37
37
|
def replace(selector)
|
38
38
|
update do |source|
|
39
39
|
source.css(selector).each do |node|
|
40
|
-
|
40
|
+
replacement_node = yield(node)
|
41
|
+
node.replace(replacement_node.to_s) if node != replacement_node
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionText
|
4
|
-
# Returns the currently loaded version of Action Text as a
|
4
|
+
# Returns the currently loaded version of Action Text as a +Gem::Version+.
|
5
5
|
def self.gem_version
|
6
6
|
Gem::Version.new VERSION::STRING
|
7
7
|
end
|
8
8
|
|
9
9
|
module VERSION
|
10
10
|
MAJOR = 7
|
11
|
-
MINOR =
|
12
|
-
TINY =
|
13
|
-
PRE = "
|
11
|
+
MINOR = 1
|
12
|
+
TINY = 0
|
13
|
+
PRE = "beta1"
|
14
14
|
|
15
15
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
16
|
end
|
@@ -8,12 +8,15 @@ module ActionText
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
included do
|
11
|
-
cattr_accessor :default_renderer, instance_accessor: false
|
12
11
|
thread_cattr_accessor :renderer, instance_accessor: false
|
13
12
|
delegate :render, to: :class
|
14
13
|
end
|
15
14
|
|
16
15
|
class_methods do
|
16
|
+
def action_controller_renderer
|
17
|
+
@action_controller_renderer ||= Class.new(ActionController::Base).renderer
|
18
|
+
end
|
19
|
+
|
17
20
|
def with_renderer(renderer)
|
18
21
|
previous_renderer = self.renderer
|
19
22
|
self.renderer = renderer
|
@@ -23,7 +26,7 @@ module ActionText
|
|
23
26
|
end
|
24
27
|
|
25
28
|
def render(*args, &block)
|
26
|
-
(renderer ||
|
29
|
+
(renderer || action_controller_renderer).render_to_string(*args, &block)
|
27
30
|
end
|
28
31
|
end
|
29
32
|
end
|