@richscripts/richtexteditor 2.13.0 → 2.14.1

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 (86) hide show
  1. package/package.json +1 -1
  2. package/richtexteditor/all_plugins.js +126 -10
  3. package/richtexteditor/all_plugins.min.js +53 -53
  4. package/richtexteditor/plugins/a11yenhance.min.js +1 -0
  5. package/richtexteditor/plugins/accessibilitychecker.min.js +1 -0
  6. package/richtexteditor/plugins/aitoolkit.min.js +14 -0
  7. package/richtexteditor/plugins/all_plugins.js +126 -10
  8. package/richtexteditor/plugins/all_plugins.min.js +53 -53
  9. package/richtexteditor/plugins/autocorrect.min.js +1 -0
  10. package/richtexteditor/plugins/autoembed.min.js +1 -0
  11. package/richtexteditor/plugins/blocktypes.min.js +2 -0
  12. package/richtexteditor/plugins/bookmarkcard.min.js +2 -0
  13. package/richtexteditor/plugins/changecase.min.js +1 -0
  14. package/richtexteditor/plugins/charlimit.min.js +2 -0
  15. package/richtexteditor/plugins/comments.min.js +3 -0
  16. package/richtexteditor/plugins/contentminimap.min.js +1 -0
  17. package/richtexteditor/plugins/crossreference.min.js +1 -0
  18. package/richtexteditor/plugins/dialoga11y.min.js +1 -0
  19. package/richtexteditor/plugins/dictation.min.js +1 -0
  20. package/richtexteditor/plugins/documentimport.min.js +4 -0
  21. package/richtexteditor/plugins/documentoutline.js +54 -6
  22. package/richtexteditor/plugins/documentoutline.min.js +1 -0
  23. package/richtexteditor/plugins/docxexport.min.js +2 -0
  24. package/richtexteditor/plugins/draghandle.min.js +1 -0
  25. package/richtexteditor/plugins/emailtoolkit.min.js +1 -0
  26. package/richtexteditor/plugins/emojiautocomplete.min.js +1 -0
  27. package/richtexteditor/plugins/foldheadings.min.js +1 -0
  28. package/richtexteditor/plugins/footnotes.min.js +1 -0
  29. package/richtexteditor/plugins/formatpainter.min.js +1 -0
  30. package/richtexteditor/plugins/formattingmarks.js +48 -3
  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.js +24 -1
  65. package/richtexteditor/plugins/sanitizer.min.js +1 -0
  66. package/richtexteditor/plugins/shadowdom.min.js +1 -0
  67. package/richtexteditor/plugins/slashcommand.min.js +2 -0
  68. package/richtexteditor/plugins/smartchips.min.js +2 -0
  69. package/richtexteditor/plugins/spellcheck.min.js +2 -0
  70. package/richtexteditor/plugins/stateinspector.min.js +2 -0
  71. package/richtexteditor/plugins/structured-content-bridge.js +5178 -0
  72. package/richtexteditor/plugins/structured-content-bridge.min.js +19 -0
  73. package/richtexteditor/plugins/tableofcontents.min.js +1 -0
  74. package/richtexteditor/plugins/tabletools.min.js +1 -0
  75. package/richtexteditor/plugins/textdirection.min.js +1 -0
  76. package/richtexteditor/plugins/textpartlanguage.min.js +1 -0
  77. package/richtexteditor/plugins/todolist.min.js +1 -0
  78. package/richtexteditor/plugins/trackedchanges.min.js +3 -0
  79. package/richtexteditor/plugins/typewriter.min.js +1 -0
  80. package/richtexteditor/plugins/watermark.min.js +1 -0
  81. package/richtexteditor/plugins/wordexport.min.js +23 -0
  82. package/richtexteditor/plugins/yjscollab.min.js +2 -0
  83. package/richtexteditor/plugins/zz_richtextbox_dialog_style.min.js +1 -0
  84. package/richtexteditor/rte-config.js +3 -0
  85. package/richtexteditor/rte.js +5 -2
  86. package/richtexteditor/runtime/colorpicker_more_ns.htm +93 -16
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.13.0",
6
+ "version": "2.14.1",
7
7
  "description": "A full-featured JavaScript rich text editor (WYSIWYG) with toolbar, plugins, i18n support, a built-in AI Toolkit (Ask AI, AI Chat, AI Review drawer, BYOK resolver), structured content APIs (JSON/Markdown), and an optional collaboration / review stack: slash commands, @mentions, human-author Track Changes, threaded comments, revision history, and Yjs-backed presence. Ships first-class React, Vue, Angular and Svelte bindings, a headless \"bring your own UI\" core, and server-safe conversion helpers.",
