actiontext 7.2.2.1 → 8.0.0.beta1
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 +24 -65
- data/app/assets/javascripts/actiontext.esm.js +27 -5
- data/app/assets/javascripts/actiontext.js +27 -5
- data/app/assets/javascripts/trix.js +203 -1758
- data/app/helpers/action_text/tag_helper.rb +13 -10
- data/app/javascript/actiontext/attachment_upload.js +24 -7
- data/lib/action_text/attribute.rb +25 -5
- data/lib/action_text/gem_version.rb +4 -4
- data/lib/action_text/plain_text_conversion.rb +1 -6
- data/lib/action_text/system_test_helper.rb +24 -21
- data/lib/generators/action_text/install/install_generator.rb +0 -12
- data/lib/generators/action_text/install/templates/actiontext.css +414 -5
- data/package.json +2 -2
- metadata +15 -15
@@ -24,10 +24,10 @@ module ActionText
|
|
24
24
|
#
|
25
25
|
# #### Example
|
26
26
|
#
|
27
|
-
#
|
27
|
+
# rich_textarea_tag "content", message.content
|
28
28
|
# # <input type="hidden" name="content" id="trix_input_post_1">
|
29
29
|
# # <trix-editor id="content" input="trix_input_post_1" class="trix-content" ...></trix-editor>
|
30
|
-
def
|
30
|
+
def rich_textarea_tag(name, value = nil, options = {})
|
31
31
|
options = options.symbolize_keys
|
32
32
|
form = options.delete(:form)
|
33
33
|
|
@@ -43,6 +43,7 @@ module ActionText
|
|
43
43
|
|
44
44
|
input_tag + editor_tag
|
45
45
|
end
|
46
|
+
alias_method :rich_text_area_tag, :rich_textarea_tag
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
@@ -56,7 +57,7 @@ module ActionView::Helpers
|
|
56
57
|
options = @options.stringify_keys
|
57
58
|
add_default_name_and_id(options)
|
58
59
|
options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) if object
|
59
|
-
html_tag = @template_object.
|
60
|
+
html_tag = @template_object.rich_textarea_tag(options.delete("name"), options.fetch("value") { value }, options.except("value"))
|
60
61
|
error_wrapping(html_tag)
|
61
62
|
end
|
62
63
|
end
|
@@ -76,28 +77,30 @@ module ActionView::Helpers
|
|
76
77
|
#
|
77
78
|
#
|
78
79
|
# #### Example
|
79
|
-
#
|
80
|
+
# rich_textarea :message, :content
|
80
81
|
# # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1">
|
81
82
|
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
|
82
83
|
#
|
83
|
-
#
|
84
|
+
# rich_textarea :message, :content, value: "<h1>Default message</h1>"
|
84
85
|
# # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1" value="<h1>Default message</h1>">
|
85
86
|
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
|
86
|
-
def
|
87
|
+
def rich_textarea(object_name, method, options = {})
|
87
88
|
Tags::ActionText.new(object_name, method, self, options).render
|
88
89
|
end
|
90
|
+
alias_method :rich_text_area, :rich_textarea
|
89
91
|
end
|
90
92
|
|
91
93
|
class FormBuilder
|
92
|
-
# Wraps ActionView::Helpers::FormHelper#
|
94
|
+
# Wraps ActionView::Helpers::FormHelper#rich_textarea for form builders:
|
93
95
|
#
|
94
96
|
# <%= form_with model: @message do |f| %>
|
95
|
-
# <%= f.
|
97
|
+
# <%= f.rich_textarea :content %>
|
96
98
|
# <% end %>
|
97
99
|
#
|
98
100
|
# Please refer to the documentation of the base helper for details.
|
99
|
-
def
|
100
|
-
@template.
|
101
|
+
def rich_textarea(method, options = {})
|
102
|
+
@template.rich_textarea(@object_name, method, objectify_options(options))
|
101
103
|
end
|
104
|
+
alias_method :rich_text_area, :rich_textarea
|
102
105
|
end
|
103
106
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { DirectUpload } from "@rails/activestorage"
|
1
|
+
import { DirectUpload, dispatchEvent } from "@rails/activestorage"
|
2
2
|
|
3
3
|
export class AttachmentUpload {
|
4
4
|
constructor(attachment, element) {
|
@@ -9,24 +9,29 @@ export class AttachmentUpload {
|
|
9
9
|
|
10
10
|
start() {
|
11
11
|
this.directUpload.create(this.directUploadDidComplete.bind(this))
|
12
|
+
this.dispatch("start")
|
12
13
|
}
|
13
14
|
|
14
15
|
directUploadWillStoreFileWithXHR(xhr) {
|
15
16
|
xhr.upload.addEventListener("progress", event => {
|
16
17
|
const progress = event.loaded / event.total * 100
|
17
18
|
this.attachment.setUploadProgress(progress)
|
19
|
+
if (progress) {
|
20
|
+
this.dispatch("progress", { progress: progress })
|
21
|
+
}
|
18
22
|
})
|
19
23
|
}
|
20
24
|
|
21
25
|
directUploadDidComplete(error, attributes) {
|
22
26
|
if (error) {
|
23
|
-
|
27
|
+
this.dispatchError(error)
|
28
|
+
} else {
|
29
|
+
this.attachment.setAttributes({
|
30
|
+
sgid: attributes.attachable_sgid,
|
31
|
+
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
32
|
+
})
|
33
|
+
this.dispatch("end")
|
24
34
|
}
|
25
|
-
|
26
|
-
this.attachment.setAttributes({
|
27
|
-
sgid: attributes.attachable_sgid,
|
28
|
-
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
29
|
-
})
|
30
35
|
}
|
31
36
|
|
32
37
|
createBlobUrl(signedId, filename) {
|
@@ -35,6 +40,18 @@ export class AttachmentUpload {
|
|
35
40
|
.replace(":filename", encodeURIComponent(filename))
|
36
41
|
}
|
37
42
|
|
43
|
+
dispatch(name, detail = {}) {
|
44
|
+
detail.attachment = this.attachment
|
45
|
+
return dispatchEvent(this.element, `direct-upload:${name}`, { detail })
|
46
|
+
}
|
47
|
+
|
48
|
+
dispatchError(error) {
|
49
|
+
const event = this.dispatch("error", { error })
|
50
|
+
if (!event.defaultPrevented) {
|
51
|
+
alert(error);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
38
55
|
get directUploadUrl() {
|
39
56
|
return this.element.dataset.directUploadUrl
|
40
57
|
}
|
@@ -41,13 +41,16 @@ module ActionText
|
|
41
41
|
# `strict_loading:` will be set to the value of the
|
42
42
|
# `strict_loading_by_default` class attribute (false by default).
|
43
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
|
+
#
|
44
47
|
#
|
45
48
|
# Note: Action Text relies on polymorphic associations, which in turn store
|
46
49
|
# class names in the database. When renaming classes that use `has_rich_text`,
|
47
50
|
# make sure to also update the class names in the
|
48
51
|
# `action_text_rich_texts.record_type` polymorphic type column of the
|
49
52
|
# corresponding rows.
|
50
|
-
def has_rich_text(name, encrypted: false, strict_loading: strict_loading_by_default)
|
53
|
+
def has_rich_text(name, encrypted: false, strict_loading: strict_loading_by_default, store_if_blank: true)
|
51
54
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
52
55
|
def #{name}
|
53
56
|
rich_text_#{name} || build_rich_text_#{name}
|
@@ -56,12 +59,29 @@ module ActionText
|
|
56
59
|
def #{name}?
|
57
60
|
rich_text_#{name}.present?
|
58
61
|
end
|
59
|
-
|
60
|
-
def #{name}=(body)
|
61
|
-
self.#{name}.body = body
|
62
|
-
end
|
63
62
|
CODE
|
64
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
|
+
|
65
85
|
rich_text_class_name = encrypted ? "ActionText::EncryptedRichText" : "ActionText::RichText"
|
66
86
|
has_one :"rich_text_#{name}", -> { where(name: name) },
|
67
87
|
class_name: rich_text_class_name, as: :record, inverse_of: :record, autosave: true, dependent: :destroy,
|
@@ -65,12 +65,7 @@ module ActionText
|
|
65
65
|
|
66
66
|
def plain_text_for_blockquote_node(node, index)
|
67
67
|
text = plain_text_for_block(node)
|
68
|
-
|
69
|
-
|
70
|
-
text = text.dup
|
71
|
-
text.insert(text.rindex(/\S/) + 1, "”")
|
72
|
-
text.insert(text.index(/\S/), "“")
|
73
|
-
text
|
68
|
+
text.sub(/\A(\s*)(.+?)(\s*)\Z/m, '\1“\2”\3')
|
74
69
|
end
|
75
70
|
|
76
71
|
def plain_text_for_li_node(node, index)
|
@@ -17,42 +17,45 @@ module ActionText
|
|
17
17
|
# Examples:
|
18
18
|
#
|
19
19
|
# # <trix-editor id="message_content" ...></trix-editor>
|
20
|
-
#
|
20
|
+
# fill_in_rich_textarea "message_content", with: "Hello <em>world!</em>"
|
21
21
|
#
|
22
22
|
# # <trix-editor placeholder="Your message here" ...></trix-editor>
|
23
|
-
#
|
23
|
+
# fill_in_rich_textarea "Your message here", with: "Hello <em>world!</em>"
|
24
24
|
#
|
25
25
|
# # <label for="message_content">Message content</label>
|
26
26
|
# # <trix-editor id="message_content" ...></trix-editor>
|
27
|
-
#
|
27
|
+
# fill_in_rich_textarea "Message content", with: "Hello <em>world!</em>"
|
28
28
|
#
|
29
29
|
# # <trix-editor aria-label="Message content" ...></trix-editor>
|
30
|
-
#
|
30
|
+
# fill_in_rich_textarea "Message content", with: "Hello <em>world!</em>"
|
31
31
|
#
|
32
32
|
# # <input id="trix_input_1" name="message[content]" type="hidden">
|
33
33
|
# # <trix-editor input="trix_input_1"></trix-editor>
|
34
|
-
#
|
35
|
-
def
|
36
|
-
find(:
|
34
|
+
# fill_in_rich_textarea "message[content]", with: "Hello <em>world!</em>"
|
35
|
+
def fill_in_rich_textarea(locator = nil, with:)
|
36
|
+
find(:rich_textarea, locator).execute_script("this.editor.loadHTML(arguments[0])", with.to_s)
|
37
37
|
end
|
38
|
+
alias_method :fill_in_rich_text_area, :fill_in_rich_textarea
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
%i[rich_textarea rich_text_area].each do |rich_textarea|
|
43
|
+
Capybara.add_selector rich_textarea do
|
44
|
+
label "rich-text area"
|
45
|
+
xpath do |locator|
|
46
|
+
if locator.nil?
|
47
|
+
XPath.descendant(:"trix-editor")
|
48
|
+
else
|
49
|
+
input_located_by_name = XPath.anywhere(:input).where(XPath.attr(:name) == locator).attr(:id)
|
50
|
+
input_located_by_label = XPath.anywhere(:label).where(XPath.string.n.is(locator)).attr(:for)
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
XPath.descendant(:"trix-editor").where \
|
53
|
+
XPath.attr(:id).equals(locator) |
|
54
|
+
XPath.attr(:placeholder).equals(locator) |
|
55
|
+
XPath.attr(:"aria-label").equals(locator) |
|
56
|
+
XPath.attr(:input).equals(input_located_by_name) |
|
57
|
+
XPath.attr(:id).equals(input_located_by_label)
|
58
|
+
end
|
56
59
|
end
|
57
60
|
end
|
58
61
|
end
|
@@ -36,20 +36,8 @@ module ActionText
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def create_actiontext_files
|
39
|
-
destination = Pathname(destination_root)
|
40
|
-
|
41
39
|
template "actiontext.css", "app/assets/stylesheets/actiontext.css"
|
42
40
|
|
43
|
-
unless destination.join("app/assets/application.css").exist?
|
44
|
-
if (stylesheets = Dir.glob "#{destination_root}/app/assets/stylesheets/application.*.{scss,css}").length > 0
|
45
|
-
insert_into_file stylesheets.first.to_s, %(@import 'actiontext.css';)
|
46
|
-
else
|
47
|
-
say <<~INSTRUCTIONS, :green
|
48
|
-
To use the Trix editor, you must require 'app/assets/stylesheets/actiontext.css' in your base stylesheet.
|
49
|
-
INSTRUCTIONS
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
41
|
gem_root = "#{__dir__}/../../../.."
|
54
42
|
|
55
43
|
copy_file "#{gem_root}/app/views/active_storage/blobs/_blob.html.erb",
|