@richscripts/richtexteditor 2.12.1 → 2.14.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 +211 -6
- package/richtexteditor/all_plugins.min.js +47 -47
- package/richtexteditor/plugins/a11yenhance.min.js +1 -0
- package/richtexteditor/plugins/accessibilitychecker.js +168 -0
- package/richtexteditor/plugins/accessibilitychecker.min.js +1 -0
- package/richtexteditor/plugins/aitoolkit.min.js +14 -0
- package/richtexteditor/plugins/all_plugins.js +211 -6
- package/richtexteditor/plugins/all_plugins.min.js +47 -47
- package/richtexteditor/plugins/autocorrect.min.js +1 -0
- package/richtexteditor/plugins/autoembed.min.js +1 -0
- package/richtexteditor/plugins/blocktypes.min.js +2 -0
- package/richtexteditor/plugins/bookmarkcard.min.js +2 -0
- package/richtexteditor/plugins/changecase.min.js +1 -0
- package/richtexteditor/plugins/charlimit.min.js +2 -0
- package/richtexteditor/plugins/comments.min.js +3 -0
- package/richtexteditor/plugins/contentminimap.min.js +1 -0
- package/richtexteditor/plugins/crossreference.min.js +1 -0
- package/richtexteditor/plugins/dialoga11y.min.js +1 -0
- package/richtexteditor/plugins/dictation.min.js +1 -0
- package/richtexteditor/plugins/documentimport.min.js +4 -0
- package/richtexteditor/plugins/documentoutline.js +43 -6
- package/richtexteditor/plugins/documentoutline.min.js +1 -0
- package/richtexteditor/plugins/docxexport.min.js +2 -0
- package/richtexteditor/plugins/draghandle.min.js +1 -0
- package/richtexteditor/plugins/emailtoolkit.min.js +1 -0
- package/richtexteditor/plugins/emojiautocomplete.min.js +1 -0
- package/richtexteditor/plugins/foldheadings.min.js +1 -0
- package/richtexteditor/plugins/footnotes.min.js +1 -0
- package/richtexteditor/plugins/formatpainter.min.js +1 -0
- package/richtexteditor/plugins/formattingmarks.min.js +1 -0
- package/richtexteditor/plugins/gapcursor.min.js +1 -0
- package/richtexteditor/plugins/ghostcomplete.min.js +1 -0
- package/richtexteditor/plugins/html2pdf.min.js +1 -0
- package/richtexteditor/plugins/imageeditor.min.js +1 -0
- package/richtexteditor/plugins/importstyles.min.js +1 -0
- package/richtexteditor/plugins/inlinecode.min.js +1 -0
- package/richtexteditor/plugins/insertcode.min.js +8 -0
- package/richtexteditor/plugins/insertemoji.min.js +1 -0
- package/richtexteditor/plugins/insertgallery.min.js +1 -0
- package/richtexteditor/plugins/inserttemplate.min.js +1 -0
- package/richtexteditor/plugins/keyboarda11y.min.js +1 -0
- package/richtexteditor/plugins/layouttable.min.js +1 -0
- package/richtexteditor/plugins/linenumbers.min.js +1 -0
- package/richtexteditor/plugins/linkchecker.min.js +1 -0
- package/richtexteditor/plugins/localdraft.min.js +1 -0
- package/richtexteditor/plugins/markdownexport.min.js +2 -0
- package/richtexteditor/plugins/mathdialog.min.js +1 -0
- package/richtexteditor/plugins/mentionplugin.min.js +3 -0
- package/richtexteditor/plugins/mergefields.min.js +1 -0
- package/richtexteditor/plugins/mermaiddiagram.min.js +16 -0
- package/richtexteditor/plugins/multilevellist.min.js +1 -0
- package/richtexteditor/plugins/multiroot.min.js +1 -0
- package/richtexteditor/plugins/nodetypes.min.js +1 -0
- package/richtexteditor/plugins/pagination.min.js +1 -0
- package/richtexteditor/plugins/pdfexport.min.js +19 -0
- package/richtexteditor/plugins/permanentpen.min.js +1 -0
- package/richtexteditor/plugins/readabilitystats.min.js +1 -0
- package/richtexteditor/plugins/readaloud.min.js +1 -0
- package/richtexteditor/plugins/restrictedediting.min.js +2 -0
- package/richtexteditor/plugins/revisionhistory.min.js +3 -0
- package/richtexteditor/plugins/rtlui.min.js +2 -0
- package/richtexteditor/plugins/rubyannotation.min.js +1 -0
- package/richtexteditor/plugins/sanitizer.min.js +1 -0
- package/richtexteditor/plugins/shadowdom.min.js +1 -0
- package/richtexteditor/plugins/slashcommand.min.js +2 -0
- package/richtexteditor/plugins/smartchips.min.js +2 -0
- package/richtexteditor/plugins/spellcheck.min.js +2 -0
- package/richtexteditor/plugins/stateinspector.min.js +2 -0
- package/richtexteditor/plugins/structured-content-bridge.js +5178 -0
- package/richtexteditor/plugins/structured-content-bridge.min.js +19 -0
- package/richtexteditor/plugins/tableofcontents.min.js +1 -0
- package/richtexteditor/plugins/tabletools.min.js +1 -0
- package/richtexteditor/plugins/textdirection.min.js +1 -0
- package/richtexteditor/plugins/textpartlanguage.min.js +1 -0
- package/richtexteditor/plugins/todolist.min.js +1 -0
- package/richtexteditor/plugins/trackedchanges.min.js +3 -0
- package/richtexteditor/plugins/typewriter.min.js +1 -0
- package/richtexteditor/plugins/watermark.min.js +1 -0
- package/richtexteditor/plugins/wordexport.min.js +23 -0
- package/richtexteditor/plugins/yjscollab.min.js +2 -0
- package/richtexteditor/plugins/zz_richtextbox_dialog_style.min.js +1 -0
- package/richtexteditor/rte-config.js +2 -0
- package/richtexteditor/rte.js +4 -2
|
@@ -745,6 +745,125 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
745
745
|
}
|
|
746
746
|
}
|
|
747
747
|
|
|
748
|
+
// 2026-09-22 Six rules the commercial checkers have and this one did not
|
|
749
|
+
// (TinyMCE's paid checker: D1, D3, D4, I3, I4, T1). One pass over the whole
|
|
750
|
+
// editable, like duplicate-id, because several need to see neighbours.
|
|
751
|
+
if (editable && editable.querySelectorAll) {
|
|
752
|
+
// D1 paragraph-as-heading: a short paragraph whose whole text is bold and
|
|
753
|
+
// visibly large looks like a heading but cannot be reached by heading
|
|
754
|
+
// navigation and is missing from the outline.
|
|
755
|
+
var paras = editable.querySelectorAll("p");
|
|
756
|
+
for (var pi = 0; pi < paras.length; pi++) {
|
|
757
|
+
var pEl = paras[pi];
|
|
758
|
+
var pText = getText(pEl);
|
|
759
|
+
if (!pText || pText.length > 80 || /[.!?:;,]$/.test(pText)) continue;
|
|
760
|
+
var win = pEl.ownerDocument.defaultView;
|
|
761
|
+
var strong = pEl.querySelector("strong, b");
|
|
762
|
+
var pStyle = win.getComputedStyle(pEl);
|
|
763
|
+
var boldAll = (strong && getText(strong) === pText) || parseInt(pStyle.fontWeight, 10) >= 600;
|
|
764
|
+
if (!boldAll) continue;
|
|
765
|
+
var sizeEl = strong || pEl;
|
|
766
|
+
var bodySize = parseFloat(win.getComputedStyle(pEl.ownerDocument.body).fontSize) || 16;
|
|
767
|
+
if (parseFloat(win.getComputedStyle(sizeEl).fontSize) < bodySize * 1.2) continue;
|
|
768
|
+
issues.push({
|
|
769
|
+
code: "paragraph-as-heading",
|
|
770
|
+
severity: "warning",
|
|
771
|
+
message: "\"" + pText + "\" looks like a heading (bold, large, on its own line) but is a paragraph. Screen-reader users cannot jump to it. Use a heading level instead.",
|
|
772
|
+
path: "document.p[" + pi + "]",
|
|
773
|
+
_target: pEl
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// D4 fake-list: two or more consecutive paragraphs that start with the same
|
|
778
|
+
// kind of hand-typed marker ("1." "2." / "-" "*" "•").
|
|
779
|
+
var markerKind = function (el) {
|
|
780
|
+
if (!el || String(el.nodeName).toLowerCase() !== "p") return null;
|
|
781
|
+
var t = getText(el);
|
|
782
|
+
if (/^\d{1,3}[.)]\s+\S/.test(t)) return "ol";
|
|
783
|
+
if (/^[-*•▪●]\s+\S/.test(t)) return "ul";
|
|
784
|
+
return null;
|
|
785
|
+
};
|
|
786
|
+
var kids = editable.children;
|
|
787
|
+
for (var ki = 0; ki < kids.length; ki++) {
|
|
788
|
+
var kind = markerKind(kids[ki]);
|
|
789
|
+
if (!kind) continue;
|
|
790
|
+
var run = 1;
|
|
791
|
+
while (ki + run < kids.length && markerKind(kids[ki + run]) === kind) run++;
|
|
792
|
+
if (run >= 2) {
|
|
793
|
+
issues.push({
|
|
794
|
+
code: "fake-list",
|
|
795
|
+
severity: "warning",
|
|
796
|
+
message: run + " paragraphs are typed as a " + (kind === "ol" ? "numbered" : "bulleted") + " list. Screen readers do not announce them as a list or say how many items it has. Use the list button instead.",
|
|
797
|
+
path: "content[" + ki + "]",
|
|
798
|
+
_target: kids[ki]
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
ki += run - 1;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// I3 / I4: alt text that is a file name, or too long to be heard comfortably.
|
|
805
|
+
var imgs = editable.querySelectorAll("img[alt]");
|
|
806
|
+
for (var ii = 0; ii < imgs.length; ii++) {
|
|
807
|
+
var alt = (imgs[ii].getAttribute("alt") || "").trim();
|
|
808
|
+
if (!alt) continue; // alt="" is a deliberate decorative marker
|
|
809
|
+
var srcName = ((imgs[ii].getAttribute("src") || "").split(/[?#]/)[0].split("/").pop() || "").toLowerCase();
|
|
810
|
+
if (/\.(jpe?g|png|gif|webp|bmp|svg|avif|tiff?)$/i.test(alt) || (srcName && alt.toLowerCase() === srcName) || /^(img|image|dsc|photo|screenshot)[-_ ]?\d+/i.test(alt)) {
|
|
811
|
+
issues.push({
|
|
812
|
+
code: "image-alt-filename",
|
|
813
|
+
severity: "error",
|
|
814
|
+
message: "Alt text \"" + alt + "\" is a file name, which describes nothing. Say what the image shows.",
|
|
815
|
+
path: "document.img[" + ii + "]",
|
|
816
|
+
_target: imgs[ii]
|
|
817
|
+
});
|
|
818
|
+
} else if (alt.length > 150) {
|
|
819
|
+
issues.push({
|
|
820
|
+
code: "image-alt-too-long",
|
|
821
|
+
severity: "warning",
|
|
822
|
+
message: "Alt text is " + alt.length + " characters. Keep it to a short description; put longer detail in the text or a caption.",
|
|
823
|
+
path: "document.img[" + ii + "]",
|
|
824
|
+
_target: imgs[ii]
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// D3 link-adjacent-duplicate: two links next to each other with the same
|
|
830
|
+
// href (typically an image link and a text link) are announced twice.
|
|
831
|
+
var links = editable.querySelectorAll("a[href]");
|
|
832
|
+
for (var li = 1; li < links.length; li++) {
|
|
833
|
+
var prev = links[li - 1], cur = links[li];
|
|
834
|
+
if (prev.getAttribute("href") !== cur.getAttribute("href")) continue;
|
|
835
|
+
var between = prev.nextSibling, adjacent = true;
|
|
836
|
+
while (between && between !== cur) {
|
|
837
|
+
if (between.nodeType === 1 || (between.nodeType === 3 && /\S/.test(between.data))) { adjacent = false; break; }
|
|
838
|
+
between = between.nextSibling;
|
|
839
|
+
}
|
|
840
|
+
if (adjacent && between === cur) {
|
|
841
|
+
issues.push({
|
|
842
|
+
code: "link-adjacent-duplicate",
|
|
843
|
+
severity: "warning",
|
|
844
|
+
message: "Two links next to each other go to the same place, so it is announced twice. Combine them into one link.",
|
|
845
|
+
path: "document.link[" + li + "]",
|
|
846
|
+
_target: cur
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// T1 table-missing-caption: a DATA table (it has header cells) without a caption.
|
|
852
|
+
var tables = editable.querySelectorAll("table");
|
|
853
|
+
for (var ti = 0; ti < tables.length; ti++) {
|
|
854
|
+
var tb = tables[ti];
|
|
855
|
+
if ((tb.getAttribute("role") || "").toLowerCase() === "presentation") continue;
|
|
856
|
+
if (!tb.querySelector("th") || tb.querySelector("caption")) continue;
|
|
857
|
+
issues.push({
|
|
858
|
+
code: "table-missing-caption",
|
|
859
|
+
severity: "warning",
|
|
860
|
+
message: "Data table has no caption. A short caption lets screen-reader users know what the table is before they enter it.",
|
|
861
|
+
path: "document.table[" + ti + "]",
|
|
862
|
+
_target: tb
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
748
867
|
return { document: null, issues: issues, valid: !issues.length, source: "dom" };
|
|
749
868
|
}
|
|
750
869
|
|
|
@@ -801,6 +920,55 @@ function RTE_Plugin_AccessibilityChecker() {
|
|
|
801
920
|
markLanguageRuns(target, options && options.lang ? options.lang : issue._lang, issue._script);
|
|
802
921
|
}
|
|
803
922
|
|
|
923
|
+
else if (issue.code === "paragraph-as-heading") {
|
|
924
|
+
var lvl = Math.max(1, Math.min(6, parseInt(options && options.targetLevel, 10) || 2));
|
|
925
|
+
var h = target.ownerDocument.createElement("h" + lvl);
|
|
926
|
+
// the paragraph's whole text was bold/large: that styling is the heading now
|
|
927
|
+
h.textContent = (target.textContent || "").replace(/^\s+|\s+$/g, "");
|
|
928
|
+
copyAttributes(target, h);
|
|
929
|
+
h.removeAttribute("style");
|
|
930
|
+
target.parentNode.replaceChild(h, target);
|
|
931
|
+
}
|
|
932
|
+
else if (issue.code === "fake-list") {
|
|
933
|
+
var d = target.ownerDocument;
|
|
934
|
+
var listParent = target.parentNode;
|
|
935
|
+
var ordered = /^\d/.test((target.textContent || "").replace(/^\s+/, ""));
|
|
936
|
+
var list = d.createElement(ordered ? "ol" : "ul");
|
|
937
|
+
var node = target;
|
|
938
|
+
while (node && node.nodeName === "P") {
|
|
939
|
+
var t = (node.textContent || "").replace(/^\s+/, "");
|
|
940
|
+
var m = ordered ? /^\d{1,3}[.)]\s+([\s\S]*)$/.exec(t) : /^[-*•▪●]\s+([\s\S]*)$/.exec(t);
|
|
941
|
+
if (!m) break;
|
|
942
|
+
var li = d.createElement("li");
|
|
943
|
+
li.textContent = m[1].replace(/\s+$/, "");
|
|
944
|
+
list.appendChild(li);
|
|
945
|
+
var next = node.nextElementSibling;
|
|
946
|
+
node.parentNode.removeChild(node);
|
|
947
|
+
node = next;
|
|
948
|
+
}
|
|
949
|
+
if (node && node.parentNode === listParent) listParent.insertBefore(list, node);
|
|
950
|
+
else listParent.appendChild(list);
|
|
951
|
+
}
|
|
952
|
+
else if (issue.code === "table-missing-caption") {
|
|
953
|
+
var capText = String((options && options.captionText) || "").replace(/^\s+|\s+$/g, "");
|
|
954
|
+
if (capText) {
|
|
955
|
+
var cap = target.ownerDocument.createElement("caption");
|
|
956
|
+
cap.textContent = capText;
|
|
957
|
+
target.insertBefore(cap, target.firstChild);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
else if (issue.code === "link-adjacent-duplicate") {
|
|
961
|
+
// merge the second link into the first: one link, one announcement
|
|
962
|
+
var prevLink = target.previousSibling;
|
|
963
|
+
while (prevLink && prevLink.nodeType !== 1) prevLink = prevLink.previousSibling;
|
|
964
|
+
if (prevLink && prevLink.nodeName === "A") {
|
|
965
|
+
while (target.firstChild) prevLink.appendChild(target.firstChild);
|
|
966
|
+
target.parentNode.removeChild(target);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
else if (issue.code === "image-alt-filename" || issue.code === "image-alt-too-long") {
|
|
970
|
+
target.setAttribute("alt", String((options && options.altText) || "").replace(/^\s+|\s+$/g, ""));
|
|
971
|
+
}
|
|
804
972
|
selectedIssueIndex = 0;
|
|
805
973
|
scheduleEditorChange();
|
|
806
974
|
return runAudit();
|
|
@@ -25348,20 +25516,57 @@ function RTE_Plugin_DocumentOutline() {
|
|
|
25348
25516
|
var minLevel = Math.max(1, Math.min(6, config.documentOutlineMinLevel || 1));
|
|
25349
25517
|
var maxLevel = Math.max(minLevel, Math.min(6, config.documentOutlineMaxLevel || 6));
|
|
25350
25518
|
var headings = editable.querySelectorAll("h1,h2,h3,h4,h5,h6");
|
|
25351
|
-
var counts = {};
|
|
25352
25519
|
var items = [];
|
|
25353
25520
|
var i;
|
|
25521
|
+
// 2026-09-24 Ids must be unique in the SAVED document. This used to keep any id a
|
|
25522
|
+
// heading already had, so copying or cutting a heading and pasting it saved the same
|
|
25523
|
+
// id twice (measured on published 2.6.7: <h1 id="hello-world"> x2), and outline
|
|
25524
|
+
// clicks / anchor links went to the first one. Now the FIRST heading in document
|
|
25525
|
+
// order keeps an id and a later duplicate is renamed, so the original's links stay
|
|
25526
|
+
// valid and the pasted copy becomes "slug-2". Ids on non-heading elements are
|
|
25527
|
+
// reserved too, so a heading never takes one. Every heading is checked, including
|
|
25528
|
+
// levels the outline does not display, since they share one id namespace.
|
|
25529
|
+
var used = {};
|
|
25530
|
+
var withId = editable.querySelectorAll("[id]");
|
|
25531
|
+
for (i = 0; i < withId.length; i++) {
|
|
25532
|
+
if (!/^H[1-6]$/.test(withId[i].nodeName)) used[withId[i].id] = true;
|
|
25533
|
+
}
|
|
25534
|
+
// Headings with text claim ids first. An EMPTY heading is usually the shell a cut
|
|
25535
|
+
// leaves behind, still carrying the moved heading's id; letting it claim first would
|
|
25536
|
+
// hand the moved heading "-2" and break every link to it. So empty headings go last
|
|
25537
|
+
// and simply drop an id that a real heading already holds.
|
|
25538
|
+
var textOf = function (el) {
|
|
25539
|
+
return (el.innerText || el.textContent || "").replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
|
|
25540
|
+
};
|
|
25541
|
+
var empties = [];
|
|
25542
|
+
for (i = 0; i < headings.length; i++) {
|
|
25543
|
+
var h = headings[i];
|
|
25544
|
+
var t = textOf(h);
|
|
25545
|
+
if (!t) { empties.push(h); continue; }
|
|
25546
|
+
var base = h.id;
|
|
25547
|
+
if (!base) base = slugifyHeading(t);
|
|
25548
|
+
else if (!used[base]) { used[base] = true; continue; }
|
|
25549
|
+
var unique = base;
|
|
25550
|
+
var n = 2;
|
|
25551
|
+
while (used[unique]) { unique = base + "-" + n; n++; }
|
|
25552
|
+
used[unique] = true;
|
|
25553
|
+
if (h.id !== unique) h.id = unique;
|
|
25554
|
+
}
|
|
25555
|
+
for (i = 0; i < empties.length; i++) {
|
|
25556
|
+
if (!empties[i].id) continue;
|
|
25557
|
+
if (used[empties[i].id]) empties[i].removeAttribute("id");
|
|
25558
|
+
else used[empties[i].id] = true;
|
|
25559
|
+
}
|
|
25354
25560
|
for (i = 0; i < headings.length; i++) {
|
|
25355
25561
|
var heading = headings[i];
|
|
25356
25562
|
var level = parseInt(heading.nodeName.substring(1), 10);
|
|
25357
25563
|
if (level < minLevel || level > maxLevel) continue;
|
|
25358
25564
|
var text = (heading.innerText || heading.textContent || "").replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
|
|
25359
|
-
var baseId = heading.id || slugifyHeading(text);
|
|
25360
|
-
counts[baseId] = (counts[baseId] || 0) + 1;
|
|
25361
|
-
var resolvedId = heading.id || (counts[baseId] === 1 ? baseId : (baseId + "-" + counts[baseId]));
|
|
25362
|
-
if (!heading.id) heading.id = resolvedId;
|
|
25363
25565
|
items.push({
|
|
25364
|
-
|
|
25566
|
+
// an empty heading may now have no id; key it internally (never written to
|
|
25567
|
+
// the element) so "" can't match the "no active heading" state and light up
|
|
25568
|
+
// every empty entry at once.
|
|
25569
|
+
id: heading.id || ("__rte-outline-" + items.length),
|
|
25365
25570
|
level: level,
|
|
25366
25571
|
text: text || ("Heading " + (items.length + 1)),
|
|
25367
25572
|
element: heading
|