@richscripts/richtexteditor 2.14.0 → 2.15.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 (238) hide show
  1. package/README.md +173 -10
  2. package/angular/build-manifest.json +23 -0
  3. package/angular/index.d.ts +71 -76
  4. package/angular/index.mjs +2 -0
  5. package/angular/package.json +4 -0
  6. package/headless/index.d.ts +3 -1
  7. package/headless/index.js +72 -16
  8. package/index.d.ts +33 -54
  9. package/integrations/initialization-scope.js +27 -0
  10. package/integrations/native-mount.js +30 -0
  11. package/integrations/shared.d.ts +23 -1
  12. package/integrations/shared.js +58 -22
  13. package/package.json +25 -3
  14. package/react/index.js +118 -37
  15. package/react/index.mjs +29 -0
  16. package/richtexteditor/all_plugins.js +7113 -1111
  17. package/richtexteditor/all_plugins.min.js +62 -59
  18. package/richtexteditor/lang/emoji/LICENSE-CLDR.txt +41 -0
  19. package/richtexteditor/lang/emoji/rte-emoji-de.js +3 -0
  20. package/richtexteditor/lang/emoji/rte-emoji-es.js +3 -0
  21. package/richtexteditor/lang/emoji/rte-emoji-fr.js +3 -0
  22. package/richtexteditor/lang/emoji/rte-emoji-it.js +3 -0
  23. package/richtexteditor/lang/emoji/rte-emoji-ja.js +3 -0
  24. package/richtexteditor/lang/emoji/rte-emoji-ko.js +3 -0
  25. package/richtexteditor/lang/emoji/rte-emoji-nl.js +3 -0
  26. package/richtexteditor/lang/emoji/rte-emoji-pt-pt.js +3 -0
  27. package/richtexteditor/lang/emoji/rte-emoji-pt.js +3 -0
  28. package/richtexteditor/lang/emoji/rte-emoji-ru.js +3 -0
  29. package/richtexteditor/lang/emoji/rte-emoji-zh-Hans.js +3 -0
  30. package/richtexteditor/lang/emoji/rte-emoji-zh-Hant.js +3 -0
  31. package/richtexteditor/lang/rte-lang-af.js +1 -0
  32. package/richtexteditor/lang/rte-lang-ar.js +1 -0
  33. package/richtexteditor/lang/rte-lang-bg.js +1 -0
  34. package/richtexteditor/lang/rte-lang-bn.js +1 -0
  35. package/richtexteditor/lang/rte-lang-bs.js +1 -0
  36. package/richtexteditor/lang/rte-lang-ca.js +1 -0
  37. package/richtexteditor/lang/rte-lang-cs.js +1 -0
  38. package/richtexteditor/lang/rte-lang-cy.js +1 -0
  39. package/richtexteditor/lang/rte-lang-da.js +1 -0
  40. package/richtexteditor/lang/rte-lang-de.js +1017 -2
  41. package/richtexteditor/lang/rte-lang-el.js +1 -0
  42. package/richtexteditor/lang/rte-lang-en.js +1 -0
  43. package/richtexteditor/lang/rte-lang-es.js +1023 -2
  44. package/richtexteditor/lang/rte-lang-et.js +1 -0
  45. package/richtexteditor/lang/rte-lang-fa.js +1 -0
  46. package/richtexteditor/lang/rte-lang-fi.js +1 -0
  47. package/richtexteditor/lang/rte-lang-fil.js +1 -0
  48. package/richtexteditor/lang/rte-lang-fj.js +1 -0
  49. package/richtexteditor/lang/rte-lang-fr.js +1023 -2
  50. package/richtexteditor/lang/rte-lang-ga.js +1 -0
  51. package/richtexteditor/lang/rte-lang-gu.js +1 -0
  52. package/richtexteditor/lang/rte-lang-he.js +1 -0
  53. package/richtexteditor/lang/rte-lang-hi.js +1 -0
  54. package/richtexteditor/lang/rte-lang-hr.js +1 -0
  55. package/richtexteditor/lang/rte-lang-ht.js +1 -0
  56. package/richtexteditor/lang/rte-lang-hu.js +1 -0
  57. package/richtexteditor/lang/rte-lang-id.js +1 -0
  58. package/richtexteditor/lang/rte-lang-is.js +1 -0
  59. package/richtexteditor/lang/rte-lang-it.js +1023 -2
  60. package/richtexteditor/lang/rte-lang-ja.js +1023 -2
  61. package/richtexteditor/lang/rte-lang-kn.js +1 -0
  62. package/richtexteditor/lang/rte-lang-ko.js +1023 -2
  63. package/richtexteditor/lang/rte-lang-lt.js +1 -0
  64. package/richtexteditor/lang/rte-lang-lv.js +1 -0
  65. package/richtexteditor/lang/rte-lang-mg.js +1 -0
  66. package/richtexteditor/lang/rte-lang-mi.js +1 -0
  67. package/richtexteditor/lang/rte-lang-ml.js +1 -0
  68. package/richtexteditor/lang/rte-lang-mr.js +1 -0
  69. package/richtexteditor/lang/rte-lang-ms.js +1 -0
  70. package/richtexteditor/lang/rte-lang-mt.js +1 -0
  71. package/richtexteditor/lang/rte-lang-mww.js +1 -0
  72. package/richtexteditor/lang/rte-lang-nb.js +1 -0
  73. package/richtexteditor/lang/rte-lang-nl.js +1023 -2
  74. package/richtexteditor/lang/rte-lang-otq.js +1 -0
  75. package/richtexteditor/lang/rte-lang-pa.js +1 -0
  76. package/richtexteditor/lang/rte-lang-pl.js +1 -0
  77. package/richtexteditor/lang/rte-lang-pt-pt.js +1023 -2
  78. package/richtexteditor/lang/rte-lang-pt.js +1023 -2
  79. package/richtexteditor/lang/rte-lang-ro.js +1 -0
  80. package/richtexteditor/lang/rte-lang-ru.js +1023 -2
  81. package/richtexteditor/lang/rte-lang-sk.js +1 -0
  82. package/richtexteditor/lang/rte-lang-sl.js +1 -0
  83. package/richtexteditor/lang/rte-lang-sm.js +1 -0
  84. package/richtexteditor/lang/rte-lang-sr-Cyrl.js +1 -0
  85. package/richtexteditor/lang/rte-lang-sr-Latn.js +1 -0
  86. package/richtexteditor/lang/rte-lang-sv.js +1 -0
  87. package/richtexteditor/lang/rte-lang-sw.js +1 -0
  88. package/richtexteditor/lang/rte-lang-ta.js +1 -0
  89. package/richtexteditor/lang/rte-lang-te.js +1 -0
  90. package/richtexteditor/lang/rte-lang-th.js +1 -0
  91. package/richtexteditor/lang/rte-lang-tlh-Latn.js +1 -0
  92. package/richtexteditor/lang/rte-lang-tlh-Piqd.js +1 -0
  93. package/richtexteditor/lang/rte-lang-to.js +1 -0
  94. package/richtexteditor/lang/rte-lang-tr.js +1 -0
  95. package/richtexteditor/lang/rte-lang-ty.js +1 -0
  96. package/richtexteditor/lang/rte-lang-uk.js +1 -0
  97. package/richtexteditor/lang/rte-lang-ur.js +1 -0
  98. package/richtexteditor/lang/rte-lang-vi.js +1 -0
  99. package/richtexteditor/lang/rte-lang-yua.js +1 -0
  100. package/richtexteditor/lang/rte-lang-zh-Hans.js +1017 -2
  101. package/richtexteditor/lang/rte-lang-zh-Hant.js +1017 -2
  102. package/richtexteditor/plugins/a11yenhance.js +2 -1
  103. package/richtexteditor/plugins/a11yenhance.min.js +1 -1
  104. package/richtexteditor/plugins/accessibilitychecker.js +252 -60
  105. package/richtexteditor/plugins/accessibilitychecker.min.js +1 -1
  106. package/richtexteditor/plugins/aitoolkit.js +966 -303
  107. package/richtexteditor/plugins/aitoolkit.min.js +7 -7
  108. package/richtexteditor/plugins/all_plugins.js +7113 -1111
  109. package/richtexteditor/plugins/all_plugins.min.js +62 -59
  110. package/richtexteditor/plugins/autocorrect.js +66 -31
  111. package/richtexteditor/plugins/autocorrect.min.js +1 -1
  112. package/richtexteditor/plugins/autoembed.js +17 -0
  113. package/richtexteditor/plugins/autoembed.min.js +1 -1
  114. package/richtexteditor/plugins/blocktypes.js +18 -9
  115. package/richtexteditor/plugins/blocktypes.min.js +1 -1
  116. package/richtexteditor/plugins/bookmarkcard.js +12 -5
  117. package/richtexteditor/plugins/bookmarkcard.min.js +2 -2
  118. package/richtexteditor/plugins/changecase.js +72 -19
  119. package/richtexteditor/plugins/changecase.min.js +1 -1
  120. package/richtexteditor/plugins/charlimit.js +62 -2
  121. package/richtexteditor/plugins/charlimit.min.js +2 -2
  122. package/richtexteditor/plugins/comments.js +458 -59
  123. package/richtexteditor/plugins/comments.min.js +4 -3
  124. package/richtexteditor/plugins/contentminimap.js +25 -6
  125. package/richtexteditor/plugins/contentminimap.min.js +1 -1
  126. package/richtexteditor/plugins/crossreference.js +1 -1
  127. package/richtexteditor/plugins/crossreference.min.js +1 -1
  128. package/richtexteditor/plugins/dialoga11y.js +26 -4
  129. package/richtexteditor/plugins/dialoga11y.min.js +1 -1
  130. package/richtexteditor/plugins/dictation.js +1 -1
  131. package/richtexteditor/plugins/documentimport.js +220 -51
  132. package/richtexteditor/plugins/documentimport.min.js +4 -4
  133. package/richtexteditor/plugins/documentoutline.js +88 -38
  134. package/richtexteditor/plugins/documentoutline.min.js +1 -1
  135. package/richtexteditor/plugins/docxexport.js +204 -32
  136. package/richtexteditor/plugins/docxexport.min.js +2 -2
  137. package/richtexteditor/plugins/draghandle.js +267 -3
  138. package/richtexteditor/plugins/draghandle.min.js +1 -1
  139. package/richtexteditor/plugins/emailtoolkit.js +28 -14
  140. package/richtexteditor/plugins/emailtoolkit.min.js +1 -1
  141. package/richtexteditor/plugins/emojiautocomplete.js +110 -56
  142. package/richtexteditor/plugins/emojiautocomplete.min.js +1 -1
  143. package/richtexteditor/plugins/footnotes.js +65 -11
  144. package/richtexteditor/plugins/footnotes.min.js +1 -1
  145. package/richtexteditor/plugins/formatpainter.js +10 -0
  146. package/richtexteditor/plugins/formatpainter.min.js +1 -1
  147. package/richtexteditor/plugins/formattingmarks.js +53 -8
  148. package/richtexteditor/plugins/formattingmarks.min.js +1 -1
  149. package/richtexteditor/plugins/gapcursor.js +60 -0
  150. package/richtexteditor/plugins/gapcursor.min.js +1 -1
  151. package/richtexteditor/plugins/ghostcomplete.js +1 -1
  152. package/richtexteditor/plugins/html2pdf.js +1 -1
  153. package/richtexteditor/plugins/html2pdf.min.js +1 -1
  154. package/richtexteditor/plugins/imageeditor.js +12 -6
  155. package/richtexteditor/plugins/imageeditor.min.js +1 -1
  156. package/richtexteditor/plugins/importstyles.js +1 -1
  157. package/richtexteditor/plugins/inlinecode.js +25 -0
  158. package/richtexteditor/plugins/inlinecode.min.js +1 -1
  159. package/richtexteditor/plugins/insertcode.js +21 -3
  160. package/richtexteditor/plugins/insertcode.min.js +4 -4
  161. package/richtexteditor/plugins/insertemoji.js +112 -15
  162. package/richtexteditor/plugins/insertemoji.min.js +1 -1
  163. package/richtexteditor/plugins/insertgallery.js +42 -36
  164. package/richtexteditor/plugins/insertgallery.min.js +1 -1
  165. package/richtexteditor/plugins/inserttemplate.js +33 -17
  166. package/richtexteditor/plugins/inserttemplate.min.js +1 -1
  167. package/richtexteditor/plugins/keyboarda11y.js +66 -3
  168. package/richtexteditor/plugins/keyboarda11y.min.js +1 -1
  169. package/richtexteditor/plugins/layouttable.js +20 -0
  170. package/richtexteditor/plugins/layouttable.min.js +1 -1
  171. package/richtexteditor/plugins/linkchecker.js +29 -9
  172. package/richtexteditor/plugins/linkchecker.min.js +1 -1
  173. package/richtexteditor/plugins/localdraft.js +51 -7
  174. package/richtexteditor/plugins/localdraft.min.js +1 -1
  175. package/richtexteditor/plugins/markdownexport.js +159 -0
  176. package/richtexteditor/plugins/markdownexport.min.js +2 -2
  177. package/richtexteditor/plugins/mathdialog.js +11 -5
  178. package/richtexteditor/plugins/mathdialog.min.js +1 -1
  179. package/richtexteditor/plugins/mentionplugin.js +88 -25
  180. package/richtexteditor/plugins/mentionplugin.min.js +1 -1
  181. package/richtexteditor/plugins/mermaiddiagram.js +9 -3
  182. package/richtexteditor/plugins/mermaiddiagram.min.js +2 -2
  183. package/richtexteditor/plugins/multilevellist.js +4 -0
  184. package/richtexteditor/plugins/multilevellist.min.js +1 -1
  185. package/richtexteditor/plugins/pdfexport.js +235 -35
  186. package/richtexteditor/plugins/pdfexport.min.js +12 -12
  187. package/richtexteditor/plugins/permanentpen.js +3 -3
  188. package/richtexteditor/plugins/permanentpen.min.js +1 -1
  189. package/richtexteditor/plugins/readabilitystats.js +25 -11
  190. package/richtexteditor/plugins/readabilitystats.min.js +1 -1
  191. package/richtexteditor/plugins/readaloud.js +29 -15
  192. package/richtexteditor/plugins/readaloud.min.js +1 -1
  193. package/richtexteditor/plugins/restrictedediting.js +166 -0
  194. package/richtexteditor/plugins/restrictedediting.min.js +2 -2
  195. package/richtexteditor/plugins/revisionhistory.js +169 -37
  196. package/richtexteditor/plugins/revisionhistory.min.js +3 -3
  197. package/richtexteditor/plugins/rubyannotation.js +19 -9
  198. package/richtexteditor/plugins/rubyannotation.min.js +1 -1
  199. package/richtexteditor/plugins/sanitizer.js +86 -4
  200. package/richtexteditor/plugins/sanitizer.min.js +1 -1
  201. package/richtexteditor/plugins/slashcommand.js +30 -16
  202. package/richtexteditor/plugins/slashcommand.min.js +2 -2
  203. package/richtexteditor/plugins/smartchips.js +36 -22
  204. package/richtexteditor/plugins/smartchips.min.js +2 -2
  205. package/richtexteditor/plugins/spellcheck.js +17 -8
  206. package/richtexteditor/plugins/spellcheck.min.js +2 -2
  207. package/richtexteditor/plugins/stateinspector.js +19 -9
  208. package/richtexteditor/plugins/stateinspector.min.js +2 -2
  209. package/richtexteditor/plugins/structured-content-bridge.js +56 -24
  210. package/richtexteditor/plugins/structured-content-bridge.min.js +7 -7
  211. package/richtexteditor/plugins/tableofcontents.js +5 -2
  212. package/richtexteditor/plugins/tableofcontents.min.js +1 -1
  213. package/richtexteditor/plugins/tabletools.js +388 -3
  214. package/richtexteditor/plugins/tabletools.min.js +1 -1
  215. package/richtexteditor/plugins/textdirection.js +18 -5
  216. package/richtexteditor/plugins/textdirection.min.js +1 -1
  217. package/richtexteditor/plugins/textpartlanguage.js +161 -32
  218. package/richtexteditor/plugins/textpartlanguage.min.js +1 -1
  219. package/richtexteditor/plugins/todolist.js +90 -6
  220. package/richtexteditor/plugins/todolist.min.js +1 -1
  221. package/richtexteditor/plugins/trackedchanges.js +1812 -25
  222. package/richtexteditor/plugins/trackedchanges.min.js +5 -3
  223. package/richtexteditor/plugins/wordexport.js +2 -2
  224. package/richtexteditor/plugins/yjscollab.js +9 -4
  225. package/richtexteditor/plugins/yjscollab.min.js +1 -1
  226. package/richtexteditor/plugins/zz_richtextbox_dialog_style.js +22 -13
  227. package/richtexteditor/plugins/zz_richtextbox_dialog_style.min.js +1 -1
  228. package/richtexteditor/rte-config.js +131 -50
  229. package/richtexteditor/rte-headless.js +19 -4
  230. package/richtexteditor/rte-webcomponent.js +89 -16
  231. package/richtexteditor/rte.js +133 -52
  232. package/richtexteditor/runtime/colorpicker_more_ns.htm +93 -16
  233. package/scripts/build-angular.mjs +97 -0
  234. package/svelte/index.d.ts +2 -1
  235. package/svelte/index.js +80 -9
  236. package/vue/index.js +130 -34
  237. package/angular/component.ts +0 -297
  238. package/angular/index.ts +0 -24
