actiontext 7.1.3 → 7.2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -73
  3. data/app/assets/javascripts/trix.js +92 -30
  4. data/app/assets/stylesheets/trix.css +3 -1
  5. data/app/helpers/action_text/content_helper.rb +11 -0
  6. data/app/helpers/action_text/tag_helper.rb +38 -28
  7. data/app/models/action_text/encrypted_rich_text.rb +2 -2
  8. data/app/models/action_text/record.rb +2 -0
  9. data/app/models/action_text/rich_text.rb +58 -26
  10. data/db/migrate/20180528164100_create_action_text_tables.rb +1 -1
  11. data/lib/action_text/attachable.rb +35 -33
  12. data/lib/action_text/attachables/content_attachment.rb +2 -0
  13. data/lib/action_text/attachables/missing_attachable.rb +2 -0
  14. data/lib/action_text/attachables/remote_image.rb +2 -0
  15. data/lib/action_text/attachment.rb +27 -25
  16. data/lib/action_text/attachment_gallery.rb +2 -0
  17. data/lib/action_text/attachments/caching.rb +2 -0
  18. data/lib/action_text/attachments/minification.rb +2 -0
  19. data/lib/action_text/attachments/trix_conversion.rb +2 -0
  20. data/lib/action_text/attribute.rb +36 -22
  21. data/lib/action_text/content.rb +51 -27
  22. data/lib/action_text/deprecator.rb +2 -0
  23. data/lib/action_text/encryption.rb +2 -0
  24. data/lib/action_text/engine.rb +2 -0
  25. data/lib/action_text/fixture_set.rb +34 -34
  26. data/lib/action_text/fragment.rb +4 -0
  27. data/lib/action_text/gem_version.rb +6 -4
  28. data/lib/action_text/html_conversion.rb +2 -0
  29. data/lib/action_text/plain_text_conversion.rb +8 -1
  30. data/lib/action_text/rendering.rb +2 -0
  31. data/lib/action_text/serialization.rb +2 -0
  32. data/lib/action_text/system_test_helper.rb +20 -17
  33. data/lib/action_text/trix_attachment.rb +2 -0
  34. data/lib/action_text/version.rb +3 -1
  35. data/lib/action_text.rb +1 -1
  36. data/lib/generators/action_text/install/install_generator.rb +10 -3
  37. data/lib/rails/generators/test_unit/install_generator.rb +2 -0
  38. data/package.json +2 -2
  39. metadata +18 -18
@@ -1,55 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
- # = Action Text \RichText
6
+ # # Action Text RichText
5
7
  #
6
- # The RichText record holds the content produced by the Trix editor in a serialized +body+ attribute.
7
- # It also holds all the references to the embedded files, which are stored using Active Storage.
8
- # This record is then associated with the Active Record model the application desires to have
9
- # rich text content using the +has_rich_text+ class method.
8
+ # The RichText record holds the content produced by the Trix editor in a
9
+ # serialized `body` attribute. It also holds all the references to the embedded
10
+ # files, which are stored using Active Storage. This record is then associated
11
+ # with the Active Record model the application desires to have rich text content
12
+ # using the `has_rich_text` class method.
10
13
  #
11
- # class Message < ActiveRecord::Base
12
- # has_rich_text :content
13
- # end
14
+ # class Message < ActiveRecord::Base
15
+ # has_rich_text :content
16
+ # end
14
17
  #
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!"
18
+ # message = Message.create!(content: "<h1>Funny times!</h1>")
19
+ # message.content #=> #<ActionText::RichText....
20
+ # message.content.to_s # => "<h1>Funny times!</h1>"
21
+ # message.content.to_plain_text # => "Funny times!"
19
22
  #
23
+ # message = Message.create!(content: "<div onclick='action()'>safe<script>unsafe</script></div>")
24
+ # message.content #=> #<ActionText::RichText....
25
+ # message.content.to_s # => "<div>safeunsafe</div>"
26
+ # message.content.to_plain_text # => "safeunsafe"
20
27
  class RichText < Record
21
- self.table_name = "action_text_rich_texts"
28
+ ##
29
+ # :method: to_s
30
+ #
31
+ # Safely transforms RichText into an HTML String.
32
+ #
33
+ # message = Message.create!(content: "<h1>Funny times!</h1>")
34
+ # message.content.to_s # => "<h1>Funny times!</h1>"
35
+ #
36
+ # message = Message.create!(content: "<div onclick='action()'>safe<script>unsafe</script></div>")
37
+ # message.content.to_s # => "<div>safeunsafe</div>"
22
38
 
