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.

@@ -4,9 +4,9 @@ require "rails-html-sanitizer"
4
4
 
5
5
  module ActionText
6
6
  module ContentHelper
7
- mattr_accessor(:sanitizer) { Rails::Html::Sanitizer.safe_list_sanitizer.new }
8
- mattr_accessor(:allowed_tags) { sanitizer.class.allowed_tags + [ ActionText::Attachment.tag_name, "figure", "figcaption" ] }
9
- mattr_accessor(:allowed_attributes) { sanitizer.class.allowed_attributes + ActionText::Attachment::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(content.to_html, tags: allowed_tags, attributes: allowed_attributes, scrubber: scrubber).html_safe
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
@@ -7,3 +7,5 @@ module ActionText
7
7
  encrypts :body
8
8
  end
9
9
  end
10
+
11
+ ActiveSupport.run_load_hooks :action_text_encrypted_rich_text, ActionText::EncryptedRichText
@@ -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
@@ -0,0 +1,3 @@
1
+ <figure class="attachment attachment--content">
2
+ <%= content_attachment.attachable %>
3
+ </figure>
@@ -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
- if node["content-type"]
10
- if matches = node["content-type"].match(/vnd\.rubyonrails\.(.+)\.html/)
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 :name
18
- validates_inclusion_of :name, in: %w( horizontal-rule )
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
- case name
22
- when "horizontal-rule"
23
- " ┄ "
24
- else
25
- " "
26
- end
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
- def to_trix_content_attachment_partial_path
34
- "action_text/attachables/content_attachments/#{name.underscore}"
35
- end
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
- module MissingAttachable
5
+ class MissingAttachable
6
6
  extend ActiveModel::Naming
7
7
 
8
- def self.to_partial_path
9
- "action_text/attachables/missing_attachable"
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.map { |name| [ name.underscore, node[name] ] }.to_h.compact
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
- # === Options
29
+ # ==== Options
30
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)
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 }) }
@@ -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} #{to_s.truncate(25).inspect}>"
142
+ "#<#{self.class.name} #{to_html.truncate(25).inspect}>"
104
143
  end
105
144
 
106
145
  def ==(other)
107
- if other.is_a?(self.class)
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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionText
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -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 |abstract_controller|
55
- ActiveSupport.on_load(abstract_controller) do
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
- ActiveSupport.on_load(:action_text_content) do
63
- self.default_renderer = Class.new(ActionController::Base).renderer
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
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionText
4
+ # = Action Text \FixtureSet
5
+ #
4
6
  # Fixtures are a way of organizing data that you want to test against; in
5
7
  # short, sample data.
6
8
  #
@@ -7,7 +7,7 @@ module ActionText
7
7
  case fragment_or_html
8
8
  when self
9
9
  fragment_or_html
10
- when Nokogiri::HTML::DocumentFragment
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.clone
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
- node.replace(yield(node).to_s)
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 <tt>Gem::Version</tt>.
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 = 0
12
- TINY = 8
13
- PRE = "2"
11
+ MINOR = 1
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -18,7 +18,7 @@ module ActionText
18
18
 
19
19
  private
20
20
  def document
21
- Nokogiri::HTML::Document.new.tap { |doc| doc.encoding = "UTF-8" }
21
+ ActionText.html_document_class.new.tap { |doc| doc.encoding = "UTF-8" }
22
22
  end
23
23
  end
24
24
  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 || default_renderer).render_to_string(*args, &block)
29
+ (renderer || action_controller_renderer).render_to_string(*args, &block)
27
30
  end
28
31
  end
29
32
  end