@richscripts/richtexteditor 2.11.1 → 2.12.0
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.
- package/package.json +1 -1
- package/richtexteditor/all_plugins.js +158 -47
- package/richtexteditor/all_plugins.min.js +55 -55
- package/richtexteditor/plugins/aitoolkit.js +27 -4
- package/richtexteditor/plugins/all_plugins.js +158 -47
- package/richtexteditor/plugins/all_plugins.min.js +55 -55
- package/richtexteditor/plugins/insertgallery.js +108 -42
- package/richtexteditor/plugins/sanitizer.js +23 -1
- package/richtexteditor/rte-config.js +6 -0
- package/richtexteditor/rte.js +8 -2
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
//
|
|
15
15
|
// config.galleryEndpoint URL returning { currentFolder, currentFolderDisplay,
|
|
16
16
|
// parentFolder, folders: [{folder,name}],
|
|
17
|
-
// images: [{url,name,folder,size,source}] }.
|
|
17
|
+
// images: [{url,name,folder,size,source,alt}] }.
|
|
18
18
|
// Absent -> local mode: presets only, no request.
|
|
19
19
|
// config.galleryImages Preset images. String, [url, text], or an object
|
|
20
|
-
// with url/src/href plus optional thumbnail/name/meta.
|
|
20
|
+
// with url/src/href plus optional thumbnail/name/meta/alt.
|
|
21
|
+
// alt (presets and server images) becomes the inserted
|
|
22
|
+
// image's alt text; without it the attribute stays absent.
|
|
21
23
|
//
|
|
22
24
|
// Uploads use window.richTextBoxUploadFile when present (it accepts a target
|
|
23
25
|
// folder), otherwise the generic window.rte_file_upload_handler.
|
|
@@ -148,7 +150,8 @@ function RTE_Plugin_InsertGallery() {
|
|
|
148
150
|
url: url,
|
|
149
151
|
thumbnail: item.thumbnail || item.thumb || item.preview || url,
|
|
150
152
|
name: item.name || item.text || item.title || getFileName(url),
|
|
151
|
-
meta: item.meta || item.description || item.alt || ""
|
|
153
|
+
meta: item.meta || item.description || item.alt || "",
|
|
154
|
+
alt: typeof item.alt === "string" ? item.alt : undefined
|
|
152
155
|
};
|
|
153
156
|
}
|
|
154
157
|
|
|
@@ -188,6 +191,7 @@ function RTE_Plugin_InsertGallery() {
|
|
|
188
191
|
folder: item.folder || "",
|
|
189
192
|
source: item.source || "upload",
|
|
190
193
|
size: item.size || 0,
|
|
194
|
+
alt: typeof item.alt === "string" ? item.alt : undefined,
|
|
191
195
|
searchText: (name + " " + item.url).toLowerCase()
|
|
192
196
|
};
|
|
193
197
|
}
|
|
@@ -381,7 +385,9 @@ function RTE_Plugin_InsertGallery() {
|
|
|
381
385
|
return;
|
|
382
386
|
}
|
|
383
387
|
|
|
384
|
-
|
|
388
|
+
// Only an alt the host supplied (preset or server listing). The file name is
|
|
389
|
+
// not alt text; leaving it absent lets the accessibility checker flag it.
|
|
390
|
+
editor.insertImageByUrl(selected.url, selected.alt);
|
|
385
391
|
closeDialog();
|
|
386
392
|
editor.focus();
|
|
387
393
|
}
|
|
@@ -612,6 +618,10 @@ function RTE_Plugin_InsertGallery() {
|
|
|
612
618
|
render();
|
|
613
619
|
}
|
|
614
620
|
|
|
621
|
+
// Upload failures to show once the folder has been re-listed (applyResponse
|
|
622
|
+
// clears the error, so it would otherwise vanish).
|
|
623
|
+
var pendingError = "";
|
|
624
|
+
|
|
615
625
|
function loadFolder(folder, selectAfterLoad) {
|
|
616
626
|
if (!serverMode) {
|
|
617
627
|
state.images = presetImages.slice(0);
|
|
@@ -625,6 +635,7 @@ function RTE_Plugin_InsertGallery() {
|
|
|
625
635
|
|
|
626
636
|
requestJson("GET", withFolder(endpoint, folder), null, function (payload, errorCode) {
|
|
627
637
|
if (errorCode) {
|
|
638
|
+
pendingError = "";
|
|
628
639
|
loadFallback(errorCode);
|
|
629
640
|
return;
|
|
630
641
|
}
|
|
@@ -633,6 +644,8 @@ function RTE_Plugin_InsertGallery() {
|
|
|
633
644
|
if (selectAfterLoad) {
|
|
634
645
|
state.selectedUrl = selectAfterLoad;
|
|
635
646
|
}
|
|
647
|
+
state.error = pendingError;
|
|
648
|
+
pendingError = "";
|
|
636
649
|
state.loading = false;
|
|
637
650
|
render();
|
|
638
651
|
});
|
|
@@ -667,67 +680,120 @@ function RTE_Plugin_InsertGallery() {
|
|
|
667
680
|
});
|
|
668
681
|
}
|
|
669
682
|
|
|
683
|
+
// config.maxUploadFileSize is enforced by the core only on its own insert
|
|
684
|
+
// paths. The gallery calls the upload handler directly, so the limit a
|
|
685
|
+
// customer set to protect their database was skipped for every image
|
|
686
|
+
// uploaded through this dialog. The accept list is only a hint to the file
|
|
687
|
+
// picker ("All files" bypasses it), so the format is checked here too.
|
|
688
|
+
function rejectReason(file) {
|
|
689
|
+
var name = String(file.name || "").toLowerCase();
|
|
690
|
+
var dot = name.lastIndexOf(".");
|
|
691
|
+
if (dot < 0 || GALLERY_ACCEPT.indexOf(name.substring(dot)) < 0) {
|
|
692
|
+
return file.name + " is not an accepted image format.";
|
|
693
|
+
}
|
|
694
|
+
var max = parseInt(config.maxUploadFileSize, 10);
|
|
695
|
+
if (max > 0 && file.size > max) {
|
|
696
|
+
return file.name + " is " + humanSize(file.size) + ". The maximum allowed size is " + humanSize(max) + ".";
|
|
697
|
+
}
|
|
698
|
+
return "";
|
|
699
|
+
}
|
|
700
|
+
|
|
670
701
|
function uploadFiles(fileList) {
|
|
671
702
|
var files = [];
|
|
703
|
+
var problems = [];
|
|
672
704
|
var index;
|
|
673
705
|
for (index = 0; index < fileList.length; index++) {
|
|
674
|
-
|
|
706
|
+
var reason = rejectReason(fileList[index]);
|
|
707
|
+
if (reason) {
|
|
708
|
+
problems.push(reason);
|
|
709
|
+
} else {
|
|
710
|
+
files.push(fileList[index]);
|
|
711
|
+
}
|
|
675
712
|
}
|
|
676
713
|
|
|
677
|
-
if (!
|
|
714
|
+
if (!canUpload) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (!files.length) {
|
|
718
|
+
if (problems.length) {
|
|
719
|
+
state.error = problems.join(" ");
|
|
720
|
+
render();
|
|
721
|
+
}
|
|
678
722
|
return;
|
|
679
723
|
}
|
|
680
724
|
|
|
681
|
-
|
|
725
|
+
// One failed file used to stop the batch, and in local mode only the
|
|
726
|
+
// LAST success was added to the list - uploading three images showed
|
|
727
|
+
// one. Now every file is attempted, every success is kept, and the
|
|
728
|
+
// failures are reported together at the end.
|
|
729
|
+
var uploadedUrls = [];
|
|
682
730
|
|
|
683
|
-
function
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
731
|
+
function finish() {
|
|
732
|
+
var lastUploadedUrl = uploadedUrls.length ? uploadedUrls[uploadedUrls.length - 1] : "";
|
|
733
|
+
|
|
734
|
+
if (serverMode) {
|
|
735
|
+
pendingError = problems.join(" ");
|
|
736
|
+
loadFolder(state.currentFolder, lastUploadedUrl || state.selectedUrl);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
689
739
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
state.selectedUrl = lastUploadedUrl;
|
|
740
|
+
// No server to re-list from, so fold the uploads into the preset
|
|
741
|
+
// list directly, newest first, and select the last one.
|
|
742
|
+
for (var u = 0; u < uploadedUrls.length; u++) {
|
|
743
|
+
var uploaded = normalizePreset(uploadedUrls[u]);
|
|
744
|
+
if (uploaded) {
|
|
745
|
+
uploaded.source = "upload";
|
|
746
|
+
presetImages.unshift(uploaded);
|
|
747
|
+
config.galleryImages.unshift(uploadedUrls[u]);
|
|
700
748
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
749
|
+
}
|
|
750
|
+
if (lastUploadedUrl) {
|
|
751
|
+
state.selectedUrl = lastUploadedUrl;
|
|
752
|
+
}
|
|
753
|
+
state.images = presetImages.slice(0);
|
|
754
|
+
state.loading = false;
|
|
755
|
+
state.error = problems.join(" ");
|
|
756
|
+
render();
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function uploadNext(nextIndex) {
|
|
760
|
+
if (nextIndex >= files.length) {
|
|
761
|
+
finish();
|
|
704
762
|
return;
|
|
705
763
|
}
|
|
706
764
|
|
|
707
765
|
var file = files[nextIndex];
|
|
766
|
+
var settled = false;
|
|
708
767
|
|
|
709
|
-
function done(url,
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
state.loading = false;
|
|
713
|
-
render();
|
|
768
|
+
function done(url, errorText) {
|
|
769
|
+
// a handler that calls back twice must not upload the rest twice
|
|
770
|
+
if (settled) {
|
|
714
771
|
return;
|
|
715
772
|
}
|
|
716
|
-
|
|
717
|
-
|
|
773
|
+
settled = true;
|
|
774
|
+
if (url) {
|
|
775
|
+
uploadedUrls.push(url);
|
|
776
|
+
} else {
|
|
777
|
+
problems.push(typeof errorText === "string" && errorText ? errorText : ("Upload failed for " + file.name + "."));
|
|
778
|
+
}
|
|
718
779
|
uploadNext(nextIndex + 1);
|
|
719
780
|
}
|
|
720
781
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
782
|
+
try {
|
|
783
|
+
if (uploadToFolder) {
|
|
784
|
+
window.richTextBoxUploadFile(file, function (url, errorCode) {
|
|
785
|
+
done(errorCode ? "" : url, errorCode ? ("Upload failed for " + file.name + ".") : "");
|
|
786
|
+
}, { folder: state.currentFolder }, nextIndex, files);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
727
789
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
790
|
+
window.rte_file_upload_handler(file, function (url, error) {
|
|
791
|
+
done(url, error);
|
|
792
|
+
}, nextIndex, files);
|
|
793
|
+
} catch (ex) {
|
|
794
|
+
// a handler that throws must not leave the dialog stuck on "Loading"
|
|
795
|
+
done("", "Upload failed for " + file.name + ".");
|
|
796
|
+
}
|
|
731
797
|
}
|
|
732
798
|
|
|
733
799
|
state.loading = true;
|
|
@@ -203,13 +203,35 @@ function RTE_Plugin_Sanitizer() {
|
|
|
203
203
|
// sanitizerAllowedIframeHosts is still the stronger control.
|
|
204
204
|
var DEFAULT_IFRAME_SANDBOX = "allow-scripts allow-same-origin allow-presentation";
|
|
205
205
|
|
|
206
|
+
// 2026-09-21 Two holes in the default sandbox, both closed here:
|
|
207
|
+
// - it was only ADDED when the iframe had none, so injected markup that
|
|
208
|
+
// brought its own sandbox="allow-top-navigation allow-popups allow-forms"
|
|
209
|
+
// kept every permission the comment above says is withheld. An existing
|
|
210
|
+
// sandbox is now narrowed to the default set, never trusted.
|
|
211
|
+
// - allow-scripts + allow-same-origin isolates nothing when the frame is
|
|
212
|
+
// served from the page's OWN origin: the framed page can reach the parent
|
|
213
|
+
// and remove its own sandbox. Same-origin frames lose allow-same-origin.
|
|
214
|
+
function applyDefaultSandbox(el, src) {
|
|
215
|
+
var allowed = DEFAULT_IFRAME_SANDBOX.split(" ");
|
|
216
|
+
var sameOrigin = false;
|
|
217
|
+
try { sameOrigin = new URL(src, location.href).origin === location.origin; } catch (e) {}
|
|
218
|
+
if (sameOrigin) allowed = allowed.filter(function (t) { return t !== "allow-same-origin"; });
|
|
219
|
+
if (el.hasAttribute("sandbox")) {
|
|
220
|
+
// keep only what the frame asked for AND the default permits; an empty
|
|
221
|
+
// sandbox (the strictest) stays empty
|
|
222
|
+
var asked = (el.getAttribute("sandbox") || "").toLowerCase().split(/\s+/);
|
|
223
|
+
allowed = allowed.filter(function (t) { return asked.indexOf(t) >= 0; });
|
|
224
|
+
}
|
|
225
|
+
el.setAttribute("sandbox", allowed.join(" "));
|
|
226
|
+
}
|
|
227
|
+
|
|
206
228
|
function iframeAllowed(el) {
|
|
207
229
|
if (config.sanitizerAllowIframes === false) return false;
|
|
208
230
|
var src = el.getAttribute("src") || "";
|
|
209
231
|
if (!/^https?:/i.test(src)) return false;
|
|
210
232
|
var hosts = config.sanitizerAllowedIframeHosts;
|
|
211
233
|
if (!hosts || !hosts.length) {
|
|
212
|
-
|
|
234
|
+
applyDefaultSandbox(el, src);
|
|
213
235
|
return true;
|
|
214
236
|
}
|
|
215
237
|
try {
|
|
@@ -68,6 +68,10 @@ RTE_DefaultConfig.inlineMath = true; // When true, editor.applyMathMarkup() wrap
|
|
|
68
68
|
RTE_DefaultConfig.imageLazyLoad = true; // Newly-inserted <img> tags get loading="lazy" + decoding="async" so big documents stay responsive.
|
|
69
69
|
RTE_DefaultConfig.documentUploadAccept = ".txt,.text,.rtf,.md,.markdown,.html,.htm,.pdf,.doc,.docx,.odt,.xls,.xlsx,.csv,.tsv,.json,.xml,.zip,.rar"; // Default Insert Document chooser filter.
|
|
70
70
|
RTE_DefaultConfig.maxUploadFileSize = 0; // Largest file the editor will insert or upload, in BYTES. 0 = no limit. Applies to the image and document dialogs, paste and drag-and-drop. Over the limit the file is refused before it is read, so nothing is embedded or sent.
|
|
71
|
+
RTE_DefaultConfig.allowBase64Images = true; // Without file_upload_handler an inserted image is embedded in the HTML as base64. false = never embed; an upload handler is then required.
|
|
72
|
+
RTE_DefaultConfig.base64ImageMaxSize = 204800; // Largest base64 image, in BYTES, after shrinking (200 KB). Larger images are refused with a message. 0 = no cap (the behaviour before core 2.6.5).
|
|
73
|
+
RTE_DefaultConfig.base64ImageShrink = true; // Resize and re-encode photos in the browser before embedding them as base64. GIF and SVG are never re-encoded.
|
|
74
|
+
RTE_DefaultConfig.base64ImageMaxDimension = 1600; // Longest side, in pixels, of an image shrunk for base64 embedding.
|
|
71
75
|
RTE_DefaultConfig.fileUploadLocalObjectUrl = true; // Without file_upload_handler, local document files insert as temporary blob: links instead of failing.
|
|
72
76
|
// Reading-mode persistence key. Default is session-only so refresh restores the toolbar.
|
|
73
77
|
// Set to true for per-editor localStorage persistence, or to a custom string to share state across editors.
|
|
@@ -936,6 +940,8 @@ RTE_DefaultConfig.text_inserttemplatetitle = "@inserttemplate";
|
|
|
936
940
|
RTE_DefaultConfig.text_uploadfailed = "The file could not be uploaded."; // Shown when the upload handler reports an error; the error code follows in brackets.
|
|
937
941
|
RTE_DefaultConfig.text_uploadnothandler = "This file cannot be attached, because file uploads are not set up on this site."; // Shown when no file_upload_handler is configured and fileUploadLocalObjectUrl is false.
|
|
938
942
|
RTE_DefaultConfig.text_uploadtoobig = "This file is {0}. The maximum allowed size is {1}."; // {0} = the file size, {1} = maxUploadFileSize. Shown when a file is refused; handle the "uploadtoobig" customdialog hook for your own dialog.
|
|
943
|
+
RTE_DefaultConfig.text_base64toobig = "This image is {0} even after resizing. Images larger than {1} need an image upload handler - see the upload documentation."; // {0} = embedded size, {1} = base64ImageMaxSize. customdialog hook: "base64toobig".
|
|
944
|
+
RTE_DefaultConfig.text_base64disabled = "Images cannot be embedded in this editor. An image upload handler must be configured (file_upload_handler)."; // Shown when allowBase64Images is false and no handler is set. customdialog hook: "base64disabled".
|
|
939
945
|
RTE_DefaultConfig.text_reachmaxlength = "The text to be added has reached the character limit for this field.";
|
|
940
946
|
|
|
941
947
|
RTE_DefaultConfig.translation = RTE_DefaultConfig.translation || {
|