23
39
  serialize :body, coder: ActionText::Content
24
40
  delegate :to_s, :nil?, to: :body
25
41
 
42
+ ##
43
+ # :method: record
44
+ #
45
+ # Returns the associated record.
26
46
  belongs_to :record, polymorphic: true, touch: true
47
+
48
+ ##
49
+ # :method: embeds
50
+ #
51
+ # Returns the `ActiveStorage::Blob`s of the embedded files.
27
52
  has_many_attached :embeds
28
53
 
29
54
  before_save do
30
55
  self.embeds = body.attachables.grep(ActiveStorage::Blob).uniq if body.present?
31
56
  end
32
57
 
33
- # Returns the +body+ attribute as plain text with all HTML tags removed.
58
+ # Returns a plain-text version of the markup contained by the `body` attribute,
59
+ # with tags removed but HTML entities encoded.
60
+ #
61
+ # message = Message.create!(content: "<h1>Funny times!</h1>")
62
+ # message.content.to_plain_text # => "Funny times!"
63
+ #
64
+ # NOTE: that the returned string is not HTML safe and should not be rendered in
65
+ # browsers.
34
66
  #
35
- # message = Message.create!(content: "<h1>Funny times!</h1>")
36
- # message.content.to_plain_text # => "Funny times!"
67
+ # message = Message.create!(content: "&lt;script&gt;alert()&lt;/script&gt;")
68
+ # message.content.to_plain_text # => "<script>alert()</script>"
37
69
  def to_plain_text
38
70
  body&.to_plain_text.to_s
39
71
  end
40
72
 
41
- # Returns the +body+ attribute in a format that makes it editable in the Trix
73
+ # Returns the `body` attribute in a format that makes it editable in the Trix
42
74
  # editor. Previews of attachments are rendered inline.
43
75
  #
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>
76
+ # content = "<h1>Funny Times!</h1><figure data-trix-attachment='{\"sgid\":\"..."\}'></figure>"
77
+ # message = Message.create!(content: content)
78
+ # message.content.to_trix_html # =>
79
+ # # <div class="trix-content">
80
+ # # <h1>Funny times!</h1>
81
+ # # <figure data-trix-attachment='{\"sgid\":\"..."\}'>
82
+ # # <img src="http://example.org/rails/active_storage/.../funny.jpg">
83
+ # # </figure>
84
+ # # </div>
53
85
  def to_trix_html
54
86
  body&.to_trix_html
55
87
  end
@@ -20,6 +20,6 @@ class CreateActionTextTables < ActiveRecord::Migration[6.0]
20
20
  setting = config.options[config.orm][:primary_key_type]
21
21
  primary_key_type = setting || :primary_key
22
22
  foreign_key_type = setting || :bigint
23
- [primary_key_type, foreign_key_type]
23
+ [ primary_key_type, foreign_key_type ]
24
24
  end
25
25
  end
@@ -1,31 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
- # = Action Text \Attachable
6
+ # # Action Text Attachable
5
7
  #
6
8
  # Include this module to make a record attachable to an ActionText::Content.
7
9
  #
8
- # class Person < ApplicationRecord
9
- # include ActionText::Attachable
10
- # end
10
+ # class Person < ApplicationRecord
11
+ # include ActionText::Attachable
12
+ # end
11
13
  #
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]
14
+ # person = Person.create! name: "Javan"
15
+ # html = %Q(<action-text-attachment sgid="#{person.attachable_sgid}"></action-text-attachment>)
16
+ # content = ActionText::Content.new(html)
17
+ # content.attachables # => [person]
16
18
  module Attachable
17
19
  extend ActiveSupport::Concern
18
20
 
19
21
  LOCATOR_NAME = "attachable"
20
22
 
21
23
  class << self
22
- # Extracts the +ActionText::Attachable+ from the attachment HTML node:
24
+ # Extracts the `ActionText::Attachable` from the attachment HTML node:
23
25
  #
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
26
+ # person = Person.create! name: "Javan"
27
+ # html = %Q(<action-text-attachment sgid="#{person.attachable_sgid}"></action-text-attachment>)
28
+ # fragment = ActionText::Fragment.wrap(html)
29
+ # attachment_node = fragment.find_all(ActionText::Attachment.tag_name).first
30
+ # ActionText::Attachable.from_node(attachment_node) # => person
29
31
  def from_node(node)
30
32
  if attachable = attachable_from_sgid(node["sgid"])
31
33
  attachable
