@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
@@ -39,34 +39,34 @@ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
39
39
  RTE_DefaultConfig.plugin_textpartlanguage = RTE_Plugin_TextPartLanguage;
40
40
 
41
41
  // The dropdown list. `dir` is only needed for right-to-left languages.
42
- if (typeof RTE_DefaultConfig.textPartLanguages === "undefined") {
43
- RTE_DefaultConfig.textPartLanguages = [
44
- { code: "ar", label: "Arabic", dir: "rtl" },
45
- { code: "zh-Hans", label: "Chinese (Simplified)" },
46
- { code: "zh-Hant", label: "Chinese (Traditional)" },
47
- { code: "cs", label: "Czech" },
48
- { code: "nl", label: "Dutch" },
49
- { code: "en", label: "English" },
50
- { code: "fr", label: "French" },
51
- { code: "de", label: "German" },
52
- { code: "el", label: "Greek" },
53
- { code: "he", label: "Hebrew", dir: "rtl" },
54
- { code: "hi", label: "Hindi" },
55
- { code: "it", label: "Italian" },
56
- { code: "ja", label: "Japanese" },
57
- { code: "ko", label: "Korean" },
58
- { code: "fa", label: "Persian", dir: "rtl" },
59
- { code: "pl", label: "Polish" },
60
- { code: "pt", label: "Portuguese" },
61
- { code: "ru", label: "Russian" },
62
- { code: "es", label: "Spanish" },
63
- { code: "sv", label: "Swedish" },
64
- { code: "tr", label: "Turkish" },
65
- { code: "uk", label: "Ukrainian" },
66
- { code: "ur", label: "Urdu", dir: "rtl" },
67
- { code: "vi", label: "Vietnamese" }
68
- ];
69
- }
42
+ // Kept by name so the menu can tell a built-in entry (named in the page's language) from the application's own label.
43
+ var RTE_TextPartLanguageDefaults = [
44
+ { code: "ar", label: "Arabic", dir: "rtl" },
45
+ { code: "zh-Hans", label: "Chinese (Simplified)" },
46
+ { code: "zh-Hant", label: "Chinese (Traditional)" },
47
+ { code: "cs", label: "Czech" },
48
+ { code: "nl", label: "Dutch" },
49
+ { code: "en", label: "English" },
50
+ { code: "fr", label: "French" },
51
+ { code: "de", label: "German" },
52
+ { code: "el", label: "Greek" },
53
+ { code: "he", label: "Hebrew", dir: "rtl" },
54
+ { code: "hi", label: "Hindi" },
55
+ { code: "it", label: "Italian" },
56
+ { code: "ja", label: "Japanese" },
57
+ { code: "ko", label: "Korean" },
58
+ { code: "fa", label: "Persian", dir: "rtl" },
59
+ { code: "pl", label: "Polish" },
60
+ { code: "pt", label: "Portuguese" },
61
+ { code: "ru", label: "Russian" },
62
+ { code: "es", label: "Spanish" },
63
+ { code: "sv", label: "Swedish" },
64
+ { code: "tr", label: "Turkish" },
65
+ { code: "uk", label: "Ukrainian" },
66
+ { code: "ur", label: "Urdu", dir: "rtl" },
67
+ { code: "vi", label: "Vietnamese" }
68
+ ];
69
+ if (typeof RTE_DefaultConfig.textPartLanguages === "undefined") RTE_DefaultConfig.textPartLanguages = RTE_TextPartLanguageDefaults.slice();
70
70
 
71
71
  // Show marked phrases with a dotted underline while editing. Editor-only: the
72
72
  // rule lives in the editing document, never in the saved HTML.