@@ -194,8 +194,11 @@ function RTE_Plugin_DocxExport() {
194
194
  bold: inherited.bold, italic: inherited.italic, underline: inherited.underline,
195
195
  strike: inherited.strike, sizeHalfPoints: inherited.sizeHalfPoints,
196
196
  color: inherited.color, highlight: inherited.highlight, vertAlign: inherited.vertAlign,
197
- mono: inherited.mono, font: inherited.font
197
+ mono: inherited.mono, font: inherited.font,
198
+ // 2026-10-15 small caps, letter spacing and the phrase's language were not exported. tools/docx/run-props.mjs
199
+ smallCaps: inherited.smallCaps, spacingTw: inherited.spacingTw, lang: inherited.lang
198
200
  };
201
+ if (node.getAttribute && node.getAttribute("lang")) s.lang = String(node.getAttribute("lang")).replace(/[^A-Za-z0-9-]/g, "");
199
202
  var tag = node.tagName ? node.tagName.toLowerCase() : "";
200
203
  if (tag === "b" || tag === "strong") s.bold = true;
201
204
  if (tag === "i" || tag === "em") s.italic = true;
@@ -224,6 +227,11 @@ function RTE_Plugin_DocxExport() {
224
227
  var fam = String(css.fontFamily).split(",")[0].replace(/^\s*['"]?|['"]?\s*$/g, "");
225
228
  if (fam) s.font = fam;
226
229
  }
230
+ if (/small-caps/.test(css.fontVariant || "") || /small-caps/.test(css.fontVariantCaps || "")) s.smallCaps = true;
231
+ if (css.letterSpacing && css.letterSpacing !== "normal") {
232
+ var lsm = /^\s*(-?[\d.]+)\s*(px|pt|em)?\s*$/.exec(css.letterSpacing);
233
+ if (lsm) { var lsPx = parseFloat(lsm[1]) * (lsm[2] === "pt" ? 4 / 3 : lsm[2] === "em" ? 16 : 1); s.spacingTw = Math.round(lsPx * 15); }
234
+ }
227
235
  if (css.fontSize) {
228
236
  var pt = cssToPoints(css.fontSize);
229
237
  // w:sz is in HALF-POINTS. Writing points here makes every
@@ -266,12 +274,15 @@ function RTE_Plugin_DocxExport() {
266
274
  if (fontName) p += '<w:rFonts w:ascii="' + esc(fontName) + '" w:hAnsi="' + esc(fontName) + '"/>';
267
275
  if (s.bold) p += "<w:b/>";
268
276
  if (s.italic) p += "<w:i/>";
277
+ if (s.smallCaps) p += "<w:smallCaps/>";
269
278
  if (s.underline) p += '<w:u w:val="single"/>';
270
279
  if (s.strike) p += "<w:strike/>";
271
280
  if (s.color) p += '<w:color w:val="' + s.color + '"/>';
281
+ if (s.spacingTw) p += '<w:spacing w:val="' + s.spacingTw + '"/>';
272
282
  if (s.highlight) p += '<w:shd w:val="clear" w:color="auto" w:fill="' + s.highlight + '"/>';
273
283
  if (s.vertAlign) p += '<w:vertAlign w:val="' + s.vertAlign + '"/>';
274
284
  if (s.sizeHalfPoints) p += '<w:sz w:val="' + s.sizeHalfPoints + '"/><w:szCs w:val="' + s.sizeHalfPoints + '"/>';
285
+ if (s.lang) p += '<w:lang w:val="' + s.lang + '"/>';
275
286
  return p ? "<w:rPr>" + p + "</w:rPr>" : "";
276
287
  }
277
288
 
@@ -388,7 +399,12 @@ function RTE_Plugin_DocxExport() {
388
399
  function noteBodyFor(doc, id) {
389
400
  try {
390
401
  var el = doc.querySelector('.rte-fn-note[data-fn-id="' + id + '"]');
391
- return el ? (el.textContent || "").replace(/\s+/g, " ").trim() : "";
402
+ if (!el) return "";
403
+ // 2026-10-15 without the "back to reference" arrow link: it was written into the Word footnote, and every
404
+ // round trip added another arrow to the note. tools/docx/footnote-text.mjs
405
+ var copy = el.cloneNode(true), backs = copy.querySelectorAll(".rte-fn-back");
406
+ for (var i = 0; i < backs.length; i++) backs[i].parentNode.removeChild(backs[i]);
407
+ return (copy.textContent || "").replace(/\s+/g, " ").trim();
392
408
  } catch (e) { return ""; }
393
409
  }
394
410
  function noteRun(b, kind, text) {
@@ -404,8 +420,14 @@ function RTE_Plugin_DocxExport() {
404
420
  b.media.push({ name: name, bytes: data.bytes });
405
421
  var rid = b.rel("image", "media/" + name);
406
422
  // EMU: 914400 per inch, and a CSS pixel is 1/96 inch.
407
- var cx = Math.round(data.width * 914400 / 96);
408
- var cy = Math.round(data.height * 914400 / 96);
423
+ // 2026-10-15 the size on the page is the image's width / height (attribute, or a px style), not its natural
424
+ // pixels: a picture resized in the editor came back full size. One given: the other keeps the ratio.
425
+ // tools/docx/image-alt-size.mjs
426
+ var dw = cssPx(img.getAttribute("width")) || cssPx(img.style && img.style.width), dh = cssPx(img.getAttribute("height")) || cssPx(img.style && img.style.height);
427
+ if (dw && !dh) dh = dw * data.height / data.width;
428
+ if (dh && !dw) dw = dh * data.width / data.height;
429
+ var cx = Math.round((dw || data.width) * 914400 / 96);
430
+ var cy = Math.round((dh || data.height) * 914400 / 96);
409
431
  var docPrId = b.media.length;
410
432
  return '<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0" ' +
411
433
  'xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing">' +
@@ -421,6 +443,9 @@ function RTE_Plugin_DocxExport() {
421
443
  "</pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>";
422
444
  }
423
445
 
446
+ // a plain number or a px length; a percentage or em cannot be turned into a page size here
447
+ function cssPx(v) { var r = /^\s*([\d.]+)\s*(px)?\s*$/.exec(v == null ? "" : String(v)); return r ? parseFloat(r[1]) : 0; }
448
+
424
449
  function encodeImage(img) {
425
450
  try {
426
451
  var w = img.naturalWidth || img.width, h = img.naturalHeight || img.height;
@@ -449,18 +474,18 @@ function RTE_Plugin_DocxExport() {
449
474
  // A compact LaTeX -> OMML writer, matching the reader in documentimport.js
450
475
  // so equations survive the round trip instead of arriving as their source.
451
476
  var TEX_GLYPH = {
452
- alpha: "α", beta: "β", gamma: "γ", delta: "δ", epsilon: "ε", zeta: "ζ", eta: "η",
453
- theta: "θ", iota: "ι", kappa: "κ", lambda: "λ", mu: "μ", nu: "ν", xi: "ξ", pi: "π",
454
- rho: "ρ", sigma: "σ", tau: "τ", upsilon: "υ", phi: "φ", varphi: "φ", chi: "χ",
455
- psi: "ψ", omega: "ω", Gamma: "Γ", Delta: "Δ", Theta: "Θ", Lambda: "Λ", Xi: "Ξ",
456
- Pi: "Π", Sigma: "Σ", Phi: "Φ", Psi: "Ψ", Omega: "Ω",
457
- pm: "±", mp: "∓", times: "×", div: "÷", cdot: "⋅", neq: "≠", leq: "≤", geq: "≥",
458
- approx: "≈", equiv: "≡", propto: "∝", infty: "∞", partial: "∂", nabla: "∇",
459
- to: "→", rightarrow: "→", leftarrow: "←", Rightarrow: "⇒", Leftrightarrow: "⇔",
460
- "in": "∈", notin: "∉", subset: "⊂", subseteq: "⊆", cup: "∪", cap: "∩",
461
- emptyset: "∅", forall: "∀", exists: "∃", neg: "¬", land: "∧", lor: "∨",
462
- dots: "…", ldots: "…", cdots: "⋯", langle: "⟨", rangle: "⟩",
463
- lfloor: "⌊", rfloor: "⌋", lceil: "⌈", rceil: "⌉", quad: " ", ",": " ", ";": " "
477
+ alpha: "\u03b1", beta: "\u03b2", gamma: "\u03b3", delta: "\u03b4", epsilon: "\u03b5", zeta: "\u03b6", eta: "\u03b7",
478
+ theta: "\u03b8", iota: "\u03b9", kappa: "\u03ba", lambda: "\u03bb", mu: "\u03bc", nu: "\u03bd", xi: "\u03be", pi: "\u03c0",
479
+ rho: "\u03c1", sigma: "\u03c3", tau: "\u03c4", upsilon: "\u03c5", phi: "\u03c6", varphi: "\u03c6", chi: "\u03c7",
480
+ psi: "\u03c8", omega: "\u03c9", Gamma: "\u0393", Delta: "\u0394", Theta: "\u0398", Lambda: "\u039b", Xi: "\u039e",
481
+ Pi: "\u03a0", Sigma: "\u03a3", Phi: "\u03a6", Psi: "\u03a8", Omega: "\u03a9",
482
+ pm: "\xb1", mp: "\u2213", times: "\xd7", div: "\xf7", cdot: "\u22c5", neq: "\u2260", leq: "\u2264", geq: "\u2265",
483
+ approx: "\u2248", equiv: "\u2261", propto: "\u221d", infty: "\u221e", partial: "\u2202", nabla: "\u2207",
484
+ to: "\u2192", rightarrow: "\u2192", leftarrow: "\u2190", Rightarrow: "\u21d2", Leftrightarrow: "\u21d4",
485
+ "in": "\u2208", notin: "\u2209", subset: "\u2282", subseteq: "\u2286", cup: "\u222a", cap: "\u2229",
486
+ emptyset: "\u2205", forall: "\u2200", exists: "\u2203", neg: "\xac", land: "\u2227", lor: "\u2228",
487
+ dots: "\u2026", ldots: "\u2026", cdots: "\u22ef", langle: "\u27e8", rangle: "\u27e9",
488
+ lfloor: "\u230a", rfloor: "\u230b", lceil: "\u2308", rceil: "\u2309", quad: " ", ",": " ", ";": " "
464
489
  };
465
490
  var TEX_FUNCS = /^(sin|cos|tan|cot|sec|csc|sinh|cosh|tanh|log|ln|lg|exp|det|dim|gcd|arg|max|min|sup|inf)$/;
466
491
 
@@ -508,7 +533,7 @@ function RTE_Plugin_DocxExport() {
508
533
  "<m:deg>" + deg + "</m:deg><m:e>" + group() + "</m:e></m:rad>";
509
534
  }
510
535
  case "sum": case "prod": case "int": case "oint": case "bigcup": case "bigcap": {
511
- var chr = { sum: "∑", prod: "∏", "int": "∫", oint: "∮", bigcup: "⋃", bigcap: "⋂" }[name];
536
+ var chr = { sum: "\u2211", prod: "\u220f", "int": "\u222b", oint: "\u222e", bigcup: "\u22c3", bigcap: "\u22c2" }[name];
512
537
  var sub = "", sup = "";
513
538
  for (var g = 0; g < 2; g++) {
514
539
  skipWs();
@@ -543,7 +568,7 @@ function RTE_Plugin_DocxExport() {
543
568
  case "operatorname":
544
569
  return "<m:func><m:funcPr/><m:fName>" + group() + "</m:fName><m:e>" + group() + "</m:e></m:func>";
545
570
  case "hat": case "vec": case "tilde": case "bar": case "overline": case "dot": case "ddot": {
546
- var mark = { hat: "̂", vec: "⃗", tilde: "̃", dot: "̇", ddot: "̈" }[name];
571
+ var mark = { hat: "\u0302", vec: "\u20d7", tilde: "\u0303", dot: "\u0307", ddot: "\u0308" }[name];
547
572
  if (name === "bar" || name === "overline") return "<m:bar><m:barPr><m:pos m:val=\"top\"/></m:barPr><m:e>" + group() + "</m:e></m:bar>";
548
573
  return '<m:acc><m:accPr><m:chr m:val="' + mark + '"/></m:accPr><m:e>' + group() + "</m:e></m:acc>";
549
574
  }
@@ -632,6 +657,14 @@ function RTE_Plugin_DocxExport() {
632
657
  // spelled <blockquote>, which dragged the Quote style along with it -
633
658
  // so indented body text arrived italic and accent-coloured.
634
659
  // 1px = 0.75pt and 1pt = 20 twips, hence px * 15.
660
+ // a signed CSS length in twips (1px = 15 twips); 0 when it is not a plain length
661
+ function lengthTwips(raw) {
662
+ var m = /^\s*(-?[\d.]+)\s*(px|pt|in|cm|mm|em|rem)?\s*$/i.exec(raw == null ? "" : String(raw));
663
+ if (!m) return 0;
664
+ var n = parseFloat(m[1]), u = (m[2] || "px").toLowerCase();
665
+ var px = u === "px" ? n : u === "pt" ? n * 4 / 3 : u === "in" ? n * 96 : u === "cm" ? n * 37.795 : u === "mm" ? n * 3.7795 : n * 16;
666
+ return Math.round(px * 15);
667
+ }
635
668
  function indentTwips(node) {
636
669
  if (!node || !node.style) return 0;
637
670
  var raw = node.style.marginLeft || node.style.paddingLeft || "";
@@ -656,13 +689,23 @@ function RTE_Plugin_DocxExport() {
656
689
  if (opts.heading) pr += '<w:pStyle w:val="Heading' + opts.heading + '"/>';
657
690
  if (opts.quote) pr += '<w:pStyle w:val="Quote"/><w:ind w:left="720"/>';
658
691
  if (opts.pre) pr += '<w:pStyle w:val="Code"/>';
692
+ if (opts.caption) pr += '<w:pStyle w:val="Caption"/>';
659
693
  if (opts.numId) pr += "<w:numPr><w:ilvl w:val=\"" + (opts.level || 0) + "\"/><w:numId w:val=\"" + opts.numId + "\"/></w:numPr>";
660
694
  if (opts.pageBreakBefore) pr += "<w:pageBreakBefore/>";
695
+ // 2026-10-15 direction, line height and first-line / hanging indent were not written (only the left indent
696
+ // and alignment were): an RTL paragraph came back left-to-right, double spacing came back single.
697
+ // pPr order is bidi, spacing, ind, jc. tools/docx/paragraph-props.mjs
698
+ if (node && node.getAttribute && (String(node.getAttribute("dir") || "").toLowerCase() === "rtl" || (node.style && node.style.direction === "rtl"))) pr += "<w:bidi/>";
699
+ var lhRaw = node && node.style ? String(node.style.lineHeight || "") : "", lhm;
700
+ if ((lhm = /^\s*([\d.]+)\s*$/.exec(lhRaw)) && parseFloat(lhm[1]) > 0) pr += '<w:spacing w:line="' + Math.round(parseFloat(lhm[1]) * 240) + '" w:lineRule="auto"/>';
701
+ else if ((lhm = /^\s*([\d.]+)%\s*$/.exec(lhRaw)) && parseFloat(lhm[1]) > 0) pr += '<w:spacing w:line="' + Math.round(parseFloat(lhm[1]) * 2.4) + '" w:lineRule="auto"/>';
702
+ else if ((lhm = /^\s*([\d.]+)(px|pt)\s*$/.exec(lhRaw)) && parseFloat(lhm[1]) > 0) pr += '<w:spacing w:line="' + Math.round(parseFloat(lhm[1]) * (lhm[2] === "pt" ? 20 : 15)) + '" w:lineRule="exact"/>';
661
703
  // Independent of the Quote style: a real blockquote already got
662
704
  // its w:ind above, so only emit this for everything else.
663
705
  if (!opts.quote) {
664
706
  var indTw = indentTwips(node);
665
- if (indTw > 0) pr += '<w:ind w:left="' + indTw + '"/>';
707
+ var tiTw = lengthTwips(node && node.style ? node.style.textIndent : "");
708
+ if (indTw > 0 || tiTw) pr += '<w:ind' + (indTw > 0 ? ' w:left="' + indTw + '"' : "") + (tiTw > 0 ? ' w:firstLine="' + tiTw + '"' : tiTw < 0 ? ' w:hanging="' + (-tiTw) + '"' : "") + '/>';
666
709
  }
667
710
  var align = node && node.style ? node.style.textAlign : "";
668
711
  if (align === "center" || align === "right") pr += '<w:jc w:val="' + align + '"/>';
@@ -672,11 +715,52 @@ function RTE_Plugin_DocxExport() {
672
715
  }
673
716
 
674
717
  function list(node, style, level, pendingBreak) {
718
+ // 2026-10-15 a task list: Word has no plain-text checklist, so each item is an indented paragraph that
719
+ // starts with a ballot box, open (U+2610) or ticked (U+2612), as Google Docs exports it. It went out as an
720
+ // ordinary bulleted list and every tick was lost. tools/docx/task-list.mjs
721
+ if (node.classList && node.classList.contains("rte-task-list")) {
722
+ var txml = "";
723
+ for (var ti = 0; ti < node.childNodes.length; ti++) {
724
+ var tli = node.childNodes[ti];
725
+ if (tli.nodeType !== 1 || tli.tagName.toLowerCase() !== "li") continue;
726
+ var town = tli.ownerDocument.createElement("p");
727
+ for (var tk = 0; tk < tli.childNodes.length; tk++) {
728
+ var tch = tli.childNodes[tk];
729
+ if (tch.nodeType === 1 && (/^(ul|ol)$/.test(tch.tagName.toLowerCase()) || (tch.classList && tch.classList.contains("rte-task-checkbox")))) continue;
730
+ town.appendChild(tch.cloneNode(true));
731
+ }
732
+ var ticked = tli.getAttribute("data-checked") === "true";
733
+ town.insertBefore(tli.ownerDocument.createTextNode((ticked ? "\u2612" : "\u2610") + " "), town.firstChild);
734
+ town.style.marginLeft = (24 * (level + 1)) + "px";
735
+ txml += para(town, styleFrom(tli, style), { pageBreakBefore: pendingBreak });
736
+ pendingBreak = false;
737
+ for (var tj = 0; tj < tli.childNodes.length; tj++) {
738
+ var tsub = tli.childNodes[tj];
739
+ if (tsub.nodeType === 1 && /^(ul|ol)$/.test(tsub.tagName.toLowerCase())) txml += list(tsub, style, Math.min(8, level + 1), false);
740
+ }
741
+ }
742
+ return txml;
743
+ }
675
744
  var ordered = node.tagName.toLowerCase() === "ol";
676
745
  // One numId per list, so two adjacent lists restart rather than
677
746
  // continuing each other's numbering.
678
747
  var numId = ++b.numSeq;
679
748
  b.numbering[numId] = ordered ? "decimal" : "bullet";
749
+ // 2026-10-04 an <ol start> starts its level there (it was always 1, so a list continued
750
+ // after a paragraph restarted in Word). tools/docx/ordered-start.mjs
751
+ var startAt = ordered ? parseInt(node.getAttribute("start"), 10) : 1;
752
+ if (isNaN(startAt)) startAt = 1;
753
+ if (startAt !== 1) (b.numStart || (b.numStart = {}))[numId] = { level: level, start: startAt };
754
+ // 2026-10-04 a lettered / Roman list keeps its numbering style in Word (it was always decimal).
755
+ // tools/docx/list-formats.mjs
756
+ if (ordered) {
757
+ var lst = String((node.style && node.style.listStyleType) || "").toLowerCase(), typ = node.getAttribute("type") || "";
758
+ var wf = /^(lower-alpha|lower-latin)$/.test(lst) || typ === "a" ? "lowerLetter"
759
+ : /^(upper-alpha|upper-latin)$/.test(lst) || typ === "A" ? "upperLetter"
760
+ : lst === "lower-roman" || typ === "i" ? "lowerRoman"
761
+ : lst === "upper-roman" || typ === "I" ? "upperRoman" : "";
762
+ if (wf) (b.numFmtAt || (b.numFmtAt = {}))[numId] = { level: level, fmt: wf };
763
+ }
680
764
  var xml = "";
681
765
  for (var i = 0; i < node.childNodes.length; i++) {
682
766
  var li = node.childNodes[i];
@@ -703,14 +787,43 @@ function RTE_Plugin_DocxExport() {
703
787
  return xml;
704
788
  }
705
789
 
790
+ // 2026-10-15 the table's width and its column widths: a 30% / 70% layout came back as equal columns and a
791
+ // full-width table as shrink-to-fit. <col> widths (% of a 6.5in text column, or px) become <w:gridCol> twips;
792
+ // a % table width becomes <w:tblW w:type="pct"> (fiftieths of a percent). tools/docx/table-widths.mjs
793
+ var TEXT_TWIPS = 9360;
794
+ function tableGrid(node) {
795
+ var cols = [], kids = node.children;
796
+ for (var i = 0; i < kids.length; i++) if (kids[i].tagName && kids[i].tagName.toLowerCase() === "colgroup") {
797
+ for (var j = 0; j < kids[i].children.length; j++) if (kids[i].children[j].tagName.toLowerCase() === "col") cols.push(kids[i].children[j]);
798
+ }
799
+ if (!cols.length) return "";
800
+ var tw = [];
801
+ for (var k = 0; k < cols.length; k++) {
802
+ var w = (cols[k].style && cols[k].style.width) || cols[k].getAttribute("width") || "", m;
803
+ if ((m = /^\s*([\d.]+)%\s*$/.exec(w))) tw.push(Math.round(parseFloat(m[1]) / 100 * TEXT_TWIPS));
804
+ else if ((m = /^\s*([\d.]+)(px)?\s*$/.exec(w))) tw.push(Math.round(parseFloat(m[1]) * 15));
805
+ else return ""; // one column without a usable width: leave the layout to Word
806
+ }
807
+ return "<w:tblGrid>" + tw.map(function (t) { return '<w:gridCol w:w="' + t + '"/>'; }).join("") + "</w:tblGrid>";
808
+ }
809
+ function tableWidth(node) {
810
+ var m = /^\s*([\d.]+)%\s*$/.exec((node.style && node.style.width) || node.getAttribute("width") || "");
811
+ return m ? '<w:tblW w:w="' + Math.round(parseFloat(m[1]) * 50) + '" w:type="pct"/>' : '<w:tblW w:w="0" w:type="auto"/>';
812
+ }
706
813
  function table(node, style) {
707
- var xml = '<w:tbl><w:tblPr><w:tblW w:w="0" w:type="auto"/>' +
814
+ var xml = '<w:tbl><w:tblPr>' + tableWidth(node) +
708
815
  '<w:tblBorders>' +
709
816
  ["top", "left", "bottom", "right", "insideH", "insideV"].map(function (s) {
710
817
  return '<w:' + s + ' w:val="single" w:sz="4" w:space="0" w:color="BFBFBF"/>';
711
818
  }).join("") +
712
- "</w:tblBorders></w:tblPr>";
819
+ "</w:tblBorders></w:tblPr>" + tableGrid(node);
713
820
  var trs = node.getElementsByTagName("tr");
821
+ // 2026-10-15 rowspan: Word marks the first cell <w:vMerge w:val="restart"/> and puts an empty <w:vMerge/>
822
+ // continuation cell at the same grid column in every row it covers. It was not written at all, so a
823
+ // merged cell came back split. down[col] = { left: rows still covered, span: its colspan }.
824
+ // tools/docx/merged-cells.mjs
825
+ var down = {};
826
+ function continuation(col) { var d = down[col]; d.left--; var s = d.span; if (!d.left) delete down[col]; return '<w:tc><w:tcPr>' + (s > 1 ? '<w:gridSpan w:val="' + s + '"/>' : '') + '<w:vMerge/></w:tcPr><w:p/></w:tc>'; }
714
827
  for (var r = 0; r < trs.length; r++) {
715
828
  var cells = [];
716
829
  for (var c = 0; c < trs[r].childNodes.length; c++) {
@@ -724,8 +837,12 @@ function RTE_Plugin_DocxExport() {
724
837
  // or the continuation arrives as unlabelled numbers.
725
838
  var isHeader = cells.length && cells[0].header;
726
839
  xml += "<w:tr>" + (isHeader ? "<w:trPr><w:tblHeader/></w:trPr>" : "");
840
+ var gcol = 0;
727
841
  for (var k = 0; k < cells.length; k++) {
842
+ while (down[gcol]) { var dspan = down[gcol].span; xml += continuation(gcol); gcol += dspan; }
728
843
  var span = parseInt(cells[k].node.getAttribute("colspan") || "1", 10) || 1;
844
+ var rspan = parseInt(cells[k].node.getAttribute("rowspan") || "1", 10) || 1;
845
+ if (rspan > 1) down[gcol] = { left: rspan - 1, span: span };
729
846
  var cs = styleFrom(cells[k].node, style);
730
847
  if (cells[k].header) cs.bold = true;
731
848
  var body = "";
@@ -738,9 +855,13 @@ function RTE_Plugin_DocxExport() {
738
855
  if (!body) body = "<w:p/>";
739
856
  xml += "<w:tc><w:tcPr>" +
740
857
  (span > 1 ? '<w:gridSpan w:val="' + span + '"/>' : "") +
858
+ (rspan > 1 ? '<w:vMerge w:val="restart"/>' : "") +
741
859
  (cells[k].header ? '<w:shd w:val="clear" w:color="auto" w:fill="F1F3F7"/>' : "") +
742
860
  "</w:tcPr>" + body + "</w:tc>";
861
+ gcol += span;
743
862
  }
863
+ // merges from above that reach past this row's last cell
864
+ for (var tail = gcol; tail < gcol + 64; tail++) { if (down[tail]) { var tspan = down[tail].span; xml += continuation(tail); tail += tspan - 1; } }
744
865
  xml += "</w:tr>";
745
866
  }
746
867
  return xml + "</w:tbl><w:p/>";
@@ -761,11 +882,34 @@ function RTE_Plugin_DocxExport() {
761
882
 
762
883
  if (c.getAttribute("data-rte-page-break") !== null || /always|page/.test((c.style && (c.style.pageBreakBefore || c.style.breakBefore)) || "")) {
763
884
  pendingBreak = true;
885
+ // 2026-10-15 the editor's marker is only a marker: its label ("Insert Page Break") was written
886
+ // into the Word document as a paragraph of text. tools/docx/page-breaks.mjs
887
+ if (c.getAttribute("data-rte-page-break") !== null) continue;
764
888
  }
765
889
 
766
890
  if (tag === "hr") { xml += '<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:space="1" w:color="C8CDD4"/></w:pBdr></w:pPr></w:p>'; continue; }
767
891
  if (tag === "ul" || tag === "ol") { xml += list(c, style, 0, pendingBreak); pendingBreak = false; continue; }
768
- if (tag === "table") { xml += table(c, style); continue; }
892
+ // 2026-10-04 captions are Word captions: a table's above it (it was dropped), an image's in its own
893
+ // paragraph after the picture (it ran on in the picture's). tools/docx/captions.mjs
894
+ if (tag === "table") {
895
+ var tcap = c.caption; // its OWN caption, not a nested table's (2026-10-05 review)
896
+ if (tcap && (tcap.textContent || "").replace(/\s+/g, "")) xml += para(tcap, styleFrom(tcap, style), { caption: true });
897
+ xml += table(c, style); continue;
898
+ }
899
+ var ownFcap = null;
900
+ if (tag === "figure") for (var fci = 0; fci < c.children.length; fci++) if (c.children[fci].tagName.toLowerCase() === "figcaption") { ownFcap = c.children[fci]; break; }
901
+ if (tag === "figure" && ownFcap) {
902
+ var fcap = ownFcap;
903
+ // the caption is taken out for the picture's paragraph and put back (the image export
904
+ // keys on the original <img>, so a clone lost the picture)
905
+ var fcapParent = fcap.parentNode, fcapNext = fcap.nextSibling;
906
+ fcapParent.removeChild(fcap);
907
+ try { xml += para(c, styleFrom(c, style), { pageBreakBefore: pendingBreak }); }
908
+ finally { fcapParent.insertBefore(fcap, fcapNext); }
909
+ pendingBreak = false;
910
+ if ((fcap.textContent || "").replace(/\s+/g, "")) xml += para(fcap, styleFrom(fcap, style), { caption: true });
911
+ continue;
912
+ }
769
913
  if (HEADING[tag]) { xml += para(c, styleFrom(c, style), { heading: HEADING[tag], pageBreakBefore: pendingBreak }); pendingBreak = false; continue; }
770
914
  if (tag === "blockquote") { xml += para(c, styleFrom(c, style), { quote: true, pageBreakBefore: pendingBreak }); pendingBreak = false; continue; }
771
915
  if (tag === "pre") { xml += para(c, styleFrom(c, style), { pre: true, pageBreakBefore: pendingBreak }); pendingBreak = false; continue; }
@@ -822,16 +966,39 @@ function RTE_Plugin_DocxExport() {
822
966
  '" w:header="720" w:footer="720" w:gutter="0"/></w:sectPr>';
823
967
  }
824
968
 
969
+ // 2026-10-04 The copy's <img> elements are NEW and have not loaded: exported straight after setHTMLCode()
970
+ // an image without width/height had naturalWidth 0 and was silently dropped. The copy is made first and
971
+ // its images are waited for (load or error, 5 s at most). tools/docx/unsized-image.mjs
825
972
  function buildDocx(options) {
973
+ // 2026-10-05 a failure here (getHTMLCode, the copy) rejects the promise instead of throwing (review)
974
+ try { return buildDocxPrepared(options); } catch (e) { return Promise.reject(e); }
975
+ }
976
+ function buildDocxPrepared(options) {
977
+ var host = document.createElement("div");
978
+ // getHTMLCode() is the serialized document: presentational
979
+ // plugins strip their own chrome from it, so page overlays,
980
+ // watermarks and formatting marks never reach the file.
981
+ host.innerHTML = editor.getHTMLCode() || "";
982
+ // Resolve class-based list styling in the editing document, before the
983
+ // export copy leaves the stylesheet's scope. Do not change saved HTML.
984
+ var liveLists = editor.getEditable().querySelectorAll("ol,ul"), exportLists = host.querySelectorAll("ol,ul");
985
+ if (liveLists.length === exportLists.length) for (var li = 0; li < liveLists.length; li++) {
986
+ var listStyle = liveLists[li].ownerDocument.defaultView.getComputedStyle(liveLists[li]).listStyleType;
987
+ if (listStyle) exportLists[li].style.listStyleType = listStyle;
988
+ }
989
+ document.body.appendChild(host);
990
+ host.style.cssText = "position:absolute;left:-99999px;top:0;width:800px;";
991
+ var pending = [].slice.call(host.querySelectorAll("img")).filter(function (im) { return !(im.complete && im.naturalWidth); });
992
+ var ready = !pending.length ? Promise.resolve() : Promise.all(pending.map(function (im) {
993
+ return new Promise(function (done) {
994
+ im.addEventListener("load", done); im.addEventListener("error", done); setTimeout(done, 5000);
995
+ });
996
+ }));
997
+ return ready.then(function () { return buildFromHost(host, options); });
998
+ }
999
+ function buildFromHost(host, options) {
826
1000
  return new Promise(function (resolve, reject) {
827
1001
  try {
828
- var host = document.createElement("div");
829
- // getHTMLCode() is the serialized document: presentational
830
- // plugins strip their own chrome from it, so page overlays,
831
- // watermarks and formatting marks never reach the file.
832
- host.innerHTML = editor.getHTMLCode() || "";
833
- document.body.appendChild(host);
834
- host.style.cssText = "position:absolute;left:-99999px;top:0;width:800px;";
835
1002
 
836
1003
  var b = new Builder();
837
1004
  var body = blocksXml(host, b);
@@ -916,7 +1083,11 @@ function RTE_Plugin_DocxExport() {
916
1083
  }
917
1084
 
918
1085
  zip(files).then(resolve, reject);
919
- } catch (e) { reject(e); }
1086
+ } catch (e) {
1087
+ // 2026-10-05 the hidden copy never stays in the page (review)
1088
+ if (host && host.parentNode) host.parentNode.removeChild(host);
1089
+ reject(e);
1090
+ }
920
1091
  });
921
1092
  }
922
1093
 
@@ -936,6 +1107,7 @@ function RTE_Plugin_DocxExport() {
936
1107
  heads +
937
1108
  '<w:style w:type="paragraph" w:styleId="Quote"><w:name w:val="Quote"/><w:basedOn w:val="Normal"/><w:rPr><w:i/><w:color w:val="4A5568"/></w:rPr></w:style>' +
938
1109
  '<w:style w:type="paragraph" w:styleId="Code"><w:name w:val="Code"/><w:basedOn w:val="Normal"/><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/></w:rPr></w:style>' +
1110
+ '<w:style w:type="paragraph" w:styleId="Caption"><w:name w:val="caption"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:rPr><w:i/><w:color w:val="44546A"/><w:sz w:val="18"/><w:szCs w:val="18"/></w:rPr></w:style>' +
939
1111
  "</w:styles>";
940
1112
  }
941
1113
 
@@ -949,8 +1121,8 @@ function RTE_Plugin_DocxExport() {
949
1121
  // Word distinguishes bullets from numbers ONLY here; the
950
1122
  // document body stores every list identically.
951
1123
  levels += '<w:lvl w:ilvl="' + lvl + '">' +
952
- '<w:start w:val="1"/>' +
953
- '<w:numFmt w:val="' + (fmt === "bullet" ? "bullet" : "decimal") + '"/>' +
1124
+ '<w:start w:val="' + (b.numStart && b.numStart[numId] && b.numStart[numId].level === lvl ? b.numStart[numId].start : 1) + '"/>' +
1125
+ '<w:numFmt w:val="' + (fmt === "bullet" ? "bullet" : (b.numFmtAt && b.numFmtAt[numId] && b.numFmtAt[numId].level === lvl ? b.numFmtAt[numId].fmt : "decimal")) + '"/>' +
954
1126
  '<w:lvlText w:val="' + (fmt === "bullet" ? "\uF0B7" : "%" + (lvl + 1) + ".") + '"/>' +
955
1127
  '<w:lvlJc w:val="left"/>' +
956
1128
  '<w:pPr><w:ind w:left="' + (720 * (lvl + 1)) + '" w:hanging="360"/></w:pPr>' +