8
8
  "main": "./richtexteditor/rte.js",
9
9
  "bin": {
@@ -25516,20 +25516,68 @@ function RTE_Plugin_DocumentOutline() {
25516
25516
  var minLevel = Math.max(1, Math.min(6, config.documentOutlineMinLevel || 1));
25517
25517
  var maxLevel = Math.max(minLevel, Math.min(6, config.documentOutlineMaxLevel || 6));
25518
25518
  var headings = editable.querySelectorAll("h1,h2,h3,h4,h5,h6");
25519
- var counts = {};
25520
25519
  var items = [];
25521
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
+ //
25530
+ // 2026-09-25 ...but only when the core does not own heading ids. With
25531
+ // autoHeadingAnchors on (the default) the core pass gives every heading a unique id
25532
+ // after each change, with the same first-keeps rule, and marks the ids it generated so
25533
+ // they can follow the text. This block ran 80 ms after each change instead: a heading
25534
+ // a person was typing got the slug of its FIRST letter ("Typed heading" -> id="t"),
25535
+ // unmarked, so the core took it for the author's own id and kept it for good. The
25536
+ // outline needs no id to work (it navigates by element), so it now leaves ids to the
25537
+ // core and keeps this pass for integrations that switched core anchors off.
25538
+ if (config.autoHeadingAnchors === false) {
25539
+ var used = {};
25540
+ var withId = editable.querySelectorAll("[id]");
25541
+ for (i = 0; i < withId.length; i++) {
25542
+ if (!/^H[1-6]$/.test(withId[i].nodeName)) used[withId[i].id] = true;
25543
+ }
25544
+ // Headings with text claim ids first. An EMPTY heading is usually the shell a cut
25545
+ // leaves behind, still carrying the moved heading's id; letting it claim first would
25546
+ // hand the moved heading "-2" and break every link to it. So empty headings go last
25547
+ // and simply drop an id that a real heading already holds.
25548
+ var textOf = function (el) {
25549
+ return (el.innerText || el.textContent || "").replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
25550
+ };
25551
+ var empties = [];
25552
+ for (i = 0; i < headings.length; i++) {
25553
+ var h = headings[i];
25554
+ var t = textOf(h);
25555
+ if (!t) { empties.push(h); continue; }
25556
+ var base = h.id;
25557
+ if (!base) base = slugifyHeading(t);
25558
+ else if (!used[base]) { used[base] = true; continue; }
25559
+ var unique = base;
25560
+ var n = 2;
25561
+ while (used[unique]) { unique = base + "-" + n; n++; }
25562
+ used[unique] = true;
25563
+ if (h.id !== unique) h.id = unique;
25564
+ }
25565
+ for (i = 0; i < empties.length; i++) {
25566
+ if (!empties[i].id) continue;
25567
+ if (used[empties[i].id]) empties[i].removeAttribute("id");
25568
+ else used[empties[i].id] = true;
25569
+ }
25570
+ }
25522
25571
  for (i = 0; i < headings.length; i++) {
25523
25572
  var heading = headings[i];
25524
25573
  var level = parseInt(heading.nodeName.substring(1), 10);
25525
25574
  if (level < minLevel || level > maxLevel) continue;
25526
25575
  var text = (heading.innerText || heading.textContent || "").replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
25527
- var baseId = heading.id || slugifyHeading(text);
25528
- counts[baseId] = (counts[baseId] || 0) + 1;
25529
- var resolvedId = heading.id || (counts[baseId] === 1 ? baseId : (baseId + "-" + counts[baseId]));
25530
- if (!heading.id) heading.id = resolvedId;
25531
25576
  items.push({
25532
- id: heading.id,
25577
+ // an empty heading may now have no id; key it internally (never written to
25578
+ // the element) so "" can't match the "no active heading" state and light up
25579
+ // every empty entry at once.
25580
+ id: heading.id || ("__rte-outline-" + items.length),
25533
25581
  level: level,
25534
25582
  text: text || ("Heading " + (items.length + 1)),
25535
25583
  element: heading
@@ -28440,22 +28488,67 @@ function RTE_Plugin_FormattingMarks() {
28440
28488
  // may be deliberate.
28441
28489
  if (parent.nodeName === "CODE" || parent.nodeName === "PRE") return;
28442
28490
 
28491
+ // Replacing the text node moves a caret inside it to the node's old place in its
28492
+ // parent - usually the paragraph start. This runs on every keyup, and the first
28493
+ // space typed at a line end is always a non-breaking one, so with marks on every
28494
+ // later character landed at the start, reversed: " edited" became "detide".
28495
+ // Remember where the selection was inside this node and put it back.
28496
+ var sel = doc.getSelection ? doc.getSelection() : null;
28497
+ var range = sel && sel.rangeCount ? sel.getRangeAt(0) : null;
28498
+ var keepStart = range && range.startContainer === node ? range.startOffset : -1;
28499
+ var keepEnd = range && range.endContainer === node ? range.endOffset : -1;
28500
+
28443
28501
  var frag = doc.createDocumentFragment();
28502
+ var pieces = []; // in order: { node, marker } - every marker stands for ONE character
28444
28503
  var buf = "";
28445
28504
  for (var i = 0; i < text.length; i++) {
28446
28505
  var def = chars[text[i]];
28506
+ // A space typed at a line end is inserted as a non-breaking placeholder that the
28507
+ // browser turns back into a space when the next character arrives. Wrapped in a
28508
+ // non-editable marker it never could, and saved as a hard &nbsp;. Leave the one
28509
+ // right before the caret alone; it is marked once the caret moves on.
28510
+ if (def && text[i] === " " && i === keepStart - 1 && keepStart === keepEnd) def = null;
28447
28511
  if (!def) { buf += text[i]; continue; }
28448
- if (buf) { frag.appendChild(doc.createTextNode(buf)); buf = ""; }
28512
+ if (buf) { pieces.push({ node: frag.appendChild(doc.createTextNode(buf)), marker: false }); buf = ""; }
28449
28513
  var span = doc.createElement("span");
28450
28514
  span.setAttribute("data-rte-fm", "1");
28451
28515
  span.setAttribute("data-rte-fm-char", text[i]);
28452
28516
  span.className = def.cls;
28453
28517
  span.setAttribute("contenteditable", "false");
28454
28518
  span.textContent = def.label;
28455
- frag.appendChild(span);
28519
+ pieces.push({ node: frag.appendChild(span), marker: true });
28456
28520
  }
28457
- if (buf) frag.appendChild(doc.createTextNode(buf));
28521
+ if (buf) pieces.push({ node: frag.appendChild(doc.createTextNode(buf)), marker: false });
28458
28522
  parent.replaceChild(frag, node);
28523
+
28524
+ if (keepStart < 0 && keepEnd < 0) return;
28525
+ // Offset k in the old text -> a point in the pieces. Inside text where possible;
28526
+ // never inside a marker (it is not editable): just before or after it.
28527
+ function pointAt(k) {
28528
+ var pos = 0;
28529
+ for (var p = 0; p < pieces.length; p++) {
28530
+ var piece = pieces[p];
28531
+ if (piece.marker) {
28532
+ if (k <= pos) return { node: parent, offset: Array.prototype.indexOf.call(parent.childNodes, piece.node) };
28533
+ pos += 1;
28534
+ } else {
28535
+ var len = piece.node.nodeValue.length;
28536
+ if (k <= pos + len) return { node: piece.node, offset: k - pos };
28537
+ pos += len;
28538
+ }
28539
+ }
28540
+ var last = pieces[pieces.length - 1].node;
28541
+ return { node: parent, offset: Array.prototype.indexOf.call(parent.childNodes, last) + 1 };
28542
+ }
28543
+ try {
28544
+ var r = doc.createRange();
28545
+ var a = pointAt(keepStart >= 0 ? keepStart : 0);
28546
+ var b = keepEnd >= 0 ? pointAt(keepEnd) : a;
28547
+ if (keepStart < 0) { r.setStart(range.startContainer, range.startOffset); } else r.setStart(a.node, a.offset);
28548
+ if (keepEnd < 0) { r.setEnd(range.endContainer, range.endOffset); } else r.setEnd(b.node, b.offset);
28549
+ sel.removeAllRanges();
28550
+ sel.addRange(r);
28551
+ } catch (e) { /* a selection we cannot restore is no reason to lose the marks */ }
28459
28552
  }
28460
28553
 
28461
28554
  // ---- serialization safety -------------------------------------------
@@ -40240,6 +40333,7 @@ function RTE_Plugin_Sanitizer() {
40240
40333
  }
40241
40334
 
40242
40335
  var attrs = [];
40336
+ var strippedSrc = false;
40243
40337
  for (var a = 0; a < el.attributes.length; a++) attrs.push(el.attributes[a]);
40244
40338
  for (var k = 0; k < attrs.length; k++) {
40245
40339
  var name = attrs[k].name.toLowerCase();
@@ -40257,7 +40351,11 @@ function RTE_Plugin_Sanitizer() {
40257
40351
  if (name.indexOf("data-") === 0 || name.indexOf("aria-") === 0) continue;
40258
40352
 
40259
40353
  if (URL_ATTRS[name]) {
40260
- if (!isSafeUrl(value, tag, name)) { note(report.removedAttributes, tag + "[" + name + "]"); el.removeAttribute(attrs[k].name); }
40354
+ if (!isSafeUrl(value, tag, name)) {
40355
+ note(report.removedAttributes, tag + "[" + name + "]");
40356
+ el.removeAttribute(attrs[k].name);
40357
+ if (name === "src") strippedSrc = true;
40358
+ }
40261
40359
  continue;
40262
40360
  }
40263
40361
  if (!ALLOWED_ATTRS[name] && !attrExtra(name)) {
@@ -40266,6 +40364,18 @@ function RTE_Plugin_Sanitizer() {
40266
40364
  }
40267
40365
  }
40268
40366
 
40367
+ // An <img> whose only source was just stripped is an empty box that
40368
+ // saves as <img> with no src -- dragging a page image into the editor
40369
+ // from a page opened on disk did exactly that (its file: src is not a
40370
+ // safe scheme). Drop the element when nothing is left to show. Keep it
40371
+ // when srcset or a lazy-load data-*src still carries an image, and never
40372
+ // touch an <img> that arrived without a src (an app placeholder).
40373
+ if (tag === "img" && strippedSrc && !el.hasAttribute("srcset") && !hasDataSource(el)) {
40374
+ note(report.removedTags, "img");
40375
+ el.parentNode.removeChild(el);
40376
+ continue;
40377
+ }
40378
+
40269
40379
  // A link that opens a new tab without rel="noopener" hands the
40270
40380
  // opener window to the destination.
40271
40381
  if (tag === "a" && (el.getAttribute("target") || "").toLowerCase() === "_blank") {
@@ -40282,6 +40392,12 @@ function RTE_Plugin_Sanitizer() {
40282
40392
  parent.removeChild(el);
40283
40393
  }
40284
40394
  function note(list, what) { if (list.indexOf(what) < 0) list.push(what); }
40395
+ function hasDataSource(el) {
40396
+ for (var i = 0; i < el.attributes.length; i++) {
40397
+ if (/^data-(.*-)?(src|srcset|original)$/i.test(el.attributes[i].name)) return true;
40398
+ }
40399
+ return false;
40400
+ }
40285
40401
 
40286
40402
  function sanitizeHtml(html) {
40287
40403
  var out = html;