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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +42 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.md +13 -0
  5. data/app/assets/javascripts/actiontext.esm.js +911 -0
  6. data/app/assets/javascripts/actiontext.js +884 -0
  7. data/app/assets/javascripts/trix.js +12165 -0
  8. data/app/assets/stylesheets/trix.css +412 -0
  9. data/app/helpers/action_text/content_helper.rb +76 -0
  10. data/app/helpers/action_text/tag_helper.rb +106 -0
  11. data/app/javascript/actiontext/attachment_upload.js +62 -0
  12. data/app/javascript/actiontext/index.js +10 -0
  13. data/app/models/action_text/encrypted_rich_text.rb +11 -0
  14. data/app/models/action_text/record.rb +11 -0
  15. data/app/models/action_text/rich_text.rb +93 -0
  16. data/app/views/action_text/attachables/_content_attachment.html.erb +3 -0
  17. data/app/views/action_text/attachables/_missing_attachable.html.erb +1 -0
  18. data/app/views/action_text/attachables/_remote_image.html.erb +8 -0
  19. data/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb +3 -0
  20. data/app/views/action_text/contents/_content.html.erb +1 -0
  21. data/app/views/active_storage/blobs/_blob.html.erb +14 -0
  22. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  23. data/db/migrate/20180528164100_create_action_text_tables.rb +25 -0
  24. data/lib/action_text/attachable.rb +156 -0
  25. data/lib/action_text/attachables/content_attachment.rb +42 -0
  26. data/lib/action_text/attachables/missing_attachable.rb +29 -0
  27. data/lib/action_text/attachables/remote_image.rb +48 -0
  28. data/lib/action_text/attachment.rb +148 -0
  29. data/lib/action_text/attachment_gallery.rb +72 -0
  30. data/lib/action_text/attachments/caching.rb +18 -0
  31. data/lib/action_text/attachments/minification.rb +19 -0
  32. data/lib/action_text/attachments/trix_conversion.rb +38 -0
  33. data/lib/action_text/attribute.rb +105 -0
  34. data/lib/action_text/content.rb +197 -0
  35. data/lib/action_text/deprecator.rb +9 -0
  36. data/lib/action_text/encryption.rb +40 -0
  37. data/lib/action_text/engine.rb +94 -0
  38. data/lib/action_text/fixture_set.rb +68 -0
  39. data/lib/action_text/fragment.rb +62 -0
  40. data/lib/action_text/gem_version.rb +19 -0
  41. data/lib/action_text/html_conversion.rb +26 -0
  42. data/lib/action_text/plain_text_conversion.rb +114 -0
  43. data/lib/action_text/rendering.rb +35 -0
  44. data/lib/action_text/serialization.rb +38 -0
  45. data/lib/action_text/system_test_helper.rb +61 -0
  46. data/lib/action_text/trix_attachment.rb +94 -0
  47. data/lib/action_text/version.rb +12 -0
  48. data/lib/action_text.rb +59 -0
  49. data/lib/generators/action_text/install/install_generator.rb +84 -0
  50. data/lib/generators/action_text/install/templates/actiontext.css +440 -0
  51. data/lib/rails/generators/test_unit/install_generator.rb +15 -0
  52. data/lib/rails/generators/test_unit/templates/fixtures.yml +4 -0
  53. data/lib/tasks/actiontext.rake +6 -0
  54. data/package.json +39 -0
  55. 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("&lt;script&gt;alert()&lt;/script&gt;")
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionText
6
+ def self.deprecator # :nodoc:
7
+ @deprecator ||= ActiveSupport::Deprecation.new
8
+ end
9
+ end
@@ -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