actiontext 7.1.5.2 → 8.1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -132
- data/app/assets/javascripts/actiontext.esm.js +114 -14
- data/app/assets/javascripts/actiontext.js +120 -17
- data/app/helpers/action_text/content_helper.rb +2 -0
- data/app/helpers/action_text/tag_helper.rb +63 -37
- data/app/javascript/actiontext/attachment_upload.js +78 -12
- data/app/javascript/actiontext/index.js +10 -1
- data/app/models/action_text/encrypted_rich_text.rb +2 -2
- data/app/models/action_text/record.rb +2 -0
- data/app/models/action_text/rich_text.rb +62 -27
- data/db/migrate/20180528164100_create_action_text_tables.rb +1 -1
- data/lib/action_text/attachable.rb +35 -33
- data/lib/action_text/attachables/content_attachment.rb +2 -0
- data/lib/action_text/attachables/missing_attachable.rb +2 -0
- data/lib/action_text/attachables/remote_image.rb +2 -0
- data/lib/action_text/attachment.rb +27 -25
- data/lib/action_text/attachment_gallery.rb +2 -0
- data/lib/action_text/attachments/caching.rb +2 -0
- data/lib/action_text/attachments/minification.rb +2 -0
- data/lib/action_text/attachments/trix_conversion.rb +2 -0
- data/lib/action_text/attribute.rb +61 -27
- data/lib/action_text/content.rb +49 -28
- data/lib/action_text/deprecator.rb +2 -0
- data/lib/action_text/encryption.rb +2 -0
- data/lib/action_text/engine.rb +7 -2
- data/lib/action_text/fixture_set.rb +35 -35
- data/lib/action_text/fragment.rb +4 -0
- data/lib/action_text/gem_version.rb +6 -4
- data/lib/action_text/html_conversion.rb +2 -0
- data/lib/action_text/plain_text_conversion.rb +65 -28
- data/lib/action_text/rendering.rb +2 -1
- data/lib/action_text/serialization.rb +2 -0
- data/lib/action_text/system_test_helper.rb +52 -33
- data/lib/action_text/trix_attachment.rb +2 -0
- data/lib/action_text/version.rb +3 -1
- data/lib/generators/action_text/install/install_generator.rb +4 -21
- data/lib/generators/action_text/install/templates/actiontext.css +414 -5
- data/lib/rails/generators/test_unit/install_generator.rb +2 -0
- data/package.json +3 -2
- metadata +28 -16
- data/app/assets/javascripts/trix.js +0 -13720
- data/app/assets/stylesheets/trix.css +0 -412
|
@@ -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
|
require "action_view/helpers/tags/placeholderable"
|
|
5
7
|
|
|
@@ -7,21 +9,32 @@ module ActionText
|
|
|
7
9
|
module TagHelper
|
|
8
10
|
cattr_accessor(:id, instance_accessor: false) { 0 }
|
|
9
11
|
|
|
10
|
-
# Returns a
|
|
11
|
-
# that Trix will write to on changes, so the content will
|
|
12
|
+
# Returns a `trix-editor` tag that instantiates the Trix JavaScript editor as
|
|
13
|
+
# well as a hidden field that Trix will write to on changes, so the content will
|
|
14
|
+
# be sent on form submissions.
|
|
15
|
+
#
|
|
16
|
+
# #### Options
|
|
17
|
+
# * `:class` - Defaults to "trix-content" so that default styles will be
|
|
18
|
+
# applied. Setting this to a different value will prevent default styles
|
|
19
|
+
# from being applied.
|
|
20
|
+
# * `[:data][:direct_upload_url]` - Defaults to `rails_direct_uploads_url`.
|
|
21
|
+
# * `[:data][:blob_url_template]` - Defaults to
|
|
22
|
+
# `rails_service_blob_url(":signed_id", ":filename")`.
|
|
23
|
+
#
|
|
12
24
|
#
|
|
13
|
-
#
|
|
14
|
-
# * <tt>:class</tt> - Defaults to "trix-content" so that default styles will be applied.
|
|
15
|
-
# Setting this to a different value will prevent default styles from being applied.
|
|
16
|
-
# * <tt>[:data][:direct_upload_url]</tt> - Defaults to +rails_direct_uploads_url+.
|
|
17
|
-
# * <tt>[:data][:blob_url_template]</tt> - Defaults to <tt>rails_service_blob_url(":signed_id", ":filename")</tt>.
|
|
25
|
+
# #### Example
|
|
18
26
|
#
|
|
19
|
-
#
|
|
27
|
+
# rich_textarea_tag "content", message.content
|
|
28
|
+
# # <input type="hidden" name="content" id="trix_input_post_1">
|
|
29
|
+
# # <trix-editor id="content" input="trix_input_post_1" class="trix-content" ...></trix-editor>
|
|
20
30
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
31
|
+
# rich_textarea_tag "content", nil do
|
|
32
|
+
# "<h1>Default content</h1>"
|
|
33
|
+
# end
|
|
34
|
+
# # <input type="hidden" name="content" id="trix_input_post_1" value="<h1>Default content</h1>">
|
|
35
|
+
# # <trix-editor id="content" input="trix_input_post_1" class="trix-content" ...></trix-editor>
|
|
36
|
+
def rich_textarea_tag(name, value = nil, options = {}, &block)
|
|
37
|
+
value = capture(&block) if value.nil? && block_given?
|
|
25
38
|
options = options.symbolize_keys
|
|
26
39
|
form = options.delete(:form)
|
|
27
40
|
|
|
@@ -37,6 +50,7 @@ module ActionText
|
|
|
37
50
|
|
|
38
51
|
input_tag + editor_tag
|
|
39
52
|
end
|
|
53
|
+
alias_method :rich_text_area_tag, :rich_textarea_tag
|
|
40
54
|
end
|
|
41
55
|
end
|
|
42
56
|
|
|
@@ -46,48 +60,60 @@ module ActionView::Helpers
|
|
|
46
60
|
|
|
47
61
|
delegate :dom_id, to: ActionView::RecordIdentifier
|
|
48
62
|
|
|
49
|
-
def render
|
|
63
|
+
def render(&block)
|
|
50
64
|
options = @options.stringify_keys
|
|
51
|
-
|
|
65
|
+
add_default_name_and_field(options)
|
|
52
66
|
options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) if object
|
|
53
|
-
html_tag = @template_object.
|
|
67
|
+
html_tag = @template_object.rich_textarea_tag(options.delete("name"), options.fetch("value") { value }, options.except("value"), &block)
|
|
54
68
|
error_wrapping(html_tag)
|
|
55
69
|
end
|
|
56
70
|
end
|
|
57
71
|
|
|
58
72
|
module FormHelper
|
|
59
|
-
# Returns a
|
|
60
|
-
# that Trix will write to on changes, so the content will
|
|
73
|
+
# Returns a `trix-editor` tag that instantiates the Trix JavaScript editor as
|
|
74
|
+
# well as a hidden field that Trix will write to on changes, so the content will
|
|
75
|
+
# be sent on form submissions.
|
|
76
|
+
#
|
|
77
|
+
# #### Options
|
|
78
|
+
# * `:class` - Defaults to "trix-content" which ensures default styling is
|
|
79
|
+
# applied.
|
|
80
|
+
# * `:value` - Adds a default value to the HTML input tag.
|
|
81
|
+
# * `[:data][:direct_upload_url]` - Defaults to `rails_direct_uploads_url`.
|
|
82
|
+
# * `[:data][:blob_url_template]` - Defaults to
|
|
83
|
+
# `rails_service_blob_url(":signed_id", ":filename")`.
|
|
84
|
+
#
|
|
61
85
|
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# * <tt>[:data][:blob_url_template]</tt> - Defaults to <tt>rails_service_blob_url(":signed_id", ":filename")</tt>.
|
|
86
|
+
# #### Example
|
|
87
|
+
# rich_textarea :message, :content
|
|
88
|
+
# # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1">
|
|
89
|
+
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
|
|
67
90
|
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
|
|
91
|
+
# rich_textarea :message, :content, value: "<h1>Default message</h1>"
|
|
92
|
+
# # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1" value="<h1>Default message</h1>">
|
|
93
|
+
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
|
|
72
94
|
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
# rich_textarea :message, :content do
|
|
96
|
+
# "<h1>Default message</h1>"
|
|
97
|
+
# end
|
|
98
|
+
# # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1" value="<h1>Default message</h1>">
|
|
99
|
+
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
|
|
100
|
+
def rich_textarea(object_name, method, options = {}, &block)
|
|
101
|
+
Tags::ActionText.new(object_name, method, self, options).render(&block)
|
|
78
102
|
end
|
|
103
|
+
alias_method :rich_text_area, :rich_textarea
|
|
79
104
|
end
|
|
80
105
|
|
|
81
106
|
class FormBuilder
|
|
82
|
-
# Wraps ActionView::Helpers::FormHelper#
|
|
107
|
+
# Wraps ActionView::Helpers::FormHelper#rich_textarea for form builders:
|
|
83
108
|
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
109
|
+
# <%= form_with model: @message do |f| %>
|
|
110
|
+
# <%= f.rich_textarea :content %>
|
|
111
|
+
# <% end %>
|
|
87
112
|
#
|
|
88
113
|
# Please refer to the documentation of the base helper for details.
|
|
89
|
-
def
|
|
90
|
-
@template.
|
|
114
|
+
def rich_textarea(method, options = {}, &block)
|
|
115
|
+
@template.rich_textarea(@object_name, method, objectify_options(options), &block)
|
|
91
116
|
end
|
|
117
|
+
alias_method :rich_text_area, :rich_textarea
|
|
92
118
|
end
|
|
93
119
|
end
|
|
@@ -1,32 +1,86 @@
|
|
|
1
|
-
import { DirectUpload } from "@rails/activestorage"
|
|
1
|
+
import { DirectUpload, dispatchEvent } from "@rails/activestorage"
|
|
2
2
|
|
|
3
3
|
export class AttachmentUpload {
|
|
4
|
-
constructor(attachment, element) {
|
|
4
|
+
constructor(attachment, element, file = attachment.file) {
|
|
5
5
|
this.attachment = attachment
|
|
6
6
|
this.element = element
|
|
7
|
-
this.directUpload = new DirectUpload(
|
|
7
|
+
this.directUpload = new DirectUpload(file, this.directUploadUrl, this)
|
|
8
|
+
this.file = file
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
start() {
|
|
11
|
-
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
this.directUpload.create((error, attributes) => this.directUploadDidComplete(error, attributes, resolve, reject))
|
|
14
|
+
this.dispatch("start")
|
|
15
|
+
})
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
directUploadWillStoreFileWithXHR(xhr) {
|
|
15
19
|
xhr.upload.addEventListener("progress", event => {
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
// Scale upload progress to 0-90% range
|
|
21
|
+
const progress = (event.loaded / event.total) * 90
|
|
22
|
+
if (progress) {
|
|
23
|
+
this.dispatch("progress", { progress: progress })
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Start simulating progress after upload completes
|
|
28
|
+
xhr.upload.addEventListener("loadend", () => {
|
|
29
|
+
this.simulateResponseProgress(xhr)
|
|
18
30
|
})
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
simulateResponseProgress(xhr) {
|
|
34
|
+
let progress = 90
|
|
35
|
+
const startTime = Date.now()
|
|
36
|
+
|
|
37
|
+
const updateProgress = () => {
|
|
38
|
+
// Simulate progress from 90% to 99% over estimated time
|
|
39
|
+
const elapsed = Date.now() - startTime
|
|
40
|
+
const estimatedResponseTime = this.estimateResponseTime()
|
|
41
|
+
const responseProgress = Math.min(elapsed / estimatedResponseTime, 1)
|
|
42
|
+
progress = 90 + (responseProgress * 9) // 90% to 99%
|
|
43
|
+
|
|
44
|
+
this.dispatch("progress", { progress })
|
|
45
|
+
|
|
46
|
+
// Continue until response arrives or we hit 99%
|
|
47
|
+
if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
|
|
48
|
+
requestAnimationFrame(updateProgress)
|
|
49
|
+
}
|
|
24
50
|
}
|
|
25
51
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
// Stop simulation when response arrives
|
|
53
|
+
xhr.addEventListener("loadend", () => {
|
|
54
|
+
this.dispatch("progress", { progress: 100 })
|
|
29
55
|
})
|
|
56
|
+
|
|
57
|
+
requestAnimationFrame(updateProgress)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
estimateResponseTime() {
|
|
61
|
+
// Base estimate: 1 second for small files, scaling up for larger files
|
|
62
|
+
const fileSize = this.file.size
|
|
63
|
+
const MB = 1024 * 1024
|
|
64
|
+
|
|
65
|
+
if (fileSize < MB) {
|
|
66
|
+
return 1000 // 1 second for files under 1MB
|
|
67
|
+
} else if (fileSize < 10 * MB) {
|
|
68
|
+
return 2000 // 2 seconds for files 1-10MB
|
|
69
|
+
} else {
|
|
70
|
+
return 3000 + (fileSize / MB * 50) // 3+ seconds for larger files
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
directUploadDidComplete(error, attributes, resolve, reject) {
|
|
75
|
+
if (error) {
|
|
76
|
+
this.dispatchError(error, reject)
|
|
77
|
+
} else {
|
|
78
|
+
resolve({
|
|
79
|
+
sgid: attributes.attachable_sgid,
|
|
80
|
+
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
|
81
|
+
})
|
|
82
|
+
this.dispatch("end")
|
|
83
|
+
}
|
|
30
84
|
}
|
|
31
85
|
|
|
32
86
|
createBlobUrl(signedId, filename) {
|
|
@@ -35,6 +89,18 @@ export class AttachmentUpload {
|
|
|
35
89
|
.replace(":filename", encodeURIComponent(filename))
|
|
36
90
|
}
|
|
37
91
|
|
|
92
|
+
dispatch(name, detail = {}) {
|
|
93
|
+
detail.attachment = this.attachment
|
|
94
|
+
return dispatchEvent(this.element, `direct-upload:${name}`, { detail })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
dispatchError(error, reject) {
|
|
98
|
+
const event = this.dispatch("error", { error })
|
|
99
|
+
if (!event.defaultPrevented) {
|
|
100
|
+
reject(error)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
38
104
|
get directUploadUrl() {
|
|
39
105
|
return this.element.dataset.directUploadUrl
|
|
40
106
|
}
|
|
@@ -4,7 +4,16 @@ addEventListener("trix-attachment-add", event => {
|
|
|
4
4
|
const { attachment, target } = event
|
|
5
5
|
|
|
6
6
|
if (attachment.file) {
|
|
7
|
-
const upload = new AttachmentUpload(attachment, target)
|
|
7
|
+
const upload = new AttachmentUpload(attachment, target, attachment.file)
|
|
8
|
+
const onProgress = event => attachment.setUploadProgress(event.detail.progress)
|
|
9
|
+
|
|
10
|
+
target.addEventListener("direct-upload:progress", onProgress)
|
|
11
|
+
|
|
8
12
|
upload.start()
|
|
13
|
+
.then(attributes => attachment.setAttributes(attributes))
|
|
14
|
+
.catch(error => alert(error))
|
|
15
|
+
.finally(() => target.removeEventListener("direct-upload:progress", onProgress))
|
|
9
16
|
}
|
|
10
17
|
})
|
|
18
|
+
|
|
19
|
+
export { AttachmentUpload }
|
|
@@ -1,55 +1,90 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionText
|
|
4
|
-
#
|
|
6
|
+
# # Action Text RichText
|
|
5
7
|
#
|
|
6
|
-
# The RichText record holds the content produced by the Trix editor in a
|
|
7
|
-
# It also holds all the references to the embedded
|
|
8
|
-
#
|
|
9
|
-
#
|
|
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
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
+
# class Message < ActiveRecord::Base
|
|
15
|
+
# has_rich_text :content
|
|
16
|
+
# end
|
|
14
17
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
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
|
-
|
|
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::Attachment records from the embedded files.
|
|
52
|
+
#
|
|
53
|
+
# Attached ActiveStorage::Blob records are extracted from the `body`
|
|
54
|
+
# in a {before_validation}[rdoc-ref:ActiveModel::Validations::Callbacks::ClassMethods#before_validation] callback.
|
|
27
55
|
has_many_attached :embeds
|
|
28
56
|
|
|
29
|
-
|
|
57
|
+
before_validation do
|
|
30
58
|
self.embeds = body.attachables.grep(ActiveStorage::Blob).uniq if body.present?
|
|
31
59
|
end
|
|
32
60
|
|
|
33
|
-
# Returns
|
|
61
|
+
# Returns a plain-text version of the markup contained by the `body` attribute,
|
|
62
|
+
# with tags removed but HTML entities encoded.
|
|
63
|
+
#
|
|
64
|
+
# message = Message.create!(content: "<h1>Funny times!</h1>")
|
|
65
|
+
# message.content.to_plain_text # => "Funny times!"
|
|
66
|
+
#
|
|
67
|
+
# NOTE: that the returned string is not HTML safe and should not be rendered in
|
|
68
|
+
# browsers.
|
|
34
69
|
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
70
|
+
# message = Message.create!(content: "<script>alert()</script>")
|
|
71
|
+
# message.content.to_plain_text # => "<script>alert()</script>"
|
|
37
72
|
def to_plain_text
|
|
38
73
|
body&.to_plain_text.to_s
|
|
39
74
|
end
|
|
40
75
|
|
|
41
|
-
# Returns the
|
|
76
|
+
# Returns the `body` attribute in a format that makes it editable in the Trix
|
|
42
77
|
# editor. Previews of attachments are rendered inline.
|
|
43
78
|
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
79
|
+
# content = "<h1>Funny Times!</h1><figure data-trix-attachment='{\"sgid\":\"..."\}'></figure>"
|
|
80
|
+
# message = Message.create!(content: content)
|
|
81
|
+
# message.content.to_trix_html # =>
|
|
82
|
+
# # <div class="trix-content">
|
|
83
|
+
# # <h1>Funny times!</h1>
|
|
84
|
+
# # <figure data-trix-attachment='{\"sgid\":\"..."\}'>
|
|
85
|
+
# # <img src="http://example.org/rails/active_storage/.../funny.jpg">
|
|
86
|
+
# # </figure>
|
|
87
|
+
# # </div>
|
|
53
88
|
def to_trix_html
|
|
54
89
|
body&.to_trix_html
|
|
55
90
|
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
|
-
#
|
|
6
|
+
# # Action Text Attachable
|
|
5
7
|
#
|
|
6
8
|
# Include this module to make a record attachable to an ActionText::Content.
|
|
7
9
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
10
|
+
# class Person < ApplicationRecord
|
|
11
|
+
# include ActionText::Attachable
|
|
12
|
+
# end
|
|
11
13
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
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
|
|
24
|
+
# Extracts the `ActionText::Attachable` from the attachment HTML node:
|
|
23
25
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
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
|
|
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
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
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
|
|
118
|
+
# Defaults to `to_partial_path`.
|
|
117
119
|
#
|
|
118
120
|
# Override to render a different partial:
|
|
119
121
|
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
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
|