@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.
Files changed (84) hide show
  1. package/package.json +1 -1
  2. package/richtexteditor/all_plugins.js +211 -6
  3. package/richtexteditor/all_plugins.min.js +47 -47
  4. package/richtexteditor/plugins/a11yenhance.min.js +1 -0
  5. package/richtexteditor/plugins/accessibilitychecker.js +168 -0
  6. package/richtexteditor/plugins/accessibilitychecker.min.js +1 -0
  7. package/richtexteditor/plugins/aitoolkit.min.js +14 -0
  8. package/richtexteditor/plugins/all_plugins.js +211 -6
  9. package/richtexteditor/plugins/all_plugins.min.js +47 -47
  10. package/richtexteditor/plugins/autocorrect.min.js +1 -0
  11. package/richtexteditor/plugins/autoembed.min.js +1 -0
  12. package/richtexteditor/plugins/blocktypes.min.js +2 -0
  13. package/richtexteditor/plugins/bookmarkcard.min.js +2 -0
  14. package/richtexteditor/plugins/changecase.min.js +1 -0
  15. package/richtexteditor/plugins/charlimit.min.js +2 -0
  16. package/richtexteditor/plugins/comments.min.js +3 -0
  17. package/richtexteditor/plugins/contentminimap.min.js +1 -0
  18. package/richtexteditor/plugins/crossreference.min.js +1 -0
  19. package/richtexteditor/plugins/dialoga11y.min.js +1 -0
  20. package/richtexteditor/plugins/dictation.min.js +1 -0
  21. package/richtexteditor/plugins/documentimport.min.js +4 -0
  22. package/richtexteditor/plugins/documentoutline.js +43 -6
  23. package/richtexteditor/plugins/documentoutline.min.js +1 -0
  24. package/richtexteditor/plugins/docxexport.min.js +2 -0
  25. package/richtexteditor/plugins/draghandle.min.js +1 -0
  26. package/richtexteditor/plugins/emailtoolkit.min.js +1 -0
  27. package/richtexteditor/plugins/emojiautocomplete.min.js +1 -0
  28. package/richtexteditor/plugins/foldheadings.min.js +1 -0
  29. package/richtexteditor/plugins/footnotes.min.js +1 -0
  30. package/richtexteditor/plugins/formatpainter.min.js +1 -0
  31. package/richtexteditor/plugins/formattingmarks.min.js +1 -0
  32. package/richtexteditor/plugins/gapcursor.min.js +1 -0
  33. package/richtexteditor/plugins/ghostcomplete.min.js +1 -0
  34. package/richtexteditor/plugins/html2pdf.min.js +1 -0
  35. package/richtexteditor/plugins/imageeditor.min.js +1 -0
  36. package/richtexteditor/plugins/importstyles.min.js +1 -0
  37. package/richtexteditor/plugins/inlinecode.min.js +1 -0
  38. package/richtexteditor/plugins/insertcode.min.js +8 -0
  39. package/richtexteditor/plugins/insertemoji.min.js +1 -0
  40. package/richtexteditor/plugins/insertgallery.min.js +1 -0
  41. package/richtexteditor/plugins/inserttemplate.min.js +1 -0
  42. package/richtexteditor/plugins/keyboarda11y.min.js +1 -0
  43. package/richtexteditor/plugins/layouttable.min.js +1 -0
  44. package/richtexteditor/plugins/linenumbers.min.js +1 -0
  45. package/richtexteditor/plugins/linkchecker.min.js +1 -0
  46. package/richtexteditor/plugins/localdraft.min.js +1 -0
  47. package/richtexteditor/plugins/markdownexport.min.js +2 -0
  48. package/richtexteditor/plugins/mathdialog.min.js +1 -0
  49. package/richtexteditor/plugins/mentionplugin.min.js +3 -0
  50. package/richtexteditor/plugins/mergefields.min.js +1 -0
  51. package/richtexteditor/plugins/mermaiddiagram.min.js +16 -0
  52. package/richtexteditor/plugins/multilevellist.min.js +1 -0
  53. package/richtexteditor/plugins/multiroot.min.js +1 -0
  54. package/richtexteditor/plugins/nodetypes.min.js +1 -0
  55. package/richtexteditor/plugins/pagination.min.js +1 -0
  56. package/richtexteditor/plugins/pdfexport.min.js +19 -0
  57. package/richtexteditor/plugins/permanentpen.min.js +1 -0
  58. package/richtexteditor/plugins/readabilitystats.min.js +1 -0
  59. package/richtexteditor/plugins/readaloud.min.js +1 -0
  60. package/richtexteditor/plugins/restrictedediting.min.js +2 -0
  61. package/richtexteditor/plugins/revisionhistory.min.js +3 -0
  62. package/richtexteditor/plugins/rtlui.min.js +2 -0
  63. package/richtexteditor/plugins/rubyannotation.min.js +1 -0
  64. package/richtexteditor/plugins/sanitizer.min.js +1 -0
  65. package/richtexteditor/plugins/shadowdom.min.js +1 -0
  66. package/richtexteditor/plugins/slashcommand.min.js +2 -0
  67. package/richtexteditor/plugins/smartchips.min.js +2 -0
  68. package/richtexteditor/plugins/spellcheck.min.js +2 -0
  69. package/richtexteditor/plugins/stateinspector.min.js +2 -0
  70. package/richtexteditor/plugins/structured-content-bridge.js +5178 -0
  71. package/richtexteditor/plugins/structured-content-bridge.min.js +19 -0
  72. package/richtexteditor/plugins/tableofcontents.min.js +1 -0
  73. package/richtexteditor/plugins/tabletools.min.js +1 -0
  74. package/richtexteditor/plugins/textdirection.min.js +1 -0
  75. package/richtexteditor/plugins/textpartlanguage.min.js +1 -0
  76. package/richtexteditor/plugins/todolist.min.js +1 -0
  77. package/richtexteditor/plugins/trackedchanges.min.js +3 -0
  78. package/richtexteditor/plugins/typewriter.min.js +1 -0
  79. package/richtexteditor/plugins/watermark.min.js +1 -0
  80. package/richtexteditor/plugins/wordexport.min.js +23 -0
  81. package/richtexteditor/plugins/yjscollab.min.js +2 -0
  82. package/richtexteditor/plugins/zz_richtextbox_dialog_style.min.js +1 -0
  83. package/richtexteditor/rte-config.js +2 -0
  84. 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
- id: heading.id,
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