@@ -57,23 +59,23 @@ module ActionText
57
59
  ActionText::Attachable.from_attachable_sgid(sgid, only: self)
58
60
  end
59
61
 
60
- # Returns the path to the partial that is used for rendering missing attachables.
61
- # Defaults to "action_text/attachables/missing_attachable".
62
+ # Returns the path to the partial that is used for rendering missing
63
+ # attachables. Defaults to "action_text/attachables/missing_attachable".
62
64
  #
63
65
  # Override to render a different partial:
64
66
  #
65
- # class User < ApplicationRecord
66
- # def self.to_missing_attachable_partial_path
67
- # "users/missing_attachable"
67
+ # class User < ApplicationRecord
68
+ # def self.to_missing_attachable_partial_path
69
+ # "users/missing_attachable"
70
+ # end
68
71
  # end
69
- # end
70
72
  def to_missing_attachable_partial_path
71
73
  ActionText::Attachables::MissingAttachable::DEFAULT_PARTIAL_PATH
72
74
  end
73
75
  end
74
76
 
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.
77
+ # Returns the Signed Global ID for the attachable. The purpose of the ID is set
78
+ # to 'attachable' so it can't be reused for other purposes.
77
79
  def attachable_sgid
78
80
  to_sgid(expires_in: nil, for: LOCATOR_NAME).to_s
79
81
  end
@@ -98,30 +100,30 @@ module ActionText
98
100
  false
99
101
  end
100
102
 
101
- # Returns the path to the partial that is used for rendering the attachable
102
- # in Trix. Defaults to +to_partial_path+.
103
+ # Returns the path to the partial that is used for rendering the attachable in
104
+ # Trix. Defaults to `to_partial_path`.
103
105
  #
104
106
  # Override to render a different partial:
105
107
  #
106
- # class User < ApplicationRecord
107
- # def to_trix_content_attachment_partial_path
108
- # "users/trix_content_attachment"
108
+ # class User < ApplicationRecord
109
+ # def to_trix_content_attachment_partial_path
110
+ # "users/trix_content_attachment"
111
+ # end
109
112
  # end
110
- # end
111
113
  def to_trix_content_attachment_partial_path
112
114
  to_partial_path
113
115
  end
114
116
 
115
117
  # Returns the path to the partial that is used for rendering the attachable.
116
- # Defaults to +to_partial_path+.
118
+ # Defaults to `to_partial_path`.
117
119
  #
118
120
  # Override to render a different partial:
119
121
  #
120
- # class User < ApplicationRecord
121
- # def to_attachable_partial_path
122
- # "users/attachable"
122
+ # class User < ApplicationRecord
123
+ # def to_attachable_partial_path
124
+ # "users/attachable"
125
+ # end
123
126
  # end
124
- # end
125
127
  def to_attachable_partial_path
126
128
  to_partial_path
127
129
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module Attachables
5
7
  class ContentAttachment # :nodoc:
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module Attachables
5
7
  class MissingAttachable
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module Attachables
5
7
  class RemoteImage
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/object/try"
4
6
 
5
7
  module ActionText
6
- # = Action Text \Attachment
8
+ # # Action Text Attachment
7
9
  #
8
10
  # Attachments serialize attachables to HTML or plain text.
9
11
  #
10
- # class Person < ApplicationRecord
11
- # include ActionText::Attachable
12
- # end
12
+ # class Person < ApplicationRecord
13
+ # include ActionText::Attachable
14
+ # end
13
15
  #
14
- # attachable = Person.create! name: "Javan"
15
- # attachment = ActionText::Attachment.from_attachable(attachable)
16
- # attachment.to_html # => "<action-text-attachment sgid=\"BAh7CEk..."
16
+ # attachable = Person.create! name: "Javan"
17
+ # attachment = ActionText::Attachment.from_attachable(attachable)
18
+ # attachment.to_html # => "<action-text-attachment sgid=\"BAh7CEk..."
17
19
  class Attachment
18
20
  include Attachments::TrixConversion, Attachments::Minification, Attachments::Caching
19
21
 
@@ -82,29 +84,29 @@ module ActionText
82
84
 
83
85
  # Converts the attachment to plain text.
84
86
  #
85
- # attachable = ActiveStorage::Blob.find_by filename: "racecar.jpg"
86
- # attachment = ActionText::Attachment.from_attachable(attachable)
87
- # attachment.to_plain_text # => "[racecar.jpg]"
87
+ # attachable = ActiveStorage::Blob.find_by filename: "racecar.jpg"
88
+ # attachment = ActionText::Attachment.from_attachable(attachable)
89
+ # attachment.to_plain_text # => "[racecar.jpg]"
88
90
  #