@@ -81,6 +81,14 @@ function RTE_Plugin_TextPartLanguage() {
81
81
 
82
82
  obj.InitConfig = function (argconfig) { config = argconfig; };
83
83
 
84
+ // record the change as its own undo step: seal the step before, run, save the snapshot, seal it
85
+ function undoStep(run) {
86
+ try { if (editor.commitBookmark) editor.commitBookmark(); } catch (er) { /* ignore */ }
87
+ var result = run();
88
+ try { if (editor.recordUndo) editor.recordUndo(); else if (editor.notifySelectionChange) editor.notifySelectionChange(); if (editor.commitBookmark) editor.commitBookmark(); } catch (er) { /* ignore */ }
89
+ return result;
90
+ }
91
+
84
92
  obj.InitEditor = function (argeditor) {
85
93
  editor = argeditor;
86
94
 
@@ -103,8 +111,9 @@ function RTE_Plugin_TextPartLanguage() {
103
111
  setTimeout(setup, 0);
104
112
 
105
113
  // Public API.
106
- editor.setTextLanguage = function (code) { return obj.Apply(code); };
107
- editor.removeTextLanguage = function () { return obj.Remove(); };
114
+ editor.setTextLanguage = function (code) { return undoStep(function () { return obj.Apply(code); }); };
115
+ // 2026-10-04 one undo step when called directly (it could not be undone). tools/api/plugin-api-undo.mjs
116
+ editor.removeTextLanguage = function () { return undoStep(function () { return obj.Remove(); }); };
108
117
  editor.getTextLanguage = function () { return obj.Current(); };
109
118
  editor.listTextLanguages = function () { return languages().slice(); };
110
119
  editor.getMarkedLanguages = function () { return obj.List(); };
@@ -124,6 +133,30 @@ function RTE_Plugin_TextPartLanguage() {
124
133
  return { code: code, label: code };
125
134
  }
126
135
 
136
+ // 2026-10-18 The menu names languages in the page's language: a built-in entry is named by Intl.DisplayNames in
137
+ // the language pack's locale (text_uilocale); a label the application set is kept as written; an entry with no
138
+ // label (it showed "undefined") gets the English name, else its code. tools/i18n/text-language-names.mjs
139
+ var displayNames = {};
140
+ function nameIn(locale, code) {
141
+ try {
142
+ if (typeof Intl === "undefined" || !Intl.DisplayNames) return "";
143
+ if (!displayNames[locale]) displayNames[locale] = new Intl.DisplayNames([locale], { type: "language" });
144
+ var n = displayNames[locale].of(code);
145
+ return n && n !== code ? n : "";
146
+ } catch (e) { return ""; }
147
+ }
148
+ function displayLabel(item) {
149
+ var code = item && item.code;
150
+ if (!code) return "";
151
+ var builtIn = null;
152
+ var defs = typeof RTE_TextPartLanguageDefaults !== "undefined" ? RTE_TextPartLanguageDefaults : [];
153
+ for (var i = 0; i < defs.length; i++) if (defs[i].code === code) builtIn = defs[i].label;
154
+ if (item.label && item.label !== builtIn) return item.label;
155
+ var locale = config && config.text_uilocale;
156
+ if (locale && !/^en(-|$)/i.test(locale)) { var local = nameIn(locale, code); if (local) return local; }
157
+ return item.label || nameIn("en", code) || code;
158
+ }
159
+
127
160
  // ---- state -----------------------------------------------------------
128
161
 
129
162
  // The nearest ancestor carrying a lang, bounded by the editable so the host
@@ -182,6 +215,8 @@ function RTE_Plugin_TextPartLanguage() {
182
215
  var doc = getDoc();
183
216
  var r = selectionRange();
184
217
  if (!doc || !r) return false;
218
+ // 2026-10-14 Suggesting mode: a language change is not tracked yet, so the track hook refuses it. tools/review/suggesting-structure-audit.mjs
219
+ if (editor.__trackHook && editor.__trackHook("refuse", { message: "Suggesting is on, so the language was not changed. Turn suggesting off to change it." }) === true) return false;
185
220
 
186
221
  // Re-picking the current language toggles it off.
187
222
  if (obj.Current() === code) return obj.Remove();
@@ -195,6 +230,43 @@ function RTE_Plugin_TextPartLanguage() {
195
230
  return code;
196
231
  }
197
232
 
233
+ // 2026-10-02 A selection that crosses blocks marks the selected text of EACH block. One
234
+ // span went AROUND the blocks: two paragraphs became four
235
+ // (<p>al</p><span lang="fr"><p>pha</p><p>be</p></span><p>ta</p>), list items and table cells
236
+ // got a span straight inside <ul> / <tr> and were split, a heading was split in two (with its
237
+ // id twice) - and removing the language left them split. CKEditor marks each block's text.
238
+ // tools/format/text-part-language.mjs
239
+ var runs = crossBlockRuns(doc, r);
240
+ if (runs) {
241
+ var spans = [];
242
+ // from the last run back: wrapping one never moves the text of a run still to come
243
+ for (var ri = runs.length - 1; ri >= 0; ri--) {
244
+ var sub = doc.createRange();
245
+ sub.setStart(runs[ri].sn, runs[ri].so);
246
+ sub.setEnd(runs[ri].en, runs[ri].eo);
247
+ widenToInlineEdges(sub, runs[ri].block);
248
+ var part = doc.createElement("span");
249
+ stamp(part, code);
250
+ part.appendChild(sub.extractContents());
251
+ sub.insertNode(part);
252
+ unwrapInner(part);
253
+ pruneEmptyText(part);
254
+ if (!part.firstChild) { part.parentNode.removeChild(part); continue; }
255
+ spans.unshift(mergeSiblings(part));
256
+ }
257
+ if (!spans.length) return false;
258
+ try {
259
+ var all = doc.createRange();
260
+ all.setStart(spans[0], 0);
261
+ all.setEnd(spans[spans.length - 1], spans[spans.length - 1].childNodes.length);
262
+ var sel = editor.getSelection();
263
+ sel.removeAllRanges();
264
+ sel.addRange(all);
265
+ } catch (e) {}
266
+ fireChange();
267
+ return code;
268
+ }
269
+
198
270
  var span = doc.createElement("span");
199
271
  stamp(span, code);
200
272
 
@@ -222,6 +294,8 @@ function RTE_Plugin_TextPartLanguage() {
222
294
  };
223
295
 
224
296
  obj.Remove = function () {
297
+ // 2026-10-14 Suggesting mode: removing this is not tracked yet, so the track hook refuses it. tools/review/suggesting-structure-audit.mjs
298
+ if (editor.__trackHook && editor.__trackHook("remove", {}) === true) return false;
225
299
  var r = selectionRange();
226
300
  if (!r) return false;
227
301
  var ed = getEditable();
@@ -256,6 +330,59 @@ function RTE_Plugin_TextPartLanguage() {
256
330
  } catch (e) { return false; }
257
331
  }
258
332
 
333
+ // The runs of selected text that share a block, when the selection crosses blocks; null when it
334
+ // starts and ends in one block (that keeps the single-span path). Whitespace-only runs (the line
335
+ // break between two blocks) are dropped; text inside a non-editable element moves with its run.
336
+ var BLOCK_RE = /^(P|DIV|LI|UL|OL|DL|DT|DD|TABLE|TBODY|THEAD|TFOOT|TR|TD|TH|CAPTION|H[1-6]|PRE|BLOCKQUOTE|ADDRESS|FIGURE|FIGCAPTION|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|NAV|MAIN|DETAILS|SUMMARY|FORM|FIELDSET|LEGEND|HR|BODY)$/;
337
+ function blockOf(node) {
338
+ for (var n = node; n; n = n.parentNode)
339
+ if (n.nodeType === 1 && BLOCK_RE.test(n.nodeName)) return n;
340
+ return null;
341
+ }
342
+ function crossBlockRuns(doc, range) {
343
+ if (range.collapsed || blockOf(range.startContainer) === blockOf(range.endContainer)) return null;
344
+ var root = range.commonAncestorContainer;
345
+ if (root.nodeType !== 1) root = root.parentNode;
346
+ var runs = [], cur = null;
347
+ var w = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {
348
+ acceptNode: function (n) { return n.nodeType === 1 && n.getAttribute("contenteditable") === "false" ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT; }
349
+ });
350
+ for (var n = w.nextNode(); n; n = w.nextNode()) {
351
+ if (n.nodeType === 1) {
352
+ if (BLOCK_RE.test(n.nodeName)) cur = null;
353
+ continue;
354
+ }
355
+ if (!range.intersectsNode(n)) continue;
356
+ var a = n === range.startContainer ? range.startOffset : 0, b = n === range.endContainer ? range.endOffset : n.data.length;
357
+ if (b <= a) continue;
358
+ var blk = blockOf(n);
359
+ if (!cur || cur.block !== blk) {
360
+ cur = { block: blk, sn: n, so: a, text: "" };
361
+ runs.push(cur);
362
+ }
363
+ cur.en = n;
364
+ cur.eo = b;
365
+ cur.text += n.data.substring(a, b);
366
+ }
367
+ var keep = [];
368
+ for (var i = 0; i < runs.length; i++) if (/\S/.test(runs[i].text)) keep.push(runs[i]);
369
+ return keep;
370
+ }
371
+ // A run starting or ending at the edge of an inline element (<b>) takes the whole element, so
372
+ // no empty <b></b> is left behind by extractContents.
373
+ function widenToInlineEdges(range, block) {
374
+ var n = range.startContainer;
375
+ if (n.nodeType === 3 && range.startOffset === 0) {
376
+ while (n.parentNode && n.parentNode !== block && !n.previousSibling && !BLOCK_RE.test(n.parentNode.nodeName)) n = n.parentNode;
377
+ range.setStartBefore(n);
378
+ }
379
+ n = range.endContainer;
380
+ if (n.nodeType === 3 && range.endOffset === n.data.length) {
381
+ while (n.parentNode && n.parentNode !== block && !n.nextSibling && !BLOCK_RE.test(n.parentNode.nodeName)) n = n.parentNode;
382
+ range.setEndAfter(n);
383
+ }
384
+ }
385
+
259
386
  function stamp(el, code) {
260
387
  el.setAttribute("lang", code);
261
388
  var dir = entry(code).dir;
@@ -350,7 +477,7 @@ function RTE_Plugin_TextPartLanguage() {
350
477
 
351
478
  for (var i = 0; i < list.length; i++) {
352
479
  (function (item) {
353
- addRow(panel, item.label, item.code, current === item.code, function () { obj.Apply(item.code); });
480
+ addRow(panel, displayLabel(item), item.code, current === item.code, function () { obj.Apply(item.code); });
354
481
  })(list[i]);
355
482
  }
356
483
 
@@ -397,7 +524,9 @@ function RTE_Plugin_TextPartLanguage() {
397
524
 
398
525
  if (code) {
399
526
  var tag = doc.createElement("span");
400
- tag.style.cssText = "opacity:.55;font-size:11px;";
527
+ // 2026-10-15 opacity .55 left the code at 4.06:1 in dark mode and 3.23:1 in light; .75 clears 4.5:1 in both.
528
+ // tools/a11y/dark-mode-contrast.mjs
529
+ tag.style.cssText = "opacity:.75;font-size:11px;";
401
530
  tag.appendChild(doc.createTextNode(code));
402
531
  row.appendChild(tag);
403
532
  }
@@ -1 +1 @@
1
- window.RTE_DefaultConfig||(window.RTE_DefaultConfig={}),RTE_DefaultConfig.plugin_textpartlanguage=RTE_Plugin_TextPartLanguage,typeof RTE_DefaultConfig.textPartLanguages>"u"&&(RTE_DefaultConfig.textPartLanguages=[{code:"ar",label:"Arabic",dir:"rtl"},{code:"zh-Hans",label:"Chinese (Simplified)"},{code:"zh-Hant",label:"Chinese (Traditional)"},{code:"cs",label:"Czech"},{code:"nl",label:"Dutch"},{code:"en",label:"English"},{code:"fr",label:"French"},{code:"de",label:"German"},{code:"el",label:"Greek"},{code:"he",label:"Hebrew",dir:"rtl"},{code:"hi",label:"Hindi"},{code:"it",label:"Italian"},{code:"ja",label:"Japanese"},{code:"ko",label:"Korean"},{code:"fa",label:"Persian",dir:"rtl"},{code:"pl",label:"Polish"},{code:"pt",label:"Portuguese"},{code:"ru",label:"Russian"},{code:"es",label:"Spanish"},{code:"sv",label:"Swedish"},{code:"tr",label:"Turkish"},{code:"uk",label:"Ukrainian"},{code:"ur",label:"Urdu",dir:"rtl"},{code:"vi",label:"Vietnamese"}]),typeof RTE_DefaultConfig.textPartLanguageHighlight>"u"&&(RTE_DefaultConfig.textPartLanguageHighlight=!0);function RTE_Plugin_TextPartLanguage(){var u=this,g,a,T=null;u.PluginName="TextPartLanguage",u.InitConfig=function(e){g=e},u.InitEditor=function(e){a=e,a.attachEvent("exec_command_textpartlanguage",function(r){r.returnValue=!0;var t=r&&r.value;!t||t==="none"||t==="remove"?u.Remove():u.Apply(t)}),a.toolbarFactoryMap&&(a.toolbarFactoryMap.textpartlanguage=function(r){return a.createToolbarItemDropDownPanel(r,q)}),h();try{a.attachEvent("ready",h)}catch{}try{a.attachEvent("aftersethtml",h)}catch{}setTimeout(h,0),a.setTextLanguage=function(r){return u.Apply(r)},a.removeTextLanguage=function(){return u.Remove()},a.getTextLanguage=function(){return u.Current()},a.listTextLanguages=function(){return y().slice()},a.getMarkedLanguages=function(){return u.List()}};function w(){try{return a.getDocument()}catch{return null}}function b(){try{return a.getEditable()}catch{return null}}function y(){var e=g&&g.textPartLanguages;return e&&e.length?e:[]}function E(e){for(var r=y(),t=0;t<r.length;t++)if(r[t].code===e)return r[t];return{code:e,label:e}}function s(e){for(var r=b(),t=e&&e.nodeType===3?e.parentNode:e;t&&t!==r&&t.nodeType===1;){if(t.hasAttribute&&t.hasAttribute("lang"))return t;t=t.parentNode}return null}function m(){try{var e=a.getSelection();return!e||e.rangeCount===0?null:e.getRangeAt(0)}catch{return null}}u.Current=function(){var e=m();if(!e)return null;var r=s(e.startContainer);return r&&!e.collapsed&&s(e.endContainer)!==r?null:r?r.getAttribute("lang"):null},u.List=function(){var e=b(),r=[];if(!e)return r;for(var t={},n=e.querySelectorAll("[lang]"),i=0;i<n.length;i++){var o=n[i].getAttribute("lang");!o||t[o]||(t[o]=!0,r.push({code:o,label:E(o).label}))}return r},u.Apply=function(e){if(e=String(e??"").trim(),!e)return!1;var r=w(),t=m();if(!r||!t)return!1;if(u.Current()===e)return u.Remove();if(t.collapsed){var n=s(t.startContainer);return n?(N(n,e),x(),e):!1}var i=r.createElement("span");N(i,e);try{t.surroundContents(i)}catch{var o=t.extractContents();i.appendChild(o),t.insertNode(i)}return L(i),k(i),i.firstChild?(_(i),H(i),x(),e):(i.parentNode&&i.parentNode.removeChild(i),!1)},u.Remove=function(){var e=m();if(!e)return!1;var r=b();if(!r)return!1;var t=0,n=s(e.startContainer);if(n&&(C(n),t++),!e.collapsed)for(var i=r.querySelectorAll("[lang]"),o=i.length-1;o>=0;o--){var l=i[o];l!==n&&S(e,l)&&(C(l),t++)}return t&&x(),t>0};function S(e,r){try{var t=r.ownerDocument.createRange();return t.selectNodeContents(r),e.compareBoundaryPoints(Range.END_TO_START,t)<0&&e.compareBoundaryPoints(Range.START_TO_END,t)>0}catch{return!1}}function N(e,r){e.setAttribute("lang",r);var t=E(r).dir;t==="rtl"||t==="ltr"?e.setAttribute("dir",t):e.removeAttribute("dir")}function L(e){for(var r=e.querySelectorAll("[lang]"),t=r.length-1;t>=0;t--)C(r[t])}function C(e){if(!(!e||!e.parentNode))if(e.nodeName==="SPAN"&&e.attributes.length<=2&&!e.getAttribute("class")&&!e.getAttribute("style")&&!e.getAttribute("id")){for(var r=e.parentNode;e.firstChild;)r.insertBefore(e.firstChild,e);r.removeChild(e);try{r.normalize()}catch{}}else e.removeAttribute("lang"),e.removeAttribute("dir")}function k(e){for(var r=e.childNodes.length-1;r>=0;r--){var t=e.childNodes[r];t.nodeType===3&&t.nodeValue===""&&e.removeChild(t)}}function _(e){var r=e.getAttribute("lang"),t=e.previousSibling;if(t&&t.nodeType===1&&t.nodeName==="SPAN"&&t.getAttribute("lang")===r){for(;e.firstChild;)t.appendChild(e.firstChild);e.parentNode.removeChild(e),e=t}var n=e.nextSibling;if(n&&n.nodeType===1&&n.nodeName==="SPAN"&&n.getAttribute("lang")===r){for(;n.firstChild;)e.appendChild(n.firstChild);n.parentNode.removeChild(n)}try{e.normalize()}catch{}return e}function H(e){try{var r=a.getSelection(),t=e.ownerDocument.createRange();t.selectNodeContents(e),r.removeAllRanges(),r.addRange(t)}catch{}}function x(){try{typeof a.updateDesign=="function"&&a.updateDesign()}catch{}try{typeof a.fireChange=="function"&&a.fireChange()}catch{}}function q(e){var r=u.Current();e.style.width="220px",e.style.maxHeight="340px",e.style.overflowY="auto",e.style.padding="4px 0";var t=y();D(e,a.getLangText&&a.getLangText("removelanguage")||"Remove language",null,!r,function(){u.Remove()});var n=e.ownerDocument.createElement("div");n.style.cssText="height:1px;margin:4px 6px;background:var(--rte-border-color,#e0e0e0);",e.appendChild(n);for(var i=0;i<t.length;i++)(function(o){D(e,o.label,o.code,r===o.code,function(){u.Apply(o.code)})})(t[i]);setTimeout(function(){var o=e.querySelectorAll("rte-toolbar-dropdown-item");if(o.length){for(var l=o[0],d=0;d<o.length;d++)if(o[d].getAttribute("aria-checked")==="true"){l=o[d];break}try{l.focus()}catch{}}},0)}function D(e,r,t,n,i){var o=e.ownerDocument,l=o.createElement("rte-toolbar-dropdown-item");e.appendChild(l),l.setAttribute("role","menuitemradio"),l.setAttribute("aria-checked",n?"true":"false"),l.setAttribute("tabindex","0"),l.style.cssText="padding:5px 12px;cursor:pointer;white-space:nowrap;display:flex;justify-content:space-between;gap:12px;color:var(--rte-text-color,inherit);"+(n?"font-weight:600;":"");var d=o.createElement("span");if(d.appendChild(o.createTextNode(r)),l.appendChild(d),t){var p=o.createElement("span");p.style.cssText="opacity:.55;font-size:11px;",p.appendChild(o.createTextNode(t)),l.appendChild(p)}l.onmouseover=function(){l.style.backgroundColor="var(--rte-hover-bg,#e6e6e6)"},l.onmouseout=function(){l.style.backgroundColor=""},l.onclick=function(){try{a.closeCurrentPopup()}catch{}try{a.focus()}catch{}i()},l.onkeydown=function(c){var f=c.key;if(f==="Enter"||f===" "||f==="Spacebar"){c.preventDefault(),c.stopPropagation(),l.onclick();return}if(f==="ArrowDown"||f==="ArrowUp"){c.preventDefault(),c.stopPropagation();var B=e.querySelectorAll("rte-toolbar-dropdown-item"),v=Array.prototype.slice.call(B),j=v.indexOf(l),R=v[(j+(f==="ArrowDown"?1:-1)+v.length)%v.length];R&&R.focus();return}if(f==="Home"||f==="End"){c.preventDefault(),c.stopPropagation();var A=e.querySelectorAll("rte-toolbar-dropdown-item"),P=f==="Home"?A[0]:A[A.length-1];P&&P.focus();return}if(f==="Escape"){c.preventDefault(),c.stopPropagation();try{a.closeCurrentPopup()}catch{}}}}function h(){var e=w();!e||e===T||(T=e,I(e))}function z(){return g&&g.textPartLanguageHighlight===!1?"":"[lang]:not(html):not(body){border-bottom:1px dotted rgba(120,120,120,.75);}[lang]:not(html):not(body):hover{background:rgba(120,120,120,.10);}"}function I(e){if(e){var r=z(),t=e.getElementById("rte-textpartlanguage-styles");if(t){if(t.getAttribute("data-css")===r)return;t.parentNode&&t.parentNode.removeChild(t)}if(r){var n=e.createElement("style");n.id="rte-textpartlanguage-styles",n.setAttribute("data-css",r),n.appendChild(e.createTextNode(r)),(e.head||e.getElementsByTagName("head")[0]||e.documentElement).appendChild(n),a&&typeof a.ensureStyleSheetLive=="function"&&a.ensureStyleSheetLive(e,n,"rte-textpartlanguage-styles",n.textContent)}}}}
1
+ window.RTE_DefaultConfig||(window.RTE_DefaultConfig={}),RTE_DefaultConfig.plugin_textpartlanguage=RTE_Plugin_TextPartLanguage;var RTE_TextPartLanguageDefaults=[{code:"ar",label:"Arabic",dir:"rtl"},{code:"zh-Hans",label:"Chinese (Simplified)"},{code:"zh-Hant",label:"Chinese (Traditional)"},{code:"cs",label:"Czech"},{code:"nl",label:"Dutch"},{code:"en",label:"English"},{code:"fr",label:"French"},{code:"de",label:"German"},{code:"el",label:"Greek"},{code:"he",label:"Hebrew",dir:"rtl"},{code:"hi",label:"Hindi"},{code:"it",label:"Italian"},{code:"ja",label:"Japanese"},{code:"ko",label:"Korean"},{code:"fa",label:"Persian",dir:"rtl"},{code:"pl",label:"Polish"},{code:"pt",label:"Portuguese"},{code:"ru",label:"Russian"},{code:"es",label:"Spanish"},{code:"sv",label:"Swedish"},{code:"tr",label:"Turkish"},{code:"uk",label:"Ukrainian"},{code:"ur",label:"Urdu",dir:"rtl"},{code:"vi",label:"Vietnamese"}];typeof RTE_DefaultConfig.textPartLanguages>"u"&&(RTE_DefaultConfig.textPartLanguages=RTE_TextPartLanguageDefaults.slice()),typeof RTE_DefaultConfig.textPartLanguageHighlight>"u"&&(RTE_DefaultConfig.textPartLanguageHighlight=!0);function RTE_Plugin_TextPartLanguage(){var c=this,h,o,w=null;c.PluginName="TextPartLanguage",c.InitConfig=function(e){h=e};function k(e){try{o.commitBookmark&&o.commitBookmark()}catch{}var r=e();try{o.recordUndo?o.recordUndo():o.notifySelectionChange&&o.notifySelectionChange(),o.commitBookmark&&o.commitBookmark()}catch{}return r}c.InitEditor=function(e){o=e,o.attachEvent("exec_command_textpartlanguage",function(r){r.returnValue=!0;var t=r&&r.value;!t||t==="none"||t==="remove"?c.Remove():c.Apply(t)}),o.toolbarFactoryMap&&(o.toolbarFactoryMap.textpartlanguage=function(r){return o.createToolbarItemDropDownPanel(r,j)}),b();try{o.attachEvent("ready",b)}catch{}try{o.attachEvent("aftersethtml",b)}catch{}setTimeout(b,0),o.setTextLanguage=function(r){return k(function(){return c.Apply(r)})},o.removeTextLanguage=function(){return k(function(){return c.Remove()})},o.getTextLanguage=function(){return c.Current()},o.listTextLanguages=function(){return T().slice()},o.getMarkedLanguages=function(){return c.List()}};function L(){try{return o.getDocument()}catch{return null}}function C(){try{return o.getEditable()}catch{return null}}function T(){var e=h&&h.textPartLanguages;return e&&e.length?e:[]}function P(e){for(var r=T(),t=0;t<r.length;t++)if(r[t].code===e)return r[t];return{code:e,label:e}}var E={};function _(e,r){try{if(typeof Intl>"u"||!Intl.DisplayNames)return"";E[e]||(E[e]=new Intl.DisplayNames([e],{type:"language"}));var t=E[e].of(r);return t&&t!==r?t:""}catch{return""}}function M(e){var r=e&&e.code;if(!r)return"";for(var t=null,a=typeof RTE_TextPartLanguageDefaults<"u"?RTE_TextPartLanguageDefaults:[],l=0;l<a.length;l++)a[l].code===r&&(t=a[l].label);if(e.label&&e.label!==t)return e.label;var i=h&&h.text_uilocale;if(i&&!/^en(-|$)/i.test(i)){var n=_(i,r);if(n)return n}return e.label||_("en",r)||r}function p(e){for(var r=C(),t=e&&e.nodeType===3?e.parentNode:e;t&&t!==r&&t.nodeType===1;){if(t.hasAttribute&&t.hasAttribute("lang"))return t;t=t.parentNode}return null}function N(){try{var e=o.getSelection();return!e||e.rangeCount===0?null:e.getRangeAt(0)}catch{return null}}c.Current=function(){var e=N();if(!e)return null;var r=p(e.startContainer);return r&&!e.collapsed&&p(e.endContainer)!==r?null:r?r.getAttribute("lang"):null},c.List=function(){var e=C(),r=[];if(!e)return r;for(var t={},a=e.querySelectorAll("[lang]"),l=0;l<a.length;l++){var i=a[l].getAttribute("lang");!i||t[i]||(t[i]=!0,r.push({code:i,label:P(i).label}))}return r},c.Apply=function(e){if(e=String(e??"").trim(),!e)return!1;var r=L(),t=N();if(!r||!t||o.__trackHook&&o.__trackHook("refuse",{message:"Suggesting is on, so the language was not changed. Turn suggesting off to change it."})===!0)return!1;if(c.Current()===e)return c.Remove();if(t.collapsed){var a=p(t.startContainer);return a?(x(a,e),y(),e):!1}var l=z(r,t);if(l){for(var i=[],n=l.length-1;n>=0;n--){var s=r.createRange();s.setStart(l[n].sn,l[n].so),s.setEnd(l[n].en,l[n].eo),G(s,l[n].block);var f=r.createElement("span");if(x(f,e),f.appendChild(s.extractContents()),s.insertNode(f),I(f),O(f),!f.firstChild){f.parentNode.removeChild(f);continue}i.unshift(H(f))}if(!i.length)return!1;try{var d=r.createRange();d.setStart(i[0],0),d.setEnd(i[i.length-1],i[i.length-1].childNodes.length);var g=o.getSelection();g.removeAllRanges(),g.addRange(d)}catch{}return y(),e}var u=r.createElement("span");x(u,e);try{t.surroundContents(u)}catch{var v=t.extractContents();u.appendChild(v),t.insertNode(u)}return I(u),O(u),u.firstChild?(H(u),V(u),y(),e):(u.parentNode&&u.parentNode.removeChild(u),!1)},c.Remove=function(){if(o.__trackHook&&o.__trackHook("remove",{})===!0)return!1;var e=N();if(!e)return!1;var r=C();if(!r)return!1;var t=0,a=p(e.startContainer);if(a&&(R(a),t++),!e.collapsed)for(var l=r.querySelectorAll("[lang]"),i=l.length-1;i>=0;i--){var n=l[i];n!==a&&q(e,n)&&(R(n),t++)}return t&&y(),t>0};function q(e,r){try{var t=r.ownerDocument.createRange();return t.selectNodeContents(r),e.compareBoundaryPoints(Range.END_TO_START,t)<0&&e.compareBoundaryPoints(Range.START_TO_END,t)>0}catch{return!1}}var m=/^(P|DIV|LI|UL|OL|DL|DT|DD|TABLE|TBODY|THEAD|TFOOT|TR|TD|TH|CAPTION|H[1-6]|PRE|BLOCKQUOTE|ADDRESS|FIGURE|FIGCAPTION|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|NAV|MAIN|DETAILS|SUMMARY|FORM|FIELDSET|LEGEND|HR|BODY)$/;function A(e){for(var r=e;r;r=r.parentNode)if(r.nodeType===1&&m.test(r.nodeName))return r;return null}function z(e,r){if(r.collapsed||A(r.startContainer)===A(r.endContainer))return null;var t=r.commonAncestorContainer;t.nodeType!==1&&(t=t.parentNode);for(var a=[],l=null,i=e.createTreeWalker(t,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,{acceptNode:function(v){return v.nodeType===1&&v.getAttribute("contenteditable")==="false"?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),n=i.nextNode();n;n=i.nextNode()){if(n.nodeType===1){m.test(n.nodeName)&&(l=null);continue}if(r.intersectsNode(n)){var s=n===r.startContainer?r.startOffset:0,f=n===r.endContainer?r.endOffset:n.data.length;if(!(f<=s)){var d=A(n);(!l||l.block!==d)&&(l={block:d,sn:n,so:s,text:""},a.push(l)),l.en=n,l.eo=f,l.text+=n.data.substring(s,f)}}}for(var g=[],u=0;u<a.length;u++)/\S/.test(a[u].text)&&g.push(a[u]);return g}function G(e,r){var t=e.startContainer;if(t.nodeType===3&&e.startOffset===0){for(;t.parentNode&&t.parentNode!==r&&!t.previousSibling&&!m.test(t.parentNode.nodeName);)t=t.parentNode;e.setStartBefore(t)}if(t=e.endContainer,t.nodeType===3&&e.endOffset===t.data.length){for(;t.parentNode&&t.parentNode!==r&&!t.nextSibling&&!m.test(t.parentNode.nodeName);)t=t.parentNode;e.setEndAfter(t)}}function x(e,r){e.setAttribute("lang",r);var t=P(r).dir;t==="rtl"||t==="ltr"?e.setAttribute("dir",t):e.removeAttribute("dir")}function I(e){for(var r=e.querySelectorAll("[lang]"),t=r.length-1;t>=0;t--)R(r[t])}function R(e){if(!(!e||!e.parentNode))if(e.nodeName==="SPAN"&&e.attributes.length<=2&&!e.getAttribute("class")&&!e.getAttribute("style")&&!e.getAttribute("id")){for(var r=e.parentNode;e.firstChild;)r.insertBefore(e.firstChild,e);r.removeChild(e);try{r.normalize()}catch{}}else e.removeAttribute("lang"),e.removeAttribute("dir")}function O(e){for(var r=e.childNodes.length-1;r>=0;r--){var t=e.childNodes[r];t.nodeType===3&&t.nodeValue===""&&e.removeChild(t)}}function H(e){var r=e.getAttribute("lang"),t=e.previousSibling;if(t&&t.nodeType===1&&t.nodeName==="SPAN"&&t.getAttribute("lang")===r){for(;e.firstChild;)t.appendChild(e.firstChild);e.parentNode.removeChild(e),e=t}var a=e.nextSibling;if(a&&a.nodeType===1&&a.nodeName==="SPAN"&&a.getAttribute("lang")===r){for(;a.firstChild;)e.appendChild(a.firstChild);a.parentNode.removeChild(a)}try{e.normalize()}catch{}return e}function V(e){try{var r=o.getSelection(),t=e.ownerDocument.createRange();t.selectNodeContents(e),r.removeAllRanges(),r.addRange(t)}catch{}}function y(){try{typeof o.updateDesign=="function"&&o.updateDesign()}catch{}try{typeof o.fireChange=="function"&&o.fireChange()}catch{}}function j(e){var r=c.Current();e.style.width="220px",e.style.maxHeight="340px",e.style.overflowY="auto",e.style.padding="4px 0";var t=T();B(e,o.getLangText&&o.getLangText("removelanguage")||"Remove language",null,!r,function(){c.Remove()});var a=e.ownerDocument.createElement("div");a.style.cssText="height:1px;margin:4px 6px;background:var(--rte-border-color,#e0e0e0);",e.appendChild(a);for(var l=0;l<t.length;l++)(function(i){B(e,M(i),i.code,r===i.code,function(){c.Apply(i.code)})})(t[l]);setTimeout(function(){var i=e.querySelectorAll("rte-toolbar-dropdown-item");if(i.length){for(var n=i[0],s=0;s<i.length;s++)if(i[s].getAttribute("aria-checked")==="true"){n=i[s];break}try{n.focus()}catch{}}},0)}function B(e,r,t,a,l){var i=e.ownerDocument,n=i.createElement("rte-toolbar-dropdown-item");e.appendChild(n),n.setAttribute("role","menuitemradio"),n.setAttribute("aria-checked",a?"true":"false"),n.setAttribute("tabindex","0"),n.style.cssText="padding:5px 12px;cursor:pointer;white-space:nowrap;display:flex;justify-content:space-between;gap:12px;color:var(--rte-text-color,inherit);"+(a?"font-weight:600;":"");var s=i.createElement("span");if(s.appendChild(i.createTextNode(r)),n.appendChild(s),t){var f=i.createElement("span");f.style.cssText="opacity:.75;font-size:11px;",f.appendChild(i.createTextNode(t)),n.appendChild(f)}n.onmouseover=function(){n.style.backgroundColor="var(--rte-hover-bg,#e6e6e6)"},n.onmouseout=function(){n.style.backgroundColor=""},n.onclick=function(){try{o.closeCurrentPopup()}catch{}try{o.focus()}catch{}l()},n.onkeydown=function(d){var g=d.key;if(g==="Enter"||g===" "||g==="Spacebar"){d.preventDefault(),d.stopPropagation(),n.onclick();return}if(g==="ArrowDown"||g==="ArrowUp"){d.preventDefault(),d.stopPropagation();var u=e.querySelectorAll("rte-toolbar-dropdown-item"),v=Array.prototype.slice.call(u),D=v.indexOf(n),F=v[(D+(g==="ArrowDown"?1:-1)+v.length)%v.length];F&&F.focus();return}if(g==="Home"||g==="End"){d.preventDefault(),d.stopPropagation();var S=e.querySelectorAll("rte-toolbar-dropdown-item"),U=g==="Home"?S[0]:S[S.length-1];U&&U.focus();return}if(g==="Escape"){d.preventDefault(),d.stopPropagation();try{o.closeCurrentPopup()}catch{}}}}function b(){var e=L();!e||e===w||(w=e,K(e))}function Y(){return h&&h.textPartLanguageHighlight===!1?"":"[lang]:not(html):not(body){border-bottom:1px dotted rgba(120,120,120,.75);}[lang]:not(html):not(body):hover{background:rgba(120,120,120,.10);}"}function K(e){if(e){var r=Y(),t=e.getElementById("rte-textpartlanguage-styles");if(t){if(t.getAttribute("data-css")===r)return;t.parentNode&&t.parentNode.removeChild(t)}if(r){var a=e.createElement("style");a.id="rte-textpartlanguage-styles",a.setAttribute("data-css",r),a.appendChild(e.createTextNode(r)),(e.head||e.getElementsByTagName("head")[0]||e.documentElement).appendChild(a),o&&typeof o.ensureStyleSheetLive=="function"&&o.ensureStyleSheetLive(e,a,"rte-textpartlanguage-styles",a.textContent)}}}}
@@ -37,13 +37,18 @@ function RTE_Plugin_TodoList() {
37
37
  e.preventDefault();
38
38
  fireChange();
39
39
  });
40
- // Keep new items (created by Enter) well-formed: a checkbox + an unchecked state.
40
+ // 2026-10-02 Enter in a task item is handled here. The browser's own Enter made a bare <li>
41
+ // and the keyup below then put the checkbox in FRONT of the caret: in Chromium and Firefox
42
+ // every item after the first saved as <li>two<span class="rte-task-checkbox"></span></li>,
43
+ // Firefox sometimes dropped the typed text altogether, and WebKit put the caret back at
44
+ // the end of the previous item. tools/keyboard/todolist-enter.mjs
45
+ editable.addEventListener("keydown", function (e) {
46
+ if (e.key !== "Enter" || e.shiftKey || e.ctrlKey || e.metaKey || e.altKey || e.isComposing || e.keyCode === 229) return;
47
+ try { if (splitTaskItem()) e.preventDefault(); } catch (er) { /* the browser's own Enter */ }
48
+ }, true);
49
+ // Keep items made any other way well-formed: a checkbox + a done state.
41
50
  editable.addEventListener("keyup", function (e) {
42
- if (e.key === "Enter" && !e.shiftKey) {
43
- obj.Normalize();
44
- var li = currentTaskItem();
45
- if (li) li.setAttribute("data-checked", "false"); // a freshly split/created item starts open
46
- }
51
+ if (e.key === "Enter" && !e.shiftKey) obj.Normalize();
47
52
  });
48
53
  // Inject CSS once the iframe head exists.
49
54
  setTimeout(function () { injectStyles(editdoc); }, 0);
@@ -99,6 +104,85 @@ function RTE_Plugin_TodoList() {
99
104
  return cb;
100
105
  }
101
106
 
107
+ function isEmptyItem(li) {
108
+ return !/[^\s\u200b]/.test(li.textContent || "") && !li.querySelector("img,iframe,video,audio,svg,object,embed,table");
109
+ }
110
+ // The caret's place in an item that holds only its checkbox: a zero-width text node after
111
+ // it (as InsertTaskList does) - next to a contenteditable="false" span alone, WebKit moves
112
+ // the caret back into the previous item.
113
+ function caretAfterCheckbox(li) {
114
+ var zw = editdoc.createTextNode("\u200b");
115
+ var cb = li.firstChild && li.firstChild.nodeType === 1 && li.firstChild.classList && li.firstChild.classList.contains("rte-task-checkbox") ? li.firstChild : null;
116
+ li.insertBefore(zw, cb ? cb.nextSibling : li.firstChild);
117
+ return zw;
118
+ }
119
+ function placeCaret(node, offset) {
120
+ var r = editdoc.createRange();
121
+ r.setStart(node, offset);
122
+ r.collapse(true);
123
+ var s = editor.getSelection();
124
+ s.removeAllRanges();
125
+ s.addRange(r);
126
+ }
127
+ function newItem() {
128
+ var li = editdoc.createElement("li");
129
+ li.className = "rte-task-item";
130
+ li.setAttribute("data-checked", "false"); // a new item starts open
131
+ li.appendChild(makeCheckbox());
132
+ return li;
133
+ }
134
+ // Enter in a task item: an empty item leaves the list (a paragraph takes its place, the list
135
+ // splits around it); at the start of an item a new open item goes above; otherwise the text
136
+ // after the caret moves into a new open item below, checkbox first, caret before that text.
137
+ function splitTaskItem() {
138
+ var s = editor.getSelection();
139
+ if (!s || s.rangeCount === 0) return false;
140
+ var r = s.getRangeAt(0);
141
+ var li = closestClass(r.startContainer, "rte-task-item", editable);
142
+ if (!li || li !== closestClass(r.endContainer, "rte-task-item", editable) || li.parentNode.nodeName !== "UL") return false;
143
+ if (typeof editor.commitBookmark === "function") { try { editor.commitBookmark(); } catch (er) { /* ignore */ } }
144
+ if (!r.collapsed) r.deleteContents();
145
+ var ul = li.parentNode;
146
+ if (isEmptyItem(li)) {
147
+ var tag = String((config && config.enterKeyTag) || "p").toLowerCase();
148
+ if (tag === "br") tag = "div";
149
+ var p = editdoc.createElement(tag);
150
+ p.appendChild(editdoc.createElement("br"));
151
+ if (li.nextSibling) {
152
+ var rest = ul.cloneNode(false);
153
+ while (li.nextSibling) rest.appendChild(li.nextSibling);
154
+ ul.parentNode.insertBefore(rest, ul.nextSibling);
155
+ }
156
+ ul.parentNode.insertBefore(p, ul.nextSibling);
157
+ ul.removeChild(li);
158
+ if (!ul.querySelector("li")) ul.parentNode.removeChild(ul);
159
+ placeCaret(p, 0);
160
+ fireChange();
161
+ return true;
162
+ }
163
+ var head = editdoc.createRange();
164
+ head.setStart(li, 0);
165
+ head.setEnd(r.startContainer, r.startOffset);
166
+ if (!/[^\s\u200b]/.test(head.toString()) && !head.cloneContents().querySelector("img,iframe,video,audio,svg,object,embed")) {
167
+ var above = newItem();
168
+ caretAfterCheckbox(above);
169
+ ul.insertBefore(above, li);
170
+ fireChange();
171
+ return true;
172
+ }
173
+ var tail = editdoc.createRange();
174
+ tail.setStart(r.startContainer, r.startOffset);
175
+ tail.setEnd(li, li.childNodes.length);
176
+ var moved = tail.extractContents();
177
+ var below = newItem();
178
+ below.appendChild(moved);
179
+ var zw = caretAfterCheckbox(below);
180
+ ul.insertBefore(below, li.nextSibling);
181
+ placeCaret(zw, 1);
182
+ fireChange();
183
+ return true;
184
+ }
185
+
102
186
  // Ensure every <li> in a task list has a checkbox span + a data-checked value.
103
187
  obj.Normalize = function () {
104
188
  if (!editable) return;
@@ -1 +1 @@
1
- window.RTE_DefaultConfig||(window.RTE_DefaultConfig={}),RTE_DefaultConfig.plugin_todolist=RTE_Plugin_TodoList;function RTE_Plugin_TodoList(){var c=this,h,n,s,l;c.PluginName="TodoList",c.InitConfig=function(t){h=t},c.InitEditor=function(t){n=t,s=n.getEditable(),l=n.getDocument(),n.attachEvent("exec_command_inserttodolist",function(r){r.returnValue=!0,c.InsertTaskList()}),s&&(s.addEventListener("click",function(r){var e=d(r.target,"rte-task-checkbox",s);if(e){var i=e.parentNode;if(!(!i||!i.classList||!i.classList.contains("rte-task-item"))){var a=i.getAttribute("data-checked")==="true";i.setAttribute("data-checked",a?"false":"true"),r.preventDefault(),u()}}}),s.addEventListener("keyup",function(r){if(r.key==="Enter"&&!r.shiftKey){c.Normalize();var e=k();e&&e.setAttribute("data-checked","false")}}),setTimeout(function(){f(l)},0))};function d(t,r,e){for(;t&&t!==e;){if(t.nodeType===1&&t.classList&&t.classList.contains(r))return t;t=t.parentNode}return null}function u(){if(typeof n.fireChange=="function")try{n.fireChange()}catch{}}function k(){try{var t=n.getSelection();if(!t||t.rangeCount===0)return null;var r=t.getRangeAt(0).startContainer;return d(r,"rte-task-item",s)}catch{return null}}function f(t){if(!(!t||t.getElementById("rte-todolist-styles"))){var r="ul.rte-task-list{list-style:none;margin:.4em 0;padding-left:0;}ul.rte-task-list li.rte-task-item{position:relative;padding-left:28px;margin:3px 0;min-height:20px;}li.rte-task-item>.rte-task-checkbox{position:absolute;left:0;top:1px;width:18px;height:18px;border:2px solid #94a3b8;border-radius:5px;box-sizing:border-box;cursor:pointer;user-select:none;transition:background .12s,border-color .12s;}li.rte-task-item>.rte-task-checkbox:hover{border-color:#1d67ba;}li.rte-task-item[data-checked='true']>.rte-task-checkbox{background:#1d67ba;border-color:#1d67ba;}li.rte-task-item[data-checked='true']>.rte-task-checkbox::after{content:'';position:absolute;left:4px;top:0px;width:6px;height:11px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);}li.rte-task-item[data-checked='true']{color:#94a3b8;text-decoration:line-through;}",e=t.createElement("style");e.id="rte-todolist-styles",e.appendChild(t.createTextNode(r)),(t.head||t.getElementsByTagName("head")[0]||t.documentElement).appendChild(e),n&&typeof n.ensureStyleSheetLive=="function"&&n.ensureStyleSheetLive(t,e,"rte-todolist-styles",e.textContent)}}function g(){var t=l.createElement("span");return t.className="rte-task-checkbox",t.setAttribute("contenteditable","false"),t}c.Normalize=function(){if(s)for(var t=s.querySelectorAll("ul.rte-task-list"),r=0;r<t.length;r++)for(var e=t[r].children,i=0;i<e.length;i++){var a=e[i];if(a.nodeName==="LI"){a.classList.add("rte-task-item"),a.getAttribute("data-checked")!=="true"&&a.getAttribute("data-checked")!=="false"&&a.setAttribute("data-checked","false");var o=a.firstChild;o&&o.nodeType===1&&o.classList&&o.classList.contains("rte-task-checkbox")||a.insertBefore(g(),a.firstChild)}}},c.InsertTaskList=function(){f(l);var t="rte-todo-pending",r='<ul class="rte-task-list"><li class="rte-task-item" data-checked="false" id="'+t+'"><span class="rte-task-checkbox" contenteditable="false"></span>&#8203;</li></ul>';n.insertHTML(r);try{var e=l.getElementById(t);if(e){e.removeAttribute("id");var i=l.createRange(),a=e.lastChild;a&&a.nodeType===3?i.setStart(a,a.length):i.setStart(e,e.childNodes.length),i.collapse(!0);var o=n.getSelection();o&&(o.removeAllRanges(),o.addRange(i))}}catch{}n.focus(),u()}}
1
+ window.RTE_DefaultConfig||(window.RTE_DefaultConfig={}),RTE_DefaultConfig.plugin_todolist=RTE_Plugin_TodoList;function RTE_Plugin_TodoList(){var c=this,h,i,l,s;c.PluginName="TodoList",c.InitConfig=function(e){h=e},c.InitEditor=function(e){i=e,l=i.getEditable(),s=i.getDocument(),i.attachEvent("exec_command_inserttodolist",function(r){r.returnValue=!0,c.InsertTaskList()}),l&&(l.addEventListener("click",function(r){var t=u(r.target,"rte-task-checkbox",l);if(t){var a=t.parentNode;if(!(!a||!a.classList||!a.classList.contains("rte-task-item"))){var n=a.getAttribute("data-checked")==="true";a.setAttribute("data-checked",n?"false":"true"),r.preventDefault(),d()}}}),l.addEventListener("keydown",function(r){if(!(r.key!=="Enter"||r.shiftKey||r.ctrlKey||r.metaKey||r.altKey||r.isComposing||r.keyCode===229))try{E()&&r.preventDefault()}catch{}},!0),l.addEventListener("keyup",function(r){r.key==="Enter"&&!r.shiftKey&&c.Normalize()}),setTimeout(function(){b(s)},0))};function u(e,r,t){for(;e&&e!==t;){if(e.nodeType===1&&e.classList&&e.classList.contains(r))return e;e=e.parentNode}return null}function d(){if(typeof i.fireChange=="function")try{i.fireChange()}catch{}}function N(){try{var e=i.getSelection();if(!e||e.rangeCount===0)return null;var r=e.getRangeAt(0).startContainer;return u(r,"rte-task-item",l)}catch{return null}}function b(e){if(!(!e||e.getElementById("rte-todolist-styles"))){var r="ul.rte-task-list{list-style:none;margin:.4em 0;padding-left:0;}ul.rte-task-list li.rte-task-item{position:relative;padding-left:28px;margin:3px 0;min-height:20px;}li.rte-task-item>.rte-task-checkbox{position:absolute;left:0;top:1px;width:18px;height:18px;border:2px solid #94a3b8;border-radius:5px;box-sizing:border-box;cursor:pointer;user-select:none;transition:background .12s,border-color .12s;}li.rte-task-item>.rte-task-checkbox:hover{border-color:#1d67ba;}li.rte-task-item[data-checked='true']>.rte-task-checkbox{background:#1d67ba;border-color:#1d67ba;}li.rte-task-item[data-checked='true']>.rte-task-checkbox::after{content:'';position:absolute;left:4px;top:0px;width:6px;height:11px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);}li.rte-task-item[data-checked='true']{color:#94a3b8;text-decoration:line-through;}",t=e.createElement("style");t.id="rte-todolist-styles",t.appendChild(e.createTextNode(r)),(e.head||e.getElementsByTagName("head")[0]||e.documentElement).appendChild(t),i&&typeof i.ensureStyleSheetLive=="function"&&i.ensureStyleSheetLive(e,t,"rte-todolist-styles",t.textContent)}}function p(){var e=s.createElement("span");return e.className="rte-task-checkbox",e.setAttribute("contenteditable","false"),e}function S(e){return!/[^\s\u200b]/.test(e.textContent||"")&&!e.querySelector("img,iframe,video,audio,svg,object,embed,table")}function v(e){var r=s.createTextNode("\u200B"),t=e.firstChild&&e.firstChild.nodeType===1&&e.firstChild.classList&&e.firstChild.classList.contains("rte-task-checkbox")?e.firstChild:null;return e.insertBefore(r,t?t.nextSibling:e.firstChild),r}function C(e,r){var t=s.createRange();t.setStart(e,r),t.collapse(!0);var a=i.getSelection();a.removeAllRanges(),a.addRange(t)}function y(){var e=s.createElement("li");return e.className="rte-task-item",e.setAttribute("data-checked","false"),e.appendChild(p()),e}function E(){var e=i.getSelection();if(!e||e.rangeCount===0)return!1;var r=e.getRangeAt(0),t=u(r.startContainer,"rte-task-item",l);if(!t||t!==u(r.endContainer,"rte-task-item",l)||t.parentNode.nodeName!=="UL")return!1;if(typeof i.commitBookmark=="function")try{i.commitBookmark()}catch{}r.collapsed||r.deleteContents();var a=t.parentNode;if(S(t)){var n=String(h&&h.enterKeyTag||"p").toLowerCase();n==="br"&&(n="div");var o=s.createElement(n);if(o.appendChild(s.createElement("br")),t.nextSibling){for(var g=a.cloneNode(!1);t.nextSibling;)g.appendChild(t.nextSibling);a.parentNode.insertBefore(g,a.nextSibling)}return a.parentNode.insertBefore(o,a.nextSibling),a.removeChild(t),a.querySelector("li")||a.parentNode.removeChild(a),C(o,0),d(),!0}var f=s.createRange();if(f.setStart(t,0),f.setEnd(r.startContainer,r.startOffset),!/[^\s\u200b]/.test(f.toString())&&!f.cloneContents().querySelector("img,iframe,video,audio,svg,object,embed")){var x=y();return v(x),a.insertBefore(x,t),d(),!0}var m=s.createRange();m.setStart(r.startContainer,r.startOffset),m.setEnd(t,t.childNodes.length);var L=m.extractContents(),k=y();k.appendChild(L);var T=v(k);return a.insertBefore(k,t.nextSibling),C(T,1),d(),!0}c.Normalize=function(){if(l)for(var e=l.querySelectorAll("ul.rte-task-list"),r=0;r<e.length;r++)for(var t=e[r].children,a=0;a<t.length;a++){var n=t[a];if(n.nodeName==="LI"){n.classList.add("rte-task-item"),n.getAttribute("data-checked")!=="true"&&n.getAttribute("data-checked")!=="false"&&n.setAttribute("data-checked","false");var o=n.firstChild;o&&o.nodeType===1&&o.classList&&o.classList.contains("rte-task-checkbox")||n.insertBefore(p(),n.firstChild)}}},c.InsertTaskList=function(){b(s);var e="rte-todo-pending",r='<ul class="rte-task-list"><li class="rte-task-item" data-checked="false" id="'+e+'"><span class="rte-task-checkbox" contenteditable="false"></span>&#8203;</li></ul>';i.insertHTML(r);try{var t=s.getElementById(e);if(t){t.removeAttribute("id");var a=s.createRange(),n=t.lastChild;n&&n.nodeType===3?a.setStart(n,n.length):a.setStart(t,t.childNodes.length),a.collapse(!0);var o=i.getSelection();o&&(o.removeAllRanges(),o.addRange(a))}}catch{}i.focus(),d()}}