actiontext 8.1.0.beta1 → 8.1.0.rc1
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 +40 -0
- data/app/assets/javascripts/actiontext.esm.js +19 -15
- data/app/assets/javascripts/actiontext.js +25 -18
- data/app/helpers/action_text/tag_helper.rb +22 -9
- data/app/javascript/actiontext/attachment_upload.js +13 -13
- data/app/javascript/actiontext/index.js +10 -1
- data/lib/action_text/gem_version.rb +1 -1
- data/lib/action_text/plain_text_conversion.rb +60 -31
- data/lib/action_text/system_test_helper.rb +14 -3
- data/lib/generators/action_text/install/install_generator.rb +0 -12
- data/package.json +1 -1
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f8a237e651fd987b5cd33e599577648aedf8a2ff14decaee0b2a3c2b2c6ab1c
|
4
|
+
data.tar.gz: 9a84f4bb3bc90cddc9c651e3571ee872a87add9150b33e28d3eef50e728493f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3fb8c1e86cd5017a454ba8615db889b4775ea0afe9f276fcfb46ed2b82583a85a6a11daf097624104a1600753b9c995c5dd7720ce289499fab9d7e57814ab22
|
7
|
+
data.tar.gz: 340ac8fc6e495d800d75ba68c205b9213d34e2cc925b542e4f46eb32fcdebda2fe4d439125219bc6d1cab4b3b01789034e0c163e23868c51c5293c294db06d31
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,43 @@
|
|
1
|
+
## Rails 8.1.0.rc1 (October 15, 2025) ##
|
2
|
+
|
3
|
+
* De-couple `@rails/actiontext/attachment_upload.js` from `Trix.Attachment`
|
4
|
+
|
5
|
+
Implement `@rails/actiontext/index.js` with a `direct-upload:progress` event
|
6
|
+
listeners and `Promise` resolution.
|
7
|
+
|
8
|
+
*Sean Doyle*
|
9
|
+
|
10
|
+
* Capture block content for form helper methods
|
11
|
+
|
12
|
+
```erb
|
13
|
+
<%= rich_textarea_tag :content, nil do %>
|
14
|
+
<h1>hello world</h1>
|
15
|
+
<% end %>
|
16
|
+
<!-- <input type="hidden" name="content" id="trix_input_1" value="<h1>hello world</h1>"/><trix-editor … -->
|
17
|
+
|
18
|
+
<%= rich_textarea :message, :content, input: "trix_input_1" do %>
|
19
|
+
<h1>hello world</h1>
|
20
|
+
<% end %>
|
21
|
+
<!-- <input type="hidden" name="message[content]" id="trix_input_1" value="<h1>hello world</h1>"/><trix-editor … -->
|
22
|
+
|
23
|
+
<%= form_with model: Message.new do |form| %>
|
24
|
+
<%= form.rich_textarea :content do %>
|
25
|
+
<h1>hello world</h1>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
28
|
+
<!-- <form action="/messages" accept-charset="UTF-8" method="post"><input type="hidden" name="message[content]" id="message_content_trix_input_message" value="<h1>hello world</h1>"/><trix-editor … -->
|
29
|
+
```
|
30
|
+
|
31
|
+
*Sean Doyle*
|
32
|
+
|
33
|
+
* Generalize `:rich_text_area` Capybara selector
|
34
|
+
|
35
|
+
Prepare for more Action Text-capable WYSIWYG editors by making
|
36
|
+
`:rich_text_area` rely on the presence of `[role="textbox"]` and
|
37
|
+
`[contenteditable]` HTML attributes rather than a `<trix-editor>` element.
|
38
|
+
|
39
|
+
*Sean Doyle*
|
40
|
+
|
1
41
|
## Rails 8.1.0.beta1 (September 04, 2025) ##
|
2
42
|
|
3
43
|
* Forward `fill_in_rich_text_area` options to Capybara
|
@@ -882,19 +882,21 @@ function autostart() {
|
|
882
882
|
setTimeout(autostart, 1);
|
883
883
|
|
884
884
|
class AttachmentUpload {
|
885
|
-
constructor(attachment, element) {
|
885
|
+
constructor(attachment, element, file = attachment.file) {
|
886
886
|
this.attachment = attachment;
|
887
887
|
this.element = element;
|
888
|
-
this.directUpload = new DirectUpload(
|
888
|
+
this.directUpload = new DirectUpload(file, this.directUploadUrl, this);
|
889
|
+
this.file = file;
|
889
890
|
}
|
890
891
|
start() {
|
891
|
-
|
892
|
-
|
892
|
+
return new Promise(((resolve, reject) => {
|
893
|
+
this.directUpload.create(((error, attributes) => this.directUploadDidComplete(error, attributes, resolve, reject)));
|
894
|
+
this.dispatch("start");
|
895
|
+
}));
|
893
896
|
}
|
894
897
|
directUploadWillStoreFileWithXHR(xhr) {
|
895
898
|
xhr.upload.addEventListener("progress", (event => {
|
896
899
|
const progress = event.loaded / event.total * 90;
|
897
|
-
this.attachment.setUploadProgress(progress);
|
898
900
|
if (progress) {
|
899
901
|
this.dispatch("progress", {
|
900
902
|
progress: progress
|
@@ -913,7 +915,6 @@ class AttachmentUpload {
|
|
913
915
|
const estimatedResponseTime = this.estimateResponseTime();
|
914
916
|
const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
|
915
917
|
progress = 90 + responseProgress * 9;
|
916
|
-
this.attachment.setUploadProgress(progress);
|
917
918
|
this.dispatch("progress", {
|
918
919
|
progress: progress
|
919
920
|
});
|
@@ -922,7 +923,6 @@ class AttachmentUpload {
|
|
922
923
|
}
|
923
924
|
};
|
924
925
|
xhr.addEventListener("loadend", (() => {
|
925
|
-
this.attachment.setUploadProgress(100);
|
926
926
|
this.dispatch("progress", {
|
927
927
|
progress: 100
|
928
928
|
});
|
@@ -930,7 +930,7 @@ class AttachmentUpload {
|
|
930
930
|
requestAnimationFrame(updateProgress);
|
931
931
|
}
|
932
932
|
estimateResponseTime() {
|
933
|
-
const fileSize = this.
|
933
|
+
const fileSize = this.file.size;
|
934
934
|
const MB = 1024 * 1024;
|
935
935
|
if (fileSize < MB) {
|
936
936
|
return 1e3;
|
@@ -940,11 +940,11 @@ class AttachmentUpload {
|
|
940
940
|
return 3e3 + fileSize / MB * 50;
|
941
941
|
}
|
942
942
|
}
|
943
|
-
directUploadDidComplete(error, attributes) {
|
943
|
+
directUploadDidComplete(error, attributes, resolve, reject) {
|
944
944
|
if (error) {
|
945
|
-
this.dispatchError(error);
|
945
|
+
this.dispatchError(error, reject);
|
946
946
|
} else {
|
947
|
-
|
947
|
+
resolve({
|
948
948
|
sgid: attributes.attachable_sgid,
|
949
949
|
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
950
950
|
});
|
@@ -960,12 +960,12 @@ class AttachmentUpload {
|
|
960
960
|
detail: detail
|
961
961
|
});
|
962
962
|
}
|
963
|
-
dispatchError(error) {
|
963
|
+
dispatchError(error, reject) {
|
964
964
|
const event = this.dispatch("error", {
|
965
965
|
error: error
|
966
966
|
});
|
967
967
|
if (!event.defaultPrevented) {
|
968
|
-
|
968
|
+
reject(error);
|
969
969
|
}
|
970
970
|
}
|
971
971
|
get directUploadUrl() {
|
@@ -979,7 +979,11 @@ class AttachmentUpload {
|
|
979
979
|
addEventListener("trix-attachment-add", (event => {
|
980
980
|
const {attachment: attachment, target: target} = event;
|
981
981
|
if (attachment.file) {
|
982
|
-
const upload = new AttachmentUpload(attachment, target);
|
983
|
-
|
982
|
+
const upload = new AttachmentUpload(attachment, target, attachment.file);
|
983
|
+
const onProgress = event => attachment.setUploadProgress(event.detail.progress);
|
984
|
+
target.addEventListener("direct-upload:progress", onProgress);
|
985
|
+
upload.start().then((attributes => attachment.setAttributes(attributes))).catch((error => alert(error))).finally((() => target.removeEventListener("direct-upload:progress", onProgress)));
|
984
986
|
}
|
985
987
|
}));
|
988
|
+
|
989
|
+
export { AttachmentUpload };
|
@@ -1,6 +1,7 @@
|
|
1
|
-
(function(factory) {
|
2
|
-
typeof define === "function" && define.amd ? define(factory) :
|
3
|
-
})
|
1
|
+
(function(global, factory) {
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self,
|
3
|
+
factory(global.ActionText = {}));
|
4
|
+
})(this, (function(exports) {
|
4
5
|
"use strict";
|
5
6
|
var sparkMd5 = {
|
6
7
|
exports: {}
|
@@ -855,19 +856,21 @@
|
|
855
856
|
}
|
856
857
|
setTimeout(autostart, 1);
|
857
858
|
class AttachmentUpload {
|
858
|
-
constructor(attachment, element) {
|
859
|
+
constructor(attachment, element, file = attachment.file) {
|
859
860
|
this.attachment = attachment;
|
860
861
|
this.element = element;
|
861
|
-
this.directUpload = new DirectUpload(
|
862
|
+
this.directUpload = new DirectUpload(file, this.directUploadUrl, this);
|
863
|
+
this.file = file;
|
862
864
|
}
|
863
865
|
start() {
|
864
|
-
|
865
|
-
|
866
|
+
return new Promise(((resolve, reject) => {
|
867
|
+
this.directUpload.create(((error, attributes) => this.directUploadDidComplete(error, attributes, resolve, reject)));
|
868
|
+
this.dispatch("start");
|
869
|
+
}));
|
866
870
|
}
|
867
871
|
directUploadWillStoreFileWithXHR(xhr) {
|
868
872
|
xhr.upload.addEventListener("progress", (event => {
|
869
873
|
const progress = event.loaded / event.total * 90;
|
870
|
-
this.attachment.setUploadProgress(progress);
|
871
874
|
if (progress) {
|
872
875
|
this.dispatch("progress", {
|
873
876
|
progress: progress
|
@@ -886,7 +889,6 @@
|
|
886
889
|
const estimatedResponseTime = this.estimateResponseTime();
|
887
890
|
const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
|
888
891
|
progress = 90 + responseProgress * 9;
|
889
|
-
this.attachment.setUploadProgress(progress);
|
890
892
|
this.dispatch("progress", {
|
891
893
|
progress: progress
|
892
894
|
});
|
@@ -895,7 +897,6 @@
|
|
895
897
|
}
|
896
898
|
};
|
897
899
|
xhr.addEventListener("loadend", (() => {
|
898
|
-
this.attachment.setUploadProgress(100);
|
899
900
|
this.dispatch("progress", {
|
900
901
|
progress: 100
|
901
902
|
});
|
@@ -903,7 +904,7 @@
|
|
903
904
|
requestAnimationFrame(updateProgress);
|
904
905
|
}
|
905
906
|
estimateResponseTime() {
|
906
|
-
const fileSize = this.
|
907
|
+
const fileSize = this.file.size;
|
907
908
|
const MB = 1024 * 1024;
|
908
909
|
if (fileSize < MB) {
|
909
910
|
return 1e3;
|
@@ -913,11 +914,11 @@
|
|
913
914
|
return 3e3 + fileSize / MB * 50;
|
914
915
|
}
|
915
916
|
}
|
916
|
-
directUploadDidComplete(error, attributes) {
|
917
|
+
directUploadDidComplete(error, attributes, resolve, reject) {
|
917
918
|
if (error) {
|
918
|
-
this.dispatchError(error);
|
919
|
+
this.dispatchError(error, reject);
|
919
920
|
} else {
|
920
|
-
|
921
|
+
resolve({
|
921
922
|
sgid: attributes.attachable_sgid,
|
922
923
|
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
923
924
|
});
|
@@ -933,12 +934,12 @@
|
|
933
934
|
detail: detail
|
934
935
|
});
|
935
936
|
}
|
936
|
-
dispatchError(error) {
|
937
|
+
dispatchError(error, reject) {
|
937
938
|
const event = this.dispatch("error", {
|
938
939
|
error: error
|
939
940
|
});
|
940
941
|
if (!event.defaultPrevented) {
|
941
|
-
|
942
|
+
reject(error);
|
942
943
|
}
|
943
944
|
}
|
944
945
|
get directUploadUrl() {
|
@@ -951,8 +952,14 @@
|
|
951
952
|
addEventListener("trix-attachment-add", (event => {
|
952
953
|
const {attachment: attachment, target: target} = event;
|
953
954
|
if (attachment.file) {
|
954
|
-
const upload = new AttachmentUpload(attachment, target);
|
955
|
-
|
955
|
+
const upload = new AttachmentUpload(attachment, target, attachment.file);
|
956
|
+
const onProgress = event => attachment.setUploadProgress(event.detail.progress);
|
957
|
+
target.addEventListener("direct-upload:progress", onProgress);
|
958
|
+
upload.start().then((attributes => attachment.setAttributes(attributes))).catch((error => alert(error))).finally((() => target.removeEventListener("direct-upload:progress", onProgress)));
|
956
959
|
}
|
957
960
|
}));
|
961
|
+
exports.AttachmentUpload = AttachmentUpload;
|
962
|
+
Object.defineProperty(exports, "__esModule", {
|
963
|
+
value: true
|
964
|
+
});
|
958
965
|
}));
|
@@ -27,7 +27,14 @@ module ActionText
|
|
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
|
-
|
30
|
+
#
|
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?
|
31
38
|
options = options.symbolize_keys
|
32
39
|
form = options.delete(:form)
|
33
40
|
|
@@ -53,11 +60,11 @@ module ActionView::Helpers
|
|
53
60
|
|
54
61
|
delegate :dom_id, to: ActionView::RecordIdentifier
|
55
62
|
|
56
|
-
def render
|
63
|
+
def render(&block)
|
57
64
|
options = @options.stringify_keys
|
58
|
-
|
65
|
+
add_default_name_and_field(options)
|
59
66
|
options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) if object
|
60
|
-
html_tag = @template_object.rich_textarea_tag(options.delete("name"), options.fetch("value") { value }, options.except("value"))
|
67
|
+
html_tag = @template_object.rich_textarea_tag(options.delete("name"), options.fetch("value") { value }, options.except("value"), &block)
|
61
68
|
error_wrapping(html_tag)
|
62
69
|
end
|
63
70
|
end
|
@@ -82,10 +89,16 @@ module ActionView::Helpers
|
|
82
89
|
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
|
83
90
|
#
|
84
91
|
# rich_textarea :message, :content, value: "<h1>Default message</h1>"
|
85
|
-
# # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1" value="
|
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>
|
94
|
+
#
|
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>">
|
86
99
|
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
|
87
|
-
def rich_textarea(object_name, method, options = {})
|
88
|
-
Tags::ActionText.new(object_name, method, self, options).render
|
100
|
+
def rich_textarea(object_name, method, options = {}, &block)
|
101
|
+
Tags::ActionText.new(object_name, method, self, options).render(&block)
|
89
102
|
end
|
90
103
|
alias_method :rich_text_area, :rich_textarea
|
91
104
|
end
|
@@ -98,8 +111,8 @@ module ActionView::Helpers
|
|
98
111
|
# <% end %>
|
99
112
|
#
|
100
113
|
# Please refer to the documentation of the base helper for details.
|
101
|
-
def rich_textarea(method, options = {})
|
102
|
-
@template.rich_textarea(@object_name, method, objectify_options(options))
|
114
|
+
def rich_textarea(method, options = {}, &block)
|
115
|
+
@template.rich_textarea(@object_name, method, objectify_options(options), &block)
|
103
116
|
end
|
104
117
|
alias_method :rich_text_area, :rich_textarea
|
105
118
|
end
|
@@ -1,22 +1,24 @@
|
|
1
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
|
-
|
12
|
+
return new Promise((resolve, reject) => {
|
13
|
+
this.directUpload.create((error, attributes) => this.directUploadDidComplete(error, attributes, resolve, reject))
|
14
|
+
this.dispatch("start")
|
15
|
+
})
|
13
16
|
}
|
14
17
|
|
15
18
|
directUploadWillStoreFileWithXHR(xhr) {
|
16
19
|
xhr.upload.addEventListener("progress", event => {
|
17
20
|
// Scale upload progress to 0-90% range
|
18
21
|
const progress = (event.loaded / event.total) * 90
|
19
|
-
this.attachment.setUploadProgress(progress)
|
20
22
|
if (progress) {
|
21
23
|
this.dispatch("progress", { progress: progress })
|
22
24
|
}
|
@@ -39,7 +41,6 @@ export class AttachmentUpload {
|
|
39
41
|
const responseProgress = Math.min(elapsed / estimatedResponseTime, 1)
|
40
42
|
progress = 90 + (responseProgress * 9) // 90% to 99%
|
41
43
|
|
42
|
-
this.attachment.setUploadProgress(progress)
|
43
44
|
this.dispatch("progress", { progress })
|
44
45
|
|
45
46
|
// Continue until response arrives or we hit 99%
|
@@ -50,7 +51,6 @@ export class AttachmentUpload {
|
|
50
51
|
|
51
52
|
// Stop simulation when response arrives
|
52
53
|
xhr.addEventListener("loadend", () => {
|
53
|
-
this.attachment.setUploadProgress(100)
|
54
54
|
this.dispatch("progress", { progress: 100 })
|
55
55
|
})
|
56
56
|
|
@@ -59,7 +59,7 @@ export class AttachmentUpload {
|
|
59
59
|
|
60
60
|
estimateResponseTime() {
|
61
61
|
// Base estimate: 1 second for small files, scaling up for larger files
|
62
|
-
const fileSize = this.
|
62
|
+
const fileSize = this.file.size
|
63
63
|
const MB = 1024 * 1024
|
64
64
|
|
65
65
|
if (fileSize < MB) {
|
@@ -71,11 +71,11 @@ export class AttachmentUpload {
|
|
71
71
|
}
|
72
72
|
}
|
73
73
|
|
74
|
-
directUploadDidComplete(error, attributes) {
|
74
|
+
directUploadDidComplete(error, attributes, resolve, reject) {
|
75
75
|
if (error) {
|
76
|
-
this.dispatchError(error)
|
76
|
+
this.dispatchError(error, reject)
|
77
77
|
} else {
|
78
|
-
|
78
|
+
resolve({
|
79
79
|
sgid: attributes.attachable_sgid,
|
80
80
|
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
81
81
|
})
|
@@ -94,10 +94,10 @@ export class AttachmentUpload {
|
|
94
94
|
return dispatchEvent(this.element, `direct-upload:${name}`, { detail })
|
95
95
|
}
|
96
96
|
|
97
|
-
dispatchError(error) {
|
97
|
+
dispatchError(error, reject) {
|
98
98
|
const event = this.dispatch("error", { error })
|
99
99
|
if (!event.defaultPrevented) {
|
100
|
-
|
100
|
+
reject(error)
|
101
101
|
}
|
102
102
|
}
|
103
103
|
|
@@ -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 }
|
@@ -7,70 +7,70 @@ module ActionText
|
|
7
7
|
extend self
|
8
8
|
|
9
9
|
def node_to_plain_text(node)
|
10
|
-
|
10
|
+
BottomUpReducer.new(node).reduce do |n, child_values|
|
11
|
+
plain_text_for_node(n, child_values)
|
12
|
+
end.then(&method(:remove_trailing_newlines))
|
11
13
|
end
|
12
14
|
|
13
15
|
private
|
14
|
-
def plain_text_for_node(node,
|
16
|
+
def plain_text_for_node(node, child_values)
|
15
17
|
if respond_to?(plain_text_method_for_node(node), true)
|
16
|
-
send(plain_text_method_for_node(node), node,
|
18
|
+
send(plain_text_method_for_node(node), node, child_values)
|
17
19
|
else
|
18
|
-
|
20
|
+
plain_text_for_child_values(child_values)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
next if skippable?(child)
|
24
|
+
def plain_text_method_for_node(node)
|
25
|
+
:"plain_text_for_#{node.name}_node"
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
texts.join
|
28
|
+
def plain_text_for_child_values(child_values)
|
29
|
+
child_values.join
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
|
32
|
+
def plain_text_for_unsupported_node(node, _child_values)
|
33
|
+
""
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
:"plain_text_for_#{
|
36
|
+
%i[ script style].each do |element|
|
37
|
+
alias_method :"plain_text_for_#{element}_node", :plain_text_for_unsupported_node
|
38
38
|
end
|
39
39
|
|
40
|
-
def plain_text_for_block(node,
|
41
|
-
"#{remove_trailing_newlines(
|
40
|
+
def plain_text_for_block(node, child_values)
|
41
|
+
"#{remove_trailing_newlines(plain_text_for_child_values(child_values))}\n\n"
|
42
42
|
end
|
43
43
|
|
44
44
|
%i[ h1 p ].each do |element|
|
45
45
|
alias_method :"plain_text_for_#{element}_node", :plain_text_for_block
|
46
46
|
end
|
47
47
|
|
48
|
-
def plain_text_for_list(node,
|
49
|
-
"#{break_if_nested_list(node, plain_text_for_block(node))}"
|
48
|
+
def plain_text_for_list(node, child_values)
|
49
|
+
"#{break_if_nested_list(node, plain_text_for_block(node, child_values))}"
|
50
50
|
end
|
51
51
|
|
52
52
|
%i[ ul ol ].each do |element|
|
53
53
|
alias_method :"plain_text_for_#{element}_node", :plain_text_for_list
|
54
54
|
end
|
55
55
|
|
56
|
-
def plain_text_for_br_node(node,
|
56
|
+
def plain_text_for_br_node(node, _child_values)
|
57
57
|
"\n"
|
58
58
|
end
|
59
59
|
|
60
|
-
def plain_text_for_text_node(node,
|
60
|
+
def plain_text_for_text_node(node, _child_values)
|
61
61
|
remove_trailing_newlines(node.text)
|
62
62
|
end
|
63
63
|
|
64
|
-
def plain_text_for_div_node(node,
|
65
|
-
"#{remove_trailing_newlines(
|
64
|
+
def plain_text_for_div_node(node, child_values)
|
65
|
+
"#{remove_trailing_newlines(plain_text_for_child_values(child_values))}\n"
|
66
66
|
end
|
67
67
|
|
68
|
-
def plain_text_for_figcaption_node(node,
|
69
|
-
"[#{remove_trailing_newlines(
|
68
|
+
def plain_text_for_figcaption_node(node, child_values)
|
69
|
+
"[#{remove_trailing_newlines(plain_text_for_child_values(child_values))}]"
|
70
70
|
end
|
71
71
|
|
72
|
-
def plain_text_for_blockquote_node(node,
|
73
|
-
text = plain_text_for_block(node)
|
72
|
+
def plain_text_for_blockquote_node(node, child_values)
|
73
|
+
text = plain_text_for_block(node, child_values)
|
74
74
|
return "“”" if text.blank?
|
75
75
|
|
76
76
|
text = text.dup
|
@@ -79,9 +79,9 @@ module ActionText
|
|
79
79
|
text
|
80
80
|
end
|
81
81
|
|
82
|
-
def plain_text_for_li_node(node,
|
83
|
-
bullet = bullet_for_li_node(node
|
84
|
-
text = remove_trailing_newlines(
|
82
|
+
def plain_text_for_li_node(node, child_values)
|
83
|
+
bullet = bullet_for_li_node(node)
|
84
|
+
text = remove_trailing_newlines(plain_text_for_child_values(child_values))
|
85
85
|
indentation = indentation_for_li_node(node)
|
86
86
|
|
87
87
|
"#{indentation}#{bullet} #{text}\n"
|
@@ -91,8 +91,9 @@ module ActionText
|
|
91
91
|
text.chomp("")
|
92
92
|
end
|
93
93
|
|
94
|
-
def bullet_for_li_node(node
|
94
|
+
def bullet_for_li_node(node)
|
95
95
|
if list_node_name_for_li_node(node) == "ol"
|
96
|
+
index = node.parent.elements.index(node)
|
96
97
|
"#{index + 1}."
|
97
98
|
else
|
98
99
|
"•"
|
@@ -121,5 +122,33 @@ module ActionText
|
|
121
122
|
text
|
122
123
|
end
|
123
124
|
end
|
125
|
+
|
126
|
+
class BottomUpReducer # :nodoc:
|
127
|
+
def initialize(node)
|
128
|
+
@node = node
|
129
|
+
@values = {}
|
130
|
+
end
|
131
|
+
|
132
|
+
def reduce(&block)
|
133
|
+
traverse_bottom_up(@node) do |n|
|
134
|
+
child_values = @values.values_at(*n.children)
|
135
|
+
@values[n] = block.call(n, child_values)
|
136
|
+
end
|
137
|
+
@values[@node]
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
def traverse_bottom_up(node, &block)
|
142
|
+
call_stack, processing_stack = [ node ], []
|
143
|
+
|
144
|
+
until call_stack.empty?
|
145
|
+
node = call_stack.pop
|
146
|
+
processing_stack.push(node)
|
147
|
+
call_stack.concat node.children
|
148
|
+
end
|
149
|
+
|
150
|
+
processing_stack.reverse_each(&block)
|
151
|
+
end
|
152
|
+
end
|
124
153
|
end
|
125
154
|
end
|
@@ -35,7 +35,13 @@ module ActionText
|
|
35
35
|
# # <trix-editor input="trix_input_1"></trix-editor>
|
36
36
|
# fill_in_rich_textarea "message[content]", with: "Hello <em>world!</em>"
|
37
37
|
def fill_in_rich_textarea(locator = nil, with:, **)
|
38
|
-
find(:rich_textarea, locator, **).execute_script(
|
38
|
+
find(:rich_textarea, locator, **).execute_script(<<~JS, with.to_s)
|
39
|
+
if ("value" in this) {
|
40
|
+
this.value = arguments[0]
|
41
|
+
} else {
|
42
|
+
this.editor.loadHTML(arguments[0])
|
43
|
+
}
|
44
|
+
JS
|
39
45
|
end
|
40
46
|
alias_method :fill_in_rich_text_area, :fill_in_rich_textarea
|
41
47
|
end
|
@@ -45,13 +51,18 @@ end
|
|
45
51
|
Capybara.add_selector rich_textarea do
|
46
52
|
label "rich-text area"
|
47
53
|
xpath do |locator|
|
54
|
+
xpath = XPath.descendant[[
|
55
|
+
XPath.attribute(:role) == "textbox",
|
56
|
+
(XPath.attribute(:contenteditable) == "") | (XPath.attribute(:contenteditable) == "true")
|
57
|
+
].reduce(:&)]
|
58
|
+
|
48
59
|
if locator.nil?
|
49
|
-
|
60
|
+
xpath
|
50
61
|
else
|
51
62
|
input_located_by_name = XPath.anywhere(:input).where(XPath.attr(:name) == locator).attr(:id)
|
52
63
|
input_located_by_label = XPath.anywhere(:label).where(XPath.string.n.is(locator)).attr(:for)
|
53
64
|
|
54
|
-
|
65
|
+
xpath.where \
|
55
66
|
XPath.attr(:id).equals(locator) |
|
56
67
|
XPath.attr(:placeholder).equals(locator) |
|
57
68
|
XPath.attr(:"aria-label").equals(locator) |
|
@@ -47,18 +47,6 @@ module ActionText
|
|
47
47
|
"app/views/layouts/action_text/contents/_content.html.erb"
|
48
48
|
end
|
49
49
|
|
50
|
-
def enable_image_processing_gem
|
51
|
-
if (gemfile_path = Pathname(destination_root).join("Gemfile")).exist?
|
52
|
-
say "Ensure image_processing gem has been enabled so image uploads will work (remember to bundle!)"
|
53
|
-
image_processing_regex = /gem ["']image_processing["']/
|
54
|
-
if File.readlines(gemfile_path).grep(image_processing_regex).any?
|
55
|
-
uncomment_lines gemfile_path, image_processing_regex
|
56
|
-
else
|
57
|
-
run "bundle add --skip-install image_processing"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
50
|
def create_migrations
|
63
51
|
rails_command "railties:install:migrations FROM=active_storage,action_text", inline: true
|
64
52
|
end
|
data/package.json
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actiontext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.1.0.
|
4
|
+
version: 8.1.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Javan Makhmali
|
@@ -17,56 +17,56 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - '='
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: 8.1.0.
|
20
|
+
version: 8.1.0.rc1
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - '='
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: 8.1.0.
|
27
|
+
version: 8.1.0.rc1
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: activerecord
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - '='
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 8.1.0.
|
34
|
+
version: 8.1.0.rc1
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - '='
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 8.1.0.
|
41
|
+
version: 8.1.0.rc1
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: activestorage
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - '='
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: 8.1.0.
|
48
|
+
version: 8.1.0.rc1
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - '='
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: 8.1.0.
|
55
|
+
version: 8.1.0.rc1
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: actionpack
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - '='
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 8.1.0.
|
62
|
+
version: 8.1.0.rc1
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - '='
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 8.1.0.
|
69
|
+
version: 8.1.0.rc1
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: nokogiri
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -174,10 +174,10 @@ licenses:
|
|
174
174
|
- MIT
|
175
175
|
metadata:
|
176
176
|
bug_tracker_uri: https://github.com/rails/rails/issues
|
177
|
-
changelog_uri: https://github.com/rails/rails/blob/v8.1.0.
|
178
|
-
documentation_uri: https://api.rubyonrails.org/v8.1.0.
|
177
|
+
changelog_uri: https://github.com/rails/rails/blob/v8.1.0.rc1/actiontext/CHANGELOG.md
|
178
|
+
documentation_uri: https://api.rubyonrails.org/v8.1.0.rc1/
|
179
179
|
mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
|
180
|
-
source_code_uri: https://github.com/rails/rails/tree/v8.1.0.
|
180
|
+
source_code_uri: https://github.com/rails/rails/tree/v8.1.0.rc1/actiontext
|
181
181
|
rubygems_mfa_required: 'true'
|
182
182
|
rdoc_options: []
|
183
183
|
require_paths:
|