89
- # Use the +caption+ when set:
91
+ # Use the `caption` when set:
90
92
  #
91
- # attachment = ActionText::Attachment.from_attachable(attachable, caption: "Vroom vroom")
92
- # attachment.to_plain_text # => "[Vroom vroom]"
93
+ # attachment = ActionText::Attachment.from_attachable(attachable, caption: "Vroom vroom")
94
+ # attachment.to_plain_text # => "[Vroom vroom]"
93
95
  #
94
96
  # The presentation can be overridden by implementing the
95
- # +attachable_plain_text_representation+ method:
97
+ # `attachable_plain_text_representation` method:
96
98
  #
97
- # class Person < ApplicationRecord
98
- # include ActionText::Attachable
99
+ # class Person < ApplicationRecord
100
+ # include ActionText::Attachable
99
101
  #
100
- # def attachable_plain_text_representation
101
- # "[#{name}]"
102
+ # def attachable_plain_text_representation
103
+ # "[#{name}]"
104
+ # end
102
105
  # end
103
- # end
104
106
  #
105
- # attachable = Person.create! name: "Javan"
106
- # attachment = ActionText::Attachment.from_attachable(attachable)
107
- # attachment.to_plain_text # => "[Javan]"
107
+ # attachable = Person.create! name: "Javan"
108
+ # attachment = ActionText::Attachment.from_attachable(attachable)
109
+ # attachment.to_plain_text # => "[Javan]"
108
110
  def to_plain_text
109
111
  if respond_to?(:attachable_plain_text_representation)
110
112
  attachable_plain_text_representation(caption)
@@ -115,9 +117,9 @@ module ActionText
115
117
 
116
118
  # Converts the attachment to HTML.
117
119
  #
118
- # attachable = Person.create! name: "Javan"
119
- # attachment = ActionText::Attachment.from_attachable(attachable)
120
- # attachment.to_html # => "<action-text-attachment sgid=\"BAh7CEk...
120
+ # attachable = Person.create! name: "Javan"
121
+ # attachment = ActionText::Attachment.from_attachable(attachable)
122
+ # attachment.to_html # => "<action-text-attachment sgid=\"BAh7CEk...
121
123
  def to_html
122
124
  HtmlConversion.node_to_html(node)
123
125
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  class AttachmentGallery
5
7
  include ActiveModel::Model
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module Attachments
5
7
  module Caching
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module Attachments
5
7
  module Minification
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/object/try"
4
6
 
5
7
  module ActionText
@@ -1,39 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module Attribute
5
7
  extend ActiveSupport::Concern
6
8
 
7
9
  class_methods do
8
- # Provides access to a dependent RichText model that holds the body and attachments for a single named rich text attribute.
9
- # This dependent attribute is lazily instantiated and will be auto-saved when it's been changed. Example:
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!"
10
22
  #
11
- # class Message < ActiveRecord::Base
12
- # has_rich_text :content
13
- # end
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.
14
26
  #
15
- # message = Message.create!(content: "<h1>Funny times!</h1>")
16
- # message.content? #=> true
17
- # message.content.to_s # => "<h1>Funny times!</h1>"
18
- # message.content.to_plain_text # => "Funny times!"
27
+ # If you wish to preload the dependent RichText model, you can use the named
28
+ # scope:
19
29
  #
20
- # The dependent RichText model will also automatically process attachments links as sent via the Trix-powered editor.
21
- # These attachments are associated with the RichText model using Active Storage.
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.
22
33
  #
23
- # If you wish to preload the dependent RichText model, you can use the named scope:
34
+ # #### Options
24
35
  #
25
- # Message.all.with_rich_text_content # Avoids N+1 queries when you just want the body, not the attachments.
26
- # Message.all.with_rich_text_content_and_embeds # Avoids N+1 queries when you just want the body and attachments.
27
- # Message.all.with_all_rich_text # Loads all rich text associations.
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.
28
39
  #
29
- # ==== Options
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).
30
43
  #
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
44
  #
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).
45
+ # Note: Action Text relies on polymorphic associations, which in turn store
46
+ # class names in the database. When renaming classes that use `has_rich_text`,
47
+ # make sure to also update the class names in the
48
+ # `action_text_rich_texts.record_type` polymorphic type column of the
49
+ # corresponding rows.
37
50
  def has_rich_text(name, encrypted: false, strict_loading: strict_loading_by_default)
38
51
  class_eval <<-CODE, __FILE__, __LINE__ + 1
39
52
  def #{name}
@@ -60,9 +73,10 @@ module ActionText
60
73
 
61
74
  # Eager load all dependent RichText models in bulk.
62
75
  def with_all_rich_text
63
- eager_load(rich_text_association_names)
76
+ includes(rich_text_association_names)
64
77
  end
65
78
 
79
+ # Returns the names of all rich text associations.
66
80
  def rich_text_association_names
67
81
  reflect_on_all_associations(:has_one).collect(&:name).select { |n| n.start_with?("rich_text_") }
68
82
  end
@@ -1,29 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
- # = Action Text \Content
6
+ # # Action Text Content
5
7
  #
6
- # The +ActionText::Content+ class wraps an HTML fragment to add support for
8
+ # The `ActionText::Content` class wraps an HTML fragment to add support for
7
9
  # 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
+ # attachments, convert the fragment to plain text, or serialize the fragment to
11
+ # the database.
10
12
  #
11
13
  # The ActionText::RichText record serializes the `body` attribute as
12
- # +ActionText::Content+.
14
+ # `ActionText::Content`.
13
15
  #
14
- # class Message < ActiveRecord::Base
15
- # has_rich_text :content
16
- # end
16
+ # class Message < ActiveRecord::Base
17
+ # has_rich_text :content
18
+ # end
17
19
  #
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!"
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!"
22
24
  class Content
23
- include Rendering, Serialization
25
+ include Rendering, Serialization, ContentHelper
24
26
 
25
27
  attr_reader :fragment
26
28
 
29
+ delegate :deconstruct, to: :fragment
27
30
  delegate :blank?, :empty?, :html_safe, :present?, to: :to_html # Delegating to to_html to avoid including the layout
28
31
 
29
32
  class << self
@@ -46,19 +49,19 @@ module ActionText
46
49
 
47
50
  # Extracts links from the HTML fragment:
48
51
  #
49
- # html = '<a href="http://example.com/">Example</a>'
50
- # content = ActionText::Content.new(html)
51
- # content.links # => ["http://example.com/"]
52
+ # html = '<a href="http://example.com/">Example</a>'
53
+ # content = ActionText::Content.new(html)
54
+ # content.links # => ["http://example.com/"]
52
55
  def links
53
56
  @links ||= fragment.find_all("a[href]").map { |a| a["href"] }.uniq
54
57
  end
55
58
 
56
59
  # Extracts +ActionText::Attachment+s from the HTML fragment:
57
60
  #
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...
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...
62
65
  def attachments
63
66
  @attachments ||= attachment_nodes.map do |node|
64
67
  attachment_for_node(node)
@@ -77,10 +80,10 @@ module ActionText
77
80
 
78
81
  # Extracts +ActionText::Attachable+s from the HTML fragment:
79
82
  #
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]
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]
84
87
  def attachables
85
88
  @attachables ||= attachment_nodes.map do |node|
86
89
  ActionText::Attachable.from_node(node)
@@ -94,6 +97,10 @@ module ActionText
94
97
 
95
98
  def render_attachments(**options, &block)
96
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
97
104
  block.call(attachment_for_node(node, **options))
98
105
  end
99
106
  self.class.new(content, canonicalize: false)
@@ -106,10 +113,20 @@ module ActionText
106
113
  self.class.new(content, canonicalize: false)
107
114
  end
108
115
 
109
- # Returns the content as plain text with all HTML tags removed.
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"
110
124
  #
111
- # content = ActionText::Content.new("<h1>Funny times!</h1>")
112
- # content.to_plain_text # => "Funny times!"
125
+ # NOTE: that the returned string is not HTML safe and should not be rendered in
126
+ # browsers.
127
+ #
128
+ # content = ActionText::Content.new("&lt;script&gt;alert()&lt;/script&gt;")
129
+ # content.to_plain_text # => "<script>alert()</script>"
113
130
  def to_plain_text
114
131
  render_attachments(with_full_attributes: false, &:to_plain_text).fragment.to_plain_text
115
132
  end
@@ -130,6 +147,13 @@ module ActionText
130
147
  "action_text/contents/content"
131
148
  end
132
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>"
133
157
  def to_s
134
158
  to_rendered_html_with_layout
135
159
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  def self.deprecator # :nodoc:
5
7
  @deprecator ||= ActiveSupport::Deprecation.new
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionText
4
6
  module Encryption
5
7
  def encrypt
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "rails"
4
6
  require "action_controller/railtie"
5
7
  require "active_record/railtie"