actiontext 8.0.3 → 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 +44 -71
- data/app/assets/javascripts/actiontext.esm.js +92 -14
- data/app/assets/javascripts/actiontext.js +98 -17
- data/app/helpers/action_text/tag_helper.rb +21 -8
- data/app/javascript/actiontext/attachment_upload.js +60 -11
- data/app/javascript/actiontext/index.js +10 -1
- data/app/models/action_text/rich_text.rb +5 -2
- data/lib/action_text/content.rb +2 -1
- data/lib/action_text/engine.rb +2 -1
- data/lib/action_text/fixture_set.rb +1 -1
- data/lib/action_text/gem_version.rb +3 -3
- data/lib/action_text/plain_text_conversion.rb +63 -28
- data/lib/action_text/rendering.rb +0 -1
- data/lib/action_text/system_test_helper.rb +17 -4
- data/lib/generators/action_text/install/install_generator.rb +0 -12
- data/package.json +2 -2
- metadata +26 -14
- data/app/assets/javascripts/trix.js +0 -13743
- data/app/assets/stylesheets/trix.css +0 -470
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,92 +1,65 @@
|
|
1
|
-
## Rails 8.0.
|
1
|
+
## Rails 8.1.0.rc1 (October 15, 2025) ##
|
2
2
|
|
3
|
-
*
|
3
|
+
* De-couple `@rails/actiontext/attachment_upload.js` from `Trix.Attachment`
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
## Rails 8.0.2.1 (August 13, 2025) ##
|
9
|
-
|
10
|
-
* No changes.
|
11
|
-
|
12
|
-
|
13
|
-
## Rails 8.0.2 (March 12, 2025) ##
|
14
|
-
|
15
|
-
* No changes.
|
16
|
-
|
17
|
-
## Rails 8.0.2 (March 12, 2025) ##
|
18
|
-
|
19
|
-
* No changes.
|
20
|
-
|
21
|
-
|
22
|
-
## Rails 8.0.1 (December 13, 2024) ##
|
23
|
-
|
24
|
-
* No changes.
|
25
|
-
|
26
|
-
|
27
|
-
## Rails 8.0.0.1 (December 10, 2024) ##
|
28
|
-
|
29
|
-
* Update vendored trix version to 2.1.10
|
30
|
-
|
31
|
-
*John Hawthorn*
|
32
|
-
|
33
|
-
|
34
|
-
## Rails 8.0.0 (November 07, 2024) ##
|
35
|
-
|
36
|
-
* No changes.
|
37
|
-
|
38
|
-
|
39
|
-
## Rails 8.0.0.rc2 (October 30, 2024) ##
|
40
|
-
|
41
|
-
* No changes.
|
42
|
-
|
43
|
-
|
44
|
-
## Rails 8.0.0.rc1 (October 19, 2024) ##
|
45
|
-
|
46
|
-
* No changes.
|
5
|
+
Implement `@rails/actiontext/index.js` with a `direct-upload:progress` event
|
6
|
+
listeners and `Promise` resolution.
|
47
7
|
|
8
|
+
*Sean Doyle*
|
48
9
|
|
49
|
-
|
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
|
+
```
|
50
30
|
|
51
|
-
*
|
31
|
+
*Sean Doyle*
|
52
32
|
|
53
|
-
|
54
|
-
editor. Previously, that action didn't dispatch any events, which made it hard
|
55
|
-
to react to the file uploads. For instance, if an upload failed, there was no
|
56
|
-
way to notify the user about it, or remove the attachment from the editor.
|
33
|
+
* Generalize `:rich_text_area` Capybara selector
|
57
34
|
|
58
|
-
|
59
|
-
|
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.
|
60
38
|
|
61
|
-
*
|
39
|
+
*Sean Doyle*
|
62
40
|
|
63
|
-
|
41
|
+
## Rails 8.1.0.beta1 (September 04, 2025) ##
|
64
42
|
|
65
|
-
|
43
|
+
* Forward `fill_in_rich_text_area` options to Capybara
|
66
44
|
|
67
45
|
```ruby
|
68
|
-
|
69
|
-
has_rich_text :content, store_if_blank: false
|
70
|
-
end
|
71
|
-
|
72
|
-
Message.create(content: "hi") # creates an ActionText::RichText
|
73
|
-
Message.create(content: "") # does not create an ActionText::RichText
|
46
|
+
fill_in_rich_textarea "Rich text editor", id: "trix_editor_1", with: "Hello world!"
|
74
47
|
```
|
75
48
|
|
76
|
-
*
|
77
|
-
|
78
|
-
* Strip `content` attribute if the key is present but the value is empty
|
49
|
+
*Sean Doyle*
|
79
50
|
|
80
|
-
|
51
|
+
* Attachment upload progress accounts for server processing time.
|
81
52
|
|
82
|
-
*
|
53
|
+
*Jeremy Daer*
|
83
54
|
|
84
|
-
|
55
|
+
* The Trix dependency is now satisfied by a gem, `action_text-trix`, rather than vendored
|
56
|
+
files. This allows applications to bump Trix versions independently of Rails
|
57
|
+
releases. Effectively this also upgrades Trix to `>= 2.1.15`.
|
85
58
|
|
86
|
-
*
|
59
|
+
*Mike Dalessio*
|
87
60
|
|
88
|
-
*
|
61
|
+
* Change `ActionText::RichText#embeds` assignment from `before_save` to `before_validation`
|
89
62
|
|
90
|
-
*
|
63
|
+
*Sean Doyle*
|
91
64
|
|
92
|
-
Please check [
|
65
|
+
Please check [8-0-stable](https://github.com/rails/rails/blob/8-0-stable/actiontext/CHANGELOG.md) for previous changes.
|
@@ -672,7 +672,7 @@ class DirectUploadController {
|
|
672
672
|
}));
|
673
673
|
}
|
674
674
|
uploadRequestDidProgress(event) {
|
675
|
-
const progress = event.loaded / event.total *
|
675
|
+
const progress = event.loaded / event.total * 90;
|
676
676
|
if (progress) {
|
677
677
|
this.dispatch("progress", {
|
678
678
|
progress: progress
|
@@ -707,6 +707,42 @@ class DirectUploadController {
|
|
707
707
|
xhr: xhr
|
708
708
|
});
|
709
709
|
xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event)));
|
710
|
+
xhr.upload.addEventListener("loadend", (() => {
|
711
|
+
this.simulateResponseProgress(xhr);
|
712
|
+
}));
|
713
|
+
}
|
714
|
+
simulateResponseProgress(xhr) {
|
715
|
+
let progress = 90;
|
716
|
+
const startTime = Date.now();
|
717
|
+
const updateProgress = () => {
|
718
|
+
const elapsed = Date.now() - startTime;
|
719
|
+
const estimatedResponseTime = this.estimateResponseTime();
|
720
|
+
const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
|
721
|
+
progress = 90 + responseProgress * 9;
|
722
|
+
this.dispatch("progress", {
|
723
|
+
progress: progress
|
724
|
+
});
|
725
|
+
if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
|
726
|
+
requestAnimationFrame(updateProgress);
|
727
|
+
}
|
728
|
+
};
|
729
|
+
xhr.addEventListener("loadend", (() => {
|
730
|
+
this.dispatch("progress", {
|
731
|
+
progress: 100
|
732
|
+
});
|
733
|
+
}));
|
734
|
+
requestAnimationFrame(updateProgress);
|
735
|
+
}
|
736
|
+
estimateResponseTime() {
|
737
|
+
const fileSize = this.file.size;
|
738
|
+
const MB = 1024 * 1024;
|
739
|
+
if (fileSize < MB) {
|
740
|
+
return 1e3;
|
741
|
+
} else if (fileSize < 10 * MB) {
|
742
|
+
return 2e3;
|
743
|
+
} else {
|
744
|
+
return 3e3 + fileSize / MB * 50;
|
745
|
+
}
|
710
746
|
}
|
711
747
|
}
|
712
748
|
|
@@ -846,31 +882,69 @@ function autostart() {
|
|
846
882
|
setTimeout(autostart, 1);
|
847
883
|
|
848
884
|
class AttachmentUpload {
|
849
|
-
constructor(attachment, element) {
|
885
|
+
constructor(attachment, element, file = attachment.file) {
|
850
886
|
this.attachment = attachment;
|
851
887
|
this.element = element;
|
852
|
-
this.directUpload = new DirectUpload(
|
888
|
+
this.directUpload = new DirectUpload(file, this.directUploadUrl, this);
|
889
|
+
this.file = file;
|
853
890
|
}
|
854
891
|
start() {
|
855
|
-
|
856
|
-
|
892
|
+
return new Promise(((resolve, reject) => {
|
893
|
+
this.directUpload.create(((error, attributes) => this.directUploadDidComplete(error, attributes, resolve, reject)));
|
894
|
+
this.dispatch("start");
|
895
|
+
}));
|
857
896
|
}
|
858
897
|
directUploadWillStoreFileWithXHR(xhr) {
|
859
898
|
xhr.upload.addEventListener("progress", (event => {
|
860
|
-
const progress = event.loaded / event.total *
|
861
|
-
this.attachment.setUploadProgress(progress);
|
899
|
+
const progress = event.loaded / event.total * 90;
|
862
900
|
if (progress) {
|
863
901
|
this.dispatch("progress", {
|
864
902
|
progress: progress
|
865
903
|
});
|
866
904
|
}
|
867
905
|
}));
|
906
|
+
xhr.upload.addEventListener("loadend", (() => {
|
907
|
+
this.simulateResponseProgress(xhr);
|
908
|
+
}));
|
868
909
|
}
|
869
|
-
|
910
|
+
simulateResponseProgress(xhr) {
|
911
|
+
let progress = 90;
|
912
|
+
const startTime = Date.now();
|
913
|
+
const updateProgress = () => {
|
914
|
+
const elapsed = Date.now() - startTime;
|
915
|
+
const estimatedResponseTime = this.estimateResponseTime();
|
916
|
+
const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
|
917
|
+
progress = 90 + responseProgress * 9;
|
918
|
+
this.dispatch("progress", {
|
919
|
+
progress: progress
|
920
|
+
});
|
921
|
+
if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
|
922
|
+
requestAnimationFrame(updateProgress);
|
923
|
+
}
|
924
|
+
};
|
925
|
+
xhr.addEventListener("loadend", (() => {
|
926
|
+
this.dispatch("progress", {
|
927
|
+
progress: 100
|
928
|
+
});
|
929
|
+
}));
|
930
|
+
requestAnimationFrame(updateProgress);
|
931
|
+
}
|
932
|
+
estimateResponseTime() {
|
933
|
+
const fileSize = this.file.size;
|
934
|
+
const MB = 1024 * 1024;
|
935
|
+
if (fileSize < MB) {
|
936
|
+
return 1e3;
|
937
|
+
} else if (fileSize < 10 * MB) {
|
938
|
+
return 2e3;
|
939
|
+
} else {
|
940
|
+
return 3e3 + fileSize / MB * 50;
|
941
|
+
}
|
942
|
+
}
|
943
|
+
directUploadDidComplete(error, attributes, resolve, reject) {
|
870
944
|
if (error) {
|
871
|
-
this.dispatchError(error);
|
945
|
+
this.dispatchError(error, reject);
|
872
946
|
} else {
|
873
|
-
|
947
|
+
resolve({
|
874
948
|
sgid: attributes.attachable_sgid,
|
875
949
|
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
876
950
|
});
|
@@ -886,12 +960,12 @@ class AttachmentUpload {
|
|
886
960
|
detail: detail
|
887
961
|
});
|
888
962
|
}
|
889
|
-
dispatchError(error) {
|
963
|
+
dispatchError(error, reject) {
|
890
964
|
const event = this.dispatch("error", {
|
891
965
|
error: error
|
892
966
|
});
|
893
967
|
if (!event.defaultPrevented) {
|
894
|
-
|
968
|
+
reject(error);
|
895
969
|
}
|
896
970
|
}
|
897
971
|
get directUploadUrl() {
|
@@ -905,7 +979,11 @@ class AttachmentUpload {
|
|
905
979
|
addEventListener("trix-attachment-add", (event => {
|
906
980
|
const {attachment: attachment, target: target} = event;
|
907
981
|
if (attachment.file) {
|
908
|
-
const upload = new AttachmentUpload(attachment, target);
|
909
|
-
|
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)));
|
910
986
|
}
|
911
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: {}
|
@@ -661,7 +662,7 @@
|
|
661
662
|
}));
|
662
663
|
}
|
663
664
|
uploadRequestDidProgress(event) {
|
664
|
-
const progress = event.loaded / event.total *
|
665
|
+
const progress = event.loaded / event.total * 90;
|
665
666
|
if (progress) {
|
666
667
|
this.dispatch("progress", {
|
667
668
|
progress: progress
|
@@ -696,6 +697,42 @@
|
|
696
697
|
xhr: xhr
|
697
698
|
});
|
698
699
|
xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event)));
|
700
|
+
xhr.upload.addEventListener("loadend", (() => {
|
701
|
+
this.simulateResponseProgress(xhr);
|
702
|
+
}));
|
703
|
+
}
|
704
|
+
simulateResponseProgress(xhr) {
|
705
|
+
let progress = 90;
|
706
|
+
const startTime = Date.now();
|
707
|
+
const updateProgress = () => {
|
708
|
+
const elapsed = Date.now() - startTime;
|
709
|
+
const estimatedResponseTime = this.estimateResponseTime();
|
710
|
+
const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
|
711
|
+
progress = 90 + responseProgress * 9;
|
712
|
+
this.dispatch("progress", {
|
713
|
+
progress: progress
|
714
|
+
});
|
715
|
+
if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
|
716
|
+
requestAnimationFrame(updateProgress);
|
717
|
+
}
|
718
|
+
};
|
719
|
+
xhr.addEventListener("loadend", (() => {
|
720
|
+
this.dispatch("progress", {
|
721
|
+
progress: 100
|
722
|
+
});
|
723
|
+
}));
|
724
|
+
requestAnimationFrame(updateProgress);
|
725
|
+
}
|
726
|
+
estimateResponseTime() {
|
727
|
+
const fileSize = this.file.size;
|
728
|
+
const MB = 1024 * 1024;
|
729
|
+
if (fileSize < MB) {
|
730
|
+
return 1e3;
|
731
|
+
} else if (fileSize < 10 * MB) {
|
732
|
+
return 2e3;
|
733
|
+
} else {
|
734
|
+
return 3e3 + fileSize / MB * 50;
|
735
|
+
}
|
699
736
|
}
|
700
737
|
}
|
701
738
|
const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])";
|
@@ -819,31 +856,69 @@
|
|
819
856
|
}
|
820
857
|
setTimeout(autostart, 1);
|
821
858
|
class AttachmentUpload {
|
822
|
-
constructor(attachment, element) {
|
859
|
+
constructor(attachment, element, file = attachment.file) {
|
823
860
|
this.attachment = attachment;
|
824
861
|
this.element = element;
|
825
|
-
this.directUpload = new DirectUpload(
|
862
|
+
this.directUpload = new DirectUpload(file, this.directUploadUrl, this);
|
863
|
+
this.file = file;
|
826
864
|
}
|
827
865
|
start() {
|
828
|
-
|
829
|
-
|
866
|
+
return new Promise(((resolve, reject) => {
|
867
|
+
this.directUpload.create(((error, attributes) => this.directUploadDidComplete(error, attributes, resolve, reject)));
|
868
|
+
this.dispatch("start");
|
869
|
+
}));
|
830
870
|
}
|
831
871
|
directUploadWillStoreFileWithXHR(xhr) {
|
832
872
|
xhr.upload.addEventListener("progress", (event => {
|
833
|
-
const progress = event.loaded / event.total *
|
834
|
-
this.attachment.setUploadProgress(progress);
|
873
|
+
const progress = event.loaded / event.total * 90;
|
835
874
|
if (progress) {
|
836
875
|
this.dispatch("progress", {
|
837
876
|
progress: progress
|
838
877
|
});
|
839
878
|
}
|
840
879
|
}));
|
880
|
+
xhr.upload.addEventListener("loadend", (() => {
|
881
|
+
this.simulateResponseProgress(xhr);
|
882
|
+
}));
|
883
|
+
}
|
884
|
+
simulateResponseProgress(xhr) {
|
885
|
+
let progress = 90;
|
886
|
+
const startTime = Date.now();
|
887
|
+
const updateProgress = () => {
|
888
|
+
const elapsed = Date.now() - startTime;
|
889
|
+
const estimatedResponseTime = this.estimateResponseTime();
|
890
|
+
const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
|
891
|
+
progress = 90 + responseProgress * 9;
|
892
|
+
this.dispatch("progress", {
|
893
|
+
progress: progress
|
894
|
+
});
|
895
|
+
if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
|
896
|
+
requestAnimationFrame(updateProgress);
|
897
|
+
}
|
898
|
+
};
|
899
|
+
xhr.addEventListener("loadend", (() => {
|
900
|
+
this.dispatch("progress", {
|
901
|
+
progress: 100
|
902
|
+
});
|
903
|
+
}));
|
904
|
+
requestAnimationFrame(updateProgress);
|
905
|
+
}
|
906
|
+
estimateResponseTime() {
|
907
|
+
const fileSize = this.file.size;
|
908
|
+
const MB = 1024 * 1024;
|
909
|
+
if (fileSize < MB) {
|
910
|
+
return 1e3;
|
911
|
+
} else if (fileSize < 10 * MB) {
|
912
|
+
return 2e3;
|
913
|
+
} else {
|
914
|
+
return 3e3 + fileSize / MB * 50;
|
915
|
+
}
|
841
916
|
}
|
842
|
-
directUploadDidComplete(error, attributes) {
|
917
|
+
directUploadDidComplete(error, attributes, resolve, reject) {
|
843
918
|
if (error) {
|
844
|
-
this.dispatchError(error);
|
919
|
+
this.dispatchError(error, reject);
|
845
920
|
} else {
|
846
|
-
|
921
|
+
resolve({
|
847
922
|
sgid: attributes.attachable_sgid,
|
848
923
|
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
849
924
|
});
|
@@ -859,12 +934,12 @@
|
|
859
934
|
detail: detail
|
860
935
|
});
|
861
936
|
}
|
862
|
-
dispatchError(error) {
|
937
|
+
dispatchError(error, reject) {
|
863
938
|
const event = this.dispatch("error", {
|
864
939
|
error: error
|
865
940
|
});
|
866
941
|
if (!event.defaultPrevented) {
|
867
|
-
|
942
|
+
reject(error);
|
868
943
|
}
|
869
944
|
}
|
870
945
|
get directUploadUrl() {
|
@@ -877,8 +952,14 @@
|
|
877
952
|
addEventListener("trix-attachment-add", (event => {
|
878
953
|
const {attachment: attachment, target: target} = event;
|
879
954
|
if (attachment.file) {
|
880
|
-
const upload = new AttachmentUpload(attachment, target);
|
881
|
-
|
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)));
|
882
959
|
}
|
883
960
|
}));
|
961
|
+
exports.AttachmentUpload = AttachmentUpload;
|
962
|
+
Object.defineProperty(exports, "__esModule", {
|
963
|
+
value: true
|
964
|
+
});
|
884
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,32 +1,81 @@
|
|
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
|
-
|
18
|
-
|
20
|
+
// Scale upload progress to 0-90% range
|
21
|
+
const progress = (event.loaded / event.total) * 90
|
19
22
|
if (progress) {
|
20
23
|
this.dispatch("progress", { progress: progress })
|
21
24
|
}
|
22
25
|
})
|
26
|
+
|
27
|
+
// Start simulating progress after upload completes
|
28
|
+
xhr.upload.addEventListener("loadend", () => {
|
29
|
+
this.simulateResponseProgress(xhr)
|
30
|
+
})
|
31
|
+
}
|
32
|
+
|
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
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
// Stop simulation when response arrives
|
53
|
+
xhr.addEventListener("loadend", () => {
|
54
|
+
this.dispatch("progress", { progress: 100 })
|
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
|
+
}
|
23
72
|
}
|
24
73
|
|
25
|
-
directUploadDidComplete(error, attributes) {
|
74
|
+
directUploadDidComplete(error, attributes, resolve, reject) {
|
26
75
|
if (error) {
|
27
|
-
this.dispatchError(error)
|
76
|
+
this.dispatchError(error, reject)
|
28
77
|
} else {
|
29
|
-
|
78
|
+
resolve({
|
30
79
|
sgid: attributes.attachable_sgid,
|
31
80
|
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
|
32
81
|
})
|
@@ -45,10 +94,10 @@ export class AttachmentUpload {
|
|
45
94
|
return dispatchEvent(this.element, `direct-upload:${name}`, { detail })
|
46
95
|
}
|
47
96
|
|
48
|
-
dispatchError(error) {
|
97
|
+
dispatchError(error, reject) {
|
49
98
|
const event = this.dispatch("error", { error })
|
50
99
|
if (!event.defaultPrevented) {
|
51
|
-
|
100
|
+
reject(error)
|
52
101
|
}
|
53
102
|
}
|
54
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 }
|