@richscripts/richtexteditor 2.4.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 (184) hide show
  1. package/README.md +360 -0
  2. package/admin/index.d.ts +100 -0
  3. package/admin/index.js +94 -0
  4. package/angular/component.ts +284 -0
  5. package/angular/index.d.ts +80 -0
  6. package/angular/index.ts +24 -0
  7. package/headless/index.d.ts +65 -0
  8. package/headless/index.js +272 -0
  9. package/index.d.ts +1412 -0
  10. package/integrations/shared.d.ts +138 -0
  11. package/integrations/shared.js +5182 -0
  12. package/package.json +130 -0
  13. package/react/index.d.ts +104 -0
  14. package/react/index.js +310 -0
  15. package/richtexteditor/CHANGELOG.md +1876 -0
  16. package/richtexteditor/all_plugins.js +31125 -0
  17. package/richtexteditor/all_plugins.min.js +78 -0
  18. package/richtexteditor/lang/rte-lang-af.js +193 -0
  19. package/richtexteditor/lang/rte-lang-ar.js +193 -0
  20. package/richtexteditor/lang/rte-lang-bg.js +193 -0
  21. package/richtexteditor/lang/rte-lang-bn.js +193 -0
  22. package/richtexteditor/lang/rte-lang-bs.js +193 -0
  23. package/richtexteditor/lang/rte-lang-ca.js +193 -0
  24. package/richtexteditor/lang/rte-lang-cs.js +193 -0
  25. package/richtexteditor/lang/rte-lang-cy.js +193 -0
  26. package/richtexteditor/lang/rte-lang-da.js +193 -0
  27. package/richtexteditor/lang/rte-lang-de.js +193 -0
  28. package/richtexteditor/lang/rte-lang-el.js +193 -0
  29. package/richtexteditor/lang/rte-lang-en.js +6 -0
  30. package/richtexteditor/lang/rte-lang-es.js +193 -0
  31. package/richtexteditor/lang/rte-lang-et.js +193 -0
  32. package/richtexteditor/lang/rte-lang-fa.js +193 -0
  33. package/richtexteditor/lang/rte-lang-fi.js +193 -0
  34. package/richtexteditor/lang/rte-lang-fil.js +193 -0
  35. package/richtexteditor/lang/rte-lang-fj.js +193 -0
  36. package/richtexteditor/lang/rte-lang-fr.js +193 -0
  37. package/richtexteditor/lang/rte-lang-ga.js +193 -0
  38. package/richtexteditor/lang/rte-lang-gu.js +193 -0
  39. package/richtexteditor/lang/rte-lang-he.js +193 -0
  40. package/richtexteditor/lang/rte-lang-hi.js +193 -0
  41. package/richtexteditor/lang/rte-lang-hr.js +193 -0
  42. package/richtexteditor/lang/rte-lang-ht.js +193 -0
  43. package/richtexteditor/lang/rte-lang-hu.js +193 -0
  44. package/richtexteditor/lang/rte-lang-id.js +193 -0
  45. package/richtexteditor/lang/rte-lang-is.js +193 -0
  46. package/richtexteditor/lang/rte-lang-it.js +193 -0
  47. package/richtexteditor/lang/rte-lang-ja.js +193 -0
  48. package/richtexteditor/lang/rte-lang-kn.js +193 -0
  49. package/richtexteditor/lang/rte-lang-ko.js +193 -0
  50. package/richtexteditor/lang/rte-lang-lt.js +193 -0
  51. package/richtexteditor/lang/rte-lang-lv.js +193 -0
  52. package/richtexteditor/lang/rte-lang-mg.js +193 -0
  53. package/richtexteditor/lang/rte-lang-mi.js +193 -0
  54. package/richtexteditor/lang/rte-lang-ml.js +193 -0
  55. package/richtexteditor/lang/rte-lang-mr.js +193 -0
  56. package/richtexteditor/lang/rte-lang-ms.js +193 -0
  57. package/richtexteditor/lang/rte-lang-mt.js +193 -0
  58. package/richtexteditor/lang/rte-lang-mww.js +193 -0
  59. package/richtexteditor/lang/rte-lang-nb.js +193 -0
  60. package/richtexteditor/lang/rte-lang-nl.js +193 -0
  61. package/richtexteditor/lang/rte-lang-otq.js +193 -0
  62. package/richtexteditor/lang/rte-lang-pa.js +193 -0
  63. package/richtexteditor/lang/rte-lang-pl.js +193 -0
  64. package/richtexteditor/lang/rte-lang-pt-pt.js +193 -0
  65. package/richtexteditor/lang/rte-lang-pt.js +193 -0
  66. package/richtexteditor/lang/rte-lang-ro.js +193 -0
  67. package/richtexteditor/lang/rte-lang-ru.js +193 -0
  68. package/richtexteditor/lang/rte-lang-sk.js +193 -0
  69. package/richtexteditor/lang/rte-lang-sl.js +193 -0
  70. package/richtexteditor/lang/rte-lang-sm.js +193 -0
  71. package/richtexteditor/lang/rte-lang-sr-Cyrl.js +193 -0
  72. package/richtexteditor/lang/rte-lang-sr-Latn.js +193 -0
  73. package/richtexteditor/lang/rte-lang-sv.js +193 -0
  74. package/richtexteditor/lang/rte-lang-sw.js +193 -0
  75. package/richtexteditor/lang/rte-lang-ta.js +193 -0
  76. package/richtexteditor/lang/rte-lang-te.js +193 -0
  77. package/richtexteditor/lang/rte-lang-th.js +193 -0
  78. package/richtexteditor/lang/rte-lang-tlh-Latn.js +193 -0
  79. package/richtexteditor/lang/rte-lang-tlh-Piqd.js +193 -0
  80. package/richtexteditor/lang/rte-lang-to.js +193 -0
  81. package/richtexteditor/lang/rte-lang-tr.js +193 -0
  82. package/richtexteditor/lang/rte-lang-ty.js +193 -0
  83. package/richtexteditor/lang/rte-lang-uk.js +193 -0
  84. package/richtexteditor/lang/rte-lang-ur.js +193 -0
  85. package/richtexteditor/lang/rte-lang-vi.js +193 -0
  86. package/richtexteditor/lang/rte-lang-yua.js +193 -0
  87. package/richtexteditor/lang/rte-lang-zh-Hans.js +193 -0
  88. package/richtexteditor/lang/rte-lang-zh-Hant.js +193 -0
  89. package/richtexteditor/lang/rte-lang.js +1 -0
  90. package/richtexteditor/plugins/-combine-plugin.txt +2 -0
  91. package/richtexteditor/plugins/-combine-plugins.bat +20 -0
  92. package/richtexteditor/plugins/a11yenhance.js +90 -0
  93. package/richtexteditor/plugins/accessibilitychecker.js +579 -0
  94. package/richtexteditor/plugins/aitoolkit.css +15830 -0
  95. package/richtexteditor/plugins/aitoolkit.js +19171 -0
  96. package/richtexteditor/plugins/all_plugins.js +31125 -0
  97. package/richtexteditor/plugins/all_plugins.min.js +78 -0
  98. package/richtexteditor/plugins/autoembed.js +174 -0
  99. package/richtexteditor/plugins/blocktypes.js +175 -0
  100. package/richtexteditor/plugins/bookmarkcard.js +226 -0
  101. package/richtexteditor/plugins/charlimit.js +195 -0
  102. package/richtexteditor/plugins/comments.js +672 -0
  103. package/richtexteditor/plugins/contentminimap.js +369 -0
  104. package/richtexteditor/plugins/crdt-engine.js +8905 -0
  105. package/richtexteditor/plugins/crdt-engine.min.js +5 -0
  106. package/richtexteditor/plugins/dialoga11y.js +174 -0
  107. package/richtexteditor/plugins/dictation.js +268 -0
  108. package/richtexteditor/plugins/documentimport.js +369 -0
  109. package/richtexteditor/plugins/documentoutline.js +341 -0
  110. package/richtexteditor/plugins/emailtoolkit.js +165 -0
  111. package/richtexteditor/plugins/emojiautocomplete.js +241 -0
  112. package/richtexteditor/plugins/foldheadings.js +181 -0
  113. package/richtexteditor/plugins/ghostcomplete.js +297 -0
  114. package/richtexteditor/plugins/html2pdf.js +158 -0
  115. package/richtexteditor/plugins/imageeditor.js +159 -0
  116. package/richtexteditor/plugins/insertcode.js +319 -0
  117. package/richtexteditor/plugins/insertemoji.js +229 -0
  118. package/richtexteditor/plugins/insertgallery.js +362 -0
  119. package/richtexteditor/plugins/inserttemplate.js +261 -0
  120. package/richtexteditor/plugins/localdraft.js +213 -0
  121. package/richtexteditor/plugins/markdownexport.js +110 -0
  122. package/richtexteditor/plugins/mathdialog.js +153 -0
  123. package/richtexteditor/plugins/mentionplugin.js +534 -0
  124. package/richtexteditor/plugins/mermaiddiagram.js +294 -0
  125. package/richtexteditor/plugins/readabilitystats.js +148 -0
  126. package/richtexteditor/plugins/readaloud.js +243 -0
  127. package/richtexteditor/plugins/restrictedediting.js +208 -0
  128. package/richtexteditor/plugins/revisionhistory.js +670 -0
  129. package/richtexteditor/plugins/samples/insertcode_v1.js +76 -0
  130. package/richtexteditor/plugins/slashcommand.js +705 -0
  131. package/richtexteditor/plugins/smartchips.js +250 -0
  132. package/richtexteditor/plugins/spellcheck.js +181 -0
  133. package/richtexteditor/plugins/todolist.js +137 -0
  134. package/richtexteditor/plugins/trackedchanges.js +658 -0
  135. package/richtexteditor/plugins/tui.image-editor/fabric.min.js +8 -0
  136. package/richtexteditor/plugins/tui.image-editor/note.txt +5 -0
  137. package/richtexteditor/plugins/tui.image-editor/svg/icon-a.svg +235 -0
  138. package/richtexteditor/plugins/tui.image-editor/svg/icon-b.svg +224 -0
  139. package/richtexteditor/plugins/tui.image-editor/svg/icon-c.svg +224 -0
  140. package/richtexteditor/plugins/tui.image-editor/svg/icon-d.svg +224 -0
  141. package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +7 -0
  142. package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.css +6 -0
  143. package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +7 -0
  144. package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.css +7 -0
  145. package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +15 -0
  146. package/richtexteditor/plugins/typewriter.js +203 -0
  147. package/richtexteditor/plugins/wordexport.js +0 -0
  148. package/richtexteditor/plugins/yjscollab.js +693 -0
  149. package/richtexteditor/plugins/zz_richtextbox_dialog_style.js +335 -0
  150. package/richtexteditor/rte-config.js +933 -0
  151. package/richtexteditor/rte.js +943 -0
  152. package/richtexteditor/rte_mobile.css +234 -0
  153. package/richtexteditor/rte_theme_default.css +4819 -0
  154. package/richtexteditor/runtime/colorpicker_more_ns.htm +2613 -0
  155. package/richtexteditor/runtime/help.htm +162 -0
  156. package/richtexteditor/runtime/images/1x1.gif +0 -0
  157. package/richtexteditor/runtime/images/cpie_Color.cur +0 -0
  158. package/richtexteditor/runtime/images/cpie_GradientPositionDark.gif +0 -0
  159. package/richtexteditor/runtime/images/cpie_GradientPositionLight.gif +0 -0
  160. package/richtexteditor/runtime/images/cpie_VerticalPosition.gif +0 -0
  161. package/richtexteditor/runtime/images/cpie_WebSafe.gif +0 -0
  162. package/richtexteditor/runtime/images/cpie_gradients.png +0 -0
  163. package/richtexteditor/runtime/images/cpns_Color.cur +0 -0
  164. package/richtexteditor/runtime/images/cpns_ColorSpace1.png +0 -0
  165. package/richtexteditor/runtime/images/cpns_ColorSpace2.png +0 -0
  166. package/richtexteditor/runtime/images/cpns_GradientPositionDark.gif +0 -0
  167. package/richtexteditor/runtime/images/cpns_GradientPositionLight.gif +0 -0
  168. package/richtexteditor/runtime/images/cpns_Vertical1.png +0 -0
  169. package/richtexteditor/runtime/images/cpns_Vertical2.png +0 -0
  170. package/richtexteditor/runtime/images/cpns_VerticalPosition.gif +0 -0
  171. package/richtexteditor/runtime/images/cpns_WebSafe.gif +0 -0
  172. package/richtexteditor/runtime/images/cpns_gradients.png +0 -0
  173. package/richtexteditor/runtime/images/cpns_hue2.png +0 -0
  174. package/richtexteditor/runtime/images/formbn.gif +0 -0
  175. package/richtexteditor/runtime/images/multiclavier.gif +0 -0
  176. package/richtexteditor/runtime/richtexteditor_content.css +362 -0
  177. package/richtexteditor/runtime/richtexteditor_preview.css +302 -0
  178. package/richtexteditor/runtime/richtexteditor_preview.js +1 -0
  179. package/server/index.d.ts +21 -0
  180. package/server/index.js +23 -0
  181. package/svelte/index.d.ts +19 -0
  182. package/svelte/index.js +59 -0
  183. package/vue/index.d.ts +94 -0
  184. package/vue/index.js +291 -0
@@ -0,0 +1,268 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ RTE_DefaultConfig.plugin_dictation = RTE_Plugin_Dictation;
4
+
5
+ if (!RTE_DefaultConfig.svgCode_dictation) {
6
+ // Solid microphone glyph, stroke-matched to the default toolbar icon set.
7
+ RTE_DefaultConfig.svgCode_dictation = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0"/><path d="M12 18v3"/><path d="M8 21h8"/></svg>';
8
+ }
9
+
10
+ /**
11
+ * Dictation plugin — converts microphone input to text via the Web Speech
12
+ * API and inserts it into the editor. Toolbar toggle button; click to start,
13
+ * click again to stop. Interim results render live under the cursor so the
14
+ * user sees what the browser heard before committing.
15
+ *
16
+ * Browser support: Chrome, Edge, Opera, Safari (via webkitSpeechRecognition).
17
+ * Firefox does not ship a SpeechRecognition implementation — we detect and
18
+ * hide the toolbar button when unsupported.
19
+ *
20
+ * Config:
21
+ * config.dictationLang — BCP-47 language, default navigator.language
22
+ * config.dictationContinuous — default true; when false, one utterance then stops
23
+ * config.dictationInterimResults — default true; show partial transcript live
24
+ * config.dictationAutoPunctuation— default true; auto-capitalize sentences + add periods
25
+ */
26
+ function RTE_Plugin_Dictation() {
27
+ var obj = this;
28
+ var config;
29
+ var editor;
30
+ var recognition = null;
31
+ var listening = false;
32
+ var button = null;
33
+ var interimNode = null;
34
+
35
+ obj.PluginName = "Dictation";
36
+
37
+ obj.InitConfig = function (argconfig) {
38
+ config = argconfig;
39
+ // Opt-in: require dictationEnabled===true (set via Tag Helper attribute
40
+ // `enable-dictation="true"` or by the host JS). The feature is opt-in
41
+ // because it requires microphone permission — we don't want a visible
42
+ // mic button on every editor that ships the plugin JS.
43
+ if (config.dictationEnabled !== true) return;
44
+ if (!isSupported()) return;
45
+
46
+ if (typeof config.dictationContinuous !== "boolean") config.dictationContinuous = true;
47
+ if (typeof config.dictationInterimResults !== "boolean") config.dictationInterimResults = true;
48
+ if (typeof config.dictationAutoPunctuation !== "boolean") config.dictationAutoPunctuation = true;
49
+ config.dictationLang = config.dictationLang || (typeof navigator !== "undefined" ? navigator.language : "en-US");
50
+
51
+ appendToolbarCommand("toolbar_default", "#{dictation}");
52
+ appendToolbarCommand("toolbar_full", "#{dictation}");
53
+ };
54
+
55
+ obj.InitEditor = function (argeditor) {
56
+ editor = argeditor;
57
+ if (config.dictationEnabled !== true) return;
58
+ if (!isSupported()) return;
59
+
60
+ editor.dictation = {
61
+ start: start,
62
+ stop: stop,
63
+ toggle: toggle,
64
+ isListening: function () { return listening; },
65
+ isSupported: function () { return true; }
66
+ };
67
+
68
+ editor.toolbarFactoryMap["dictation"] = createButton;
69
+
70
+ editor.attachEvent("exec_command_dictation", function () { toggle(); });
71
+
72
+ // Stop recognition when the editor is torn down.
73
+ editor.attachEvent("destroy", function () { if (recognition) try { recognition.abort(); } catch (e) { } });
74
+ };
75
+
76
+ function isSupported() {
77
+ return typeof window !== "undefined"
78
+ && (typeof window.SpeechRecognition === "function"
79
+ || typeof window.webkitSpeechRecognition === "function");
80
+ }
81
+
82
+ function appendToolbarCommand(key, token) {
83
+ var current = config[key] || "";
84
+ if (current.indexOf(token) !== -1) return;
85
+ config[key] = current ? current + " " + token : token;
86
+ }
87
+
88
+ function createButton(cmd) {
89
+ button = editor.createToolbarButton(cmd, {
90
+ tooltip: "Dictate (click to start/stop)",
91
+ svgcode: config.svgCode_dictation
92
+ });
93
+ return button;
94
+ }
95
+
96
+ function buildRecognition() {
97
+ var Impl = window.SpeechRecognition || window.webkitSpeechRecognition;
98
+ var r = new Impl();
99
+ r.continuous = !!config.dictationContinuous;
100
+ r.interimResults = !!config.dictationInterimResults;
101
+ r.lang = config.dictationLang;
102
+
103
+ r.onresult = function (event) {
104
+ var finalTranscript = "";
105
+ var interimTranscript = "";
106
+ for (var i = event.resultIndex; i < event.results.length; i++) {
107
+ var result = event.results[i];
108
+ if (result.isFinal) finalTranscript += result[0].transcript;
109
+ else interimTranscript += result[0].transcript;
110
+ }
111
+ if (finalTranscript) {
112
+ insertFinal(finalTranscript);
113
+ }
114
+ if (interimTranscript) {
115
+ renderInterim(interimTranscript);
116
+ } else if (finalTranscript) {
117
+ clearInterim();
118
+ }
119
+ };
120
+
121
+ r.onend = function () {
122
+ // If we're supposed to be listening (not user-stopped) restart —
123
+ // some browsers stop after a silence window even in continuous mode.
124
+ if (listening) {
125
+ try { r.start(); return; } catch (e) { /* ignore: race with user-stop */ }
126
+ }
127
+ listening = false;
128
+ updateButton();
129
+ clearInterim();
130
+ };
131
+
132
+ r.onerror = function (event) {
133
+ listening = false;
134
+ updateButton();
135
+ clearInterim();
136
+ if (event && event.error === "not-allowed") {
137
+ notify("Microphone permission denied. Enable it in your browser site settings to dictate.");
138
+ } else if (event && event.error === "no-speech") {
139
+ // benign; user didn't speak — no toast
140
+ } else if (event && event.error) {
141
+ notify("Dictation error: " + event.error);
142
+ }
143
+ };
144
+
145
+ return r;
146
+ }
147
+
148
+ function start() {
149
+ if (!isSupported() || listening) return;
150
+ if (!recognition) recognition = buildRecognition();
151
+ try { recognition.start(); }
152
+ catch (e) {
153
+ // start() throws if already started; reset and retry.
154
+ try { recognition.abort(); } catch (e2) { }
155
+ recognition = buildRecognition();
156
+ try { recognition.start(); } catch (e3) { return; }
157
+ }
158
+ listening = true;
159
+ updateButton();
160
+ }
161
+
162
+ function stop() {
163
+ if (!recognition || !listening) return;
164
+ listening = false; // set first so onend doesn't auto-restart
165
+ try { recognition.stop(); } catch (e) { /* ignore */ }
166
+ updateButton();
167
+ clearInterim();
168
+ }
169
+
170
+ function toggle() {
171
+ if (listening) stop();
172
+ else start();
173
+ }
174
+
175
+ function insertFinal(text) {
176
+ if (!text) return;
177
+ var normalized = normalize(text, true);
178
+ if (!normalized) return;
179
+ try {
180
+ if (editor.insertText) editor.insertText(normalized);
181
+ else if (editor.insertHTML) editor.insertHTML(escapeHtml(normalized));
182
+ } catch (e) { }
183
+ }
184
+
185
+ function renderInterim(text) {
186
+ if (!text || !config.dictationInterimResults) return;
187
+ var normalized = normalize(text, false);
188
+ if (!interimNode) {
189
+ var doc = editor.getDocument ? editor.getDocument() : document;
190
+ interimNode = doc.createElement("span");
191
+ interimNode.setAttribute("data-rte-dictation-interim", "true");
192
+ interimNode.style.cssText = "opacity:.55;color:#0f8b8d;font-style:italic;pointer-events:none;user-select:none;";
193
+ try {
194
+ var sel = editor.getSelection();
195
+ if (sel && sel.rangeCount > 0) {
196
+ var range = sel.getRangeAt(0);
197
+ range.collapse(true);
198
+ range.insertNode(interimNode);
199
+ }
200
+ } catch (e) { }
201
+ }
202
+ if (interimNode) interimNode.textContent = " " + normalized;
203
+ }
204
+
205
+ function clearInterim() {
206
+ if (interimNode && interimNode.parentNode) {
207
+ interimNode.parentNode.removeChild(interimNode);
208
+ }
209
+ interimNode = null;
210
+ }
211
+
212
+ function updateButton() {
213
+ if (!button) return;
214
+ button.classList.toggle("rte-dictation-on", listening);
215
+ button.setAttribute("aria-pressed", listening ? "true" : "false");
216
+ button.setAttribute("title", listening ? "Dictating… (click to stop)" : "Dictate (click to start/stop)");
217
+ ensureStyles();
218
+ }
219
+
220
+ var stylesInjected = false;
221
+ function ensureStyles() {
222
+ if (stylesInjected) return;
223
+ stylesInjected = true;
224
+ var css = ".rte-dictation-on { position: relative; background: linear-gradient(180deg,#fee2e2,#fecaca); box-shadow: inset 0 0 0 1px #f87171; }"
225
+ + ".rte-dictation-on::after { content: ''; position: absolute; top: 4px; right: 4px; width: 6px; height: 6px; border-radius: 50%; background: #dc2626; box-shadow: 0 0 0 0 rgba(220,38,38,.6); animation: rte-dictation-pulse 1.4s infinite; }"
226
+ + "@@keyframes rte-dictation-pulse { 0%{box-shadow:0 0 0 0 rgba(220,38,38,.6);} 70%{box-shadow:0 0 0 6px rgba(220,38,38,0);} 100%{box-shadow:0 0 0 0 rgba(220,38,38,0);} }";
227
+ // Note: the "@@" above is a literal pass-through for Razor safety; at
228
+ // runtime we ship with a single @ via this replace:
229
+ css = css.replace(/@@keyframes/g, "@keyframes");
230
+ var style = document.createElement("style");
231
+ style.setAttribute("data-rte-dictation", "1");
232
+ style.appendChild(document.createTextNode(css));
233
+ document.head.appendChild(style);
234
+ }
235
+
236
+ function normalize(text, isFinal) {
237
+ if (!text) return "";
238
+ var t = text.replace(/\s+/g, " ");
239
+ if (!isFinal) return t.trim();
240
+ // Final chunks: collapse leading whitespace, apply light auto-punctuation.
241
+ t = t.replace(/^\s+/, "");
242
+ if (config.dictationAutoPunctuation) {
243
+ // Capitalize first letter.
244
+ if (t.length > 0 && /[a-z]/.test(t[0])) {
245
+ t = t[0].toUpperCase() + t.substring(1);
246
+ }
247
+ // Ensure trailing space so consecutive dictations don't glue words together.
248
+ if (!/[.!?,;:]$/.test(t)) t += ".";
249
+ t += " ";
250
+ } else {
251
+ if (!/\s$/.test(t)) t += " ";
252
+ }
253
+ return t;
254
+ }
255
+
256
+ function escapeHtml(s) {
257
+ return String(s).replace(/[&<>"']/g, function (c) {
258
+ return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
259
+ });
260
+ }
261
+
262
+ function notify(msg) {
263
+ try {
264
+ if (editor && editor.showAlert) { editor.showAlert(msg); return; }
265
+ } catch (e) { }
266
+ if (typeof window !== "undefined" && window.console) window.console.warn("[dictation]", msg);
267
+ }
268
+ }
@@ -0,0 +1,369 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-09 Document import — the natural pair to Export-to-Word/Markdown/PDF.
4
+ // Opens a local file and loads it into the editor. Library-free for the common
5
+ // formats: Markdown (.md/.markdown via the core fromMarkdown engine), HTML
6
+ // (.html/.htm), plain text (.txt), and Word's HTML-based format (.doc, which
7
+ // Word saves as MSO-flavored HTML) — Word junk (mso-* styles, o:/w: tags,
8
+ // conditional comments) is stripped the same way paste-from-Word is.
9
+ //
10
+ // Zipped .docx is NOT parseable library-free (it's a ZIP of XML); for it (or to
11
+ // override any type) supply config.documentImportResolver(file) returning an
12
+ // HTML string or Promise<string>, so a server/library can convert it.
13
+ //
14
+ // API:
15
+ // editor.openImportDialog(options?) -> file picker, then import
16
+ // editor.importFile(file, options?) -> import a File object (Promise)
17
+ // editor.htmlFromImportText(text, kind) -> convert text to HTML (kind: md|html|txt|doc)
18
+ // Command: exec_command "importdocument" opens the picker. Slash: "/import".
19
+ // Config:
20
+ // config.documentImport = false // disable
21
+ // config.documentImportMode = "replace" | "insert" // default "replace"
22
+ // config.documentImportAccept = ".md,.markdown,.html,.htm,.txt,.doc,.docx"
23
+ // config.documentImportResolver = function(file){ return htmlOrPromise; }
24
+ RTE_DefaultConfig.plugin_documentimport = RTE_Plugin_DocumentImport;
25
+ if (typeof RTE_DefaultConfig.documentImport === "undefined") RTE_DefaultConfig.documentImport = true;
26
+
27
+ function RTE_Plugin_DocumentImport() {
28
+ var obj = this;
29
+ var config, editor;
30
+
31
+ obj.PluginName = "DocumentImport";
32
+
33
+ obj.InitConfig = function (argconfig) { config = argconfig; };
34
+
35
+ obj.InitEditor = function (argeditor) {
36
+ editor = argeditor;
37
+ if (config.documentImport === false) return;
38
+
39
+ editor.htmlFromImportText = function (text, kind) { return toHtml(String(text == null ? "" : text), kind); };
40
+ editor.importFile = function (file, options) { return importFile(file, options || {}); };
41
+ editor.openImportDialog = function (options) { return openPicker(options || {}); };
42
+
43
+ editor.attachEvent("exec_command_importdocument", function (state) {
44
+ state.returnValue = true;
45
+ state.stopBubble = true;
46
+ openPicker({});
47
+ });
48
+
49
+ if (editor.slashCommands && typeof editor.slashCommands.register === "function") {
50
+ try {
51
+ editor.slashCommands.register({
52
+ id: "import-document",
53
+ title: "Import document",
54
+ description: "Open a Markdown, HTML, text, or Word (.doc) file into the editor",
55
+ keywords: ["import", "open", "file", "word", "markdown", "upload"],
56
+ action: function () { openPicker({}); }
57
+ });
58
+ } catch (e) {}
59
+ }
60
+ };
61
+
62
+ function kindFromName(name) {
63
+ var n = String(name || "").toLowerCase();
64
+ if (/\.(md|markdown|mdown|mkd)$/.test(n)) return "md";
65
+ if (/\.html?$/.test(n)) return "html";
66
+ if (/\.doc$/.test(n)) return "doc";
67
+ if (/\.docx$/.test(n)) return "docx";
68
+ if (/\.txt$/.test(n)) return "txt";
69
+ return "txt";
70
+ }
71
+
72
+ function esc(s) {
73
+ return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
74
+ }
75
+
76
+ // Strip Word/MSO debris from .doc HTML (same intent as paste-from-Word).
77
+ function cleanWordHtml(html) {
78
+ var h = String(html || "");
79
+ // body only, drop head/xml/style islands and conditional comments
80
+ var bodyMatch = /<body[^>]*>([\s\S]*?)<\/body>/i.exec(h);
81
+ if (bodyMatch) h = bodyMatch[1];
82
+ h = h.replace(/<!--\[if[\s\S]*?\[endif\]-->/gi, "");
83
+ h = h.replace(/<!--[\s\S]*?-->/g, "");
84
+ h = h.replace(/<\/?(o:p|o:|w:|xml|style|meta|link|title|head)[^>]*>/gi, "");
85
+ h = h.replace(/<\\?\?xml[^>]*>/gi, "");
86
+ // strip mso-* declarations + empty style/class/lang attrs
87
+ h = h.replace(/\sstyle="[^"]*"/gi, function (m) {
88
+ var cleaned = m.replace(/mso-[^;"]*;?/gi, "").replace(/style="\s*;*\s*"/i, "");
89
+ return /style="\s*"/.test(cleaned) || cleaned === ' style=""' ? "" : cleaned;
90
+ });
91
+ h = h.replace(/\sclass="Mso[^"]*"/gi, "");
92
+ h = h.replace(/\s(lang|xmlns(:\w+)?)="[^"]*"/gi, "");
93
+ return h;
94
+ }
95
+
96
+ function toHtml(text, kind) {
97
+ switch (kind) {
98
+ case "md":
99
+ if (editor && typeof editor.fromMarkdown === "function") return editor.fromMarkdown(text, { apply: false });
100
+ return "<p>" + esc(text).replace(/\n{2,}/g, "</p><p>").replace(/\n/g, "<br>") + "</p>";
101
+ case "html":
102
+ return text;
103
+ case "doc":
104
+ return cleanWordHtml(text);
105
+ case "txt":
106
+ default:
107
+ var paras = String(text).replace(/\r\n?/g, "\n").split(/\n{2,}/);
108
+ return paras.map(function (p) {
109
+ return "<p>" + esc(p).replace(/\n/g, "<br>") + "</p>";
110
+ }).join("");
111
+ }
112
+ }
113
+
114
+ function applyHtml(html, mode) {
115
+ if (html == null) return false;
116
+ if (mode === "insert") {
117
+ if (typeof editor.insertHTML === "function") { editor.insertHTML(html); return true; }
118
+ }
119
+ if (typeof editor.setHTMLCode === "function") { editor.setHTMLCode(html); return true; }
120
+ return false;
121
+ }
122
+
123
+ function importFile(file, options) {
124
+ var mode = options.mode || config.documentImportMode || "replace";
125
+ return new Promise(function (resolve) {
126
+ if (!file) { resolve(false); return; }
127
+ var kind = kindFromName(file.name);
128
+
129
+ // BYOK resolver wins for any type (lets a host handle .docx etc.).
130
+ if (typeof config.documentImportResolver === "function") {
131
+ try {
132
+ var r = config.documentImportResolver(file);
133
+ Promise.resolve(r).then(function (html) {
134
+ if (typeof html === "string") { resolve(applyHtml(html, mode)); }
135
+ else { builtin(); }
136
+ }, function () { builtin(); });
137
+ return;
138
+ } catch (e) { builtin(); return; }
139
+ }
140
+ builtin();
141
+
142
+ function builtin() {
143
+ if (kind === "docx") {
144
+ // Library-free .docx: unzip word/document.xml (native
145
+ // DecompressionStream) + transform WordprocessingML -> HTML.
146
+ if (!docxSupported()) {
147
+ notify("This browser can't unpack .docx (no DecompressionStream). Save as .doc/.html/.md, or wire config.documentImportResolver.");
148
+ resolve(false);
149
+ return;
150
+ }
151
+ readDocx(file).then(function (html) {
152
+ if (html != null) resolve(applyHtml(html, mode));
153
+ else { notify("Could not read this .docx file."); resolve(false); }
154
+ }, function () { notify("Could not read this .docx file."); resolve(false); });
155
+ return;
156
+ }
157
+ if (typeof FileReader === "undefined") { resolve(false); return; }
158
+ var reader = new FileReader();
159
+ reader.onload = function () {
160
+ var html = toHtml(reader.result, kind);
161
+ resolve(applyHtml(html, mode));
162
+ };
163
+ reader.onerror = function () { resolve(false); };
164
+ reader.readAsText(file);
165
+ }
166
+ });
167
+ }
168
+
169
+ function openPicker(options) {
170
+ try {
171
+ var input = document.createElement("input");
172
+ input.type = "file";
173
+ input.accept = options.accept || config.documentImportAccept || ".md,.markdown,.html,.htm,.txt,.doc,.docx";
174
+ input.style.position = "fixed";
175
+ input.style.left = "-9999px";
176
+ input.addEventListener("change", function () {
177
+ var f = input.files && input.files[0];
178
+ if (f) importFile(f, options);
179
+ setTimeout(function () { try { document.body.removeChild(input); } catch (e) {} }, 0);
180
+ });
181
+ document.body.appendChild(input);
182
+ input.click();
183
+ return true;
184
+ } catch (e) {
185
+ if (window.console) console.error("documentimport: openPicker failed", e);
186
+ return false;
187
+ }
188
+ }
189
+
190
+ // ---- library-free .docx (ZIP of XML) ----
191
+ function docxSupported() {
192
+ return typeof DecompressionStream === "function" && typeof DOMParser === "function" &&
193
+ (typeof Response === "function" || typeof Blob === "function");
194
+ }
195
+
196
+ // Locate an entry via the ZIP central directory (reliable comp sizes/offsets).
197
+ function findZipEntry(bytes, wantName) {
198
+ var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
199
+ // End Of Central Directory: signature 0x06054b50, scan from the end.
200
+ var eocd = -1;
201
+ for (var i = bytes.length - 22; i >= 0 && i >= bytes.length - 22 - 65536; i--) {
202
+ if (dv.getUint32(i, true) === 0x06054b50) { eocd = i; break; }
203
+ }
204
+ if (eocd < 0) return null;
205
+ var cdOffset = dv.getUint32(eocd + 16, true);
206
+ var cdCount = dv.getUint16(eocd + 10, true);
207
+ var p = cdOffset;
208
+ for (var n = 0; n < cdCount; n++) {
209
+ if (dv.getUint32(p, true) !== 0x02014b50) break;
210
+ var method = dv.getUint16(p + 10, true);
211
+ var compSize = dv.getUint32(p + 20, true);
212
+ var nameLen = dv.getUint16(p + 28, true);
213
+ var extraLen = dv.getUint16(p + 30, true);
214
+ var commentLen = dv.getUint16(p + 32, true);
215
+ var localOff = dv.getUint32(p + 42, true);
216
+ // Normalize separators: the ZIP spec mandates "/", but some Windows
217
+ // tools (.NET ZipFile on older runtimes) emit "\".
218
+ var name = utf8(bytes.subarray(p + 46, p + 46 + nameLen)).replace(/\\/g, "/");
219
+ if (name === wantName) {
220
+ var lh = new DataView(bytes.buffer, bytes.byteOffset + localOff, 30);
221
+ var lNameLen = lh.getUint16(26, true);
222
+ var lExtraLen = lh.getUint16(28, true);
223
+ var dataStart = localOff + 30 + lNameLen + lExtraLen;
224
+ return { method: method, data: bytes.subarray(dataStart, dataStart + compSize) };
225
+ }
226
+ p += 46 + nameLen + extraLen + commentLen;
227
+ }
228
+ return null;
229
+ }
230
+
231
+ function utf8(u8) {
232
+ try { return new TextDecoder("utf-8").decode(u8); } catch (e) {
233
+ var s = ""; for (var i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]); return s;
234
+ }
235
+ }
236
+
237
+ function inflateRaw(u8) {
238
+ // method 0 = stored, 8 = deflate (raw).
239
+ var ds = new DecompressionStream("deflate-raw");
240
+ var blob = new Blob([u8]);
241
+ return new Response(blob.stream().pipeThrough(ds)).arrayBuffer().then(function (ab) {
242
+ return new Uint8Array(ab);
243
+ });
244
+ }
245
+
246
+ function readDocx(file) {
247
+ var bufPromise = file.arrayBuffer ? file.arrayBuffer() : new Promise(function (res, rej) {
248
+ var r = new FileReader(); r.onload = function () { res(r.result); }; r.onerror = rej; r.readAsArrayBuffer(file);
249
+ });
250
+ return bufPromise.then(function (ab) {
251
+ var bytes = new Uint8Array(ab);
252
+ var entry = findZipEntry(bytes, "word/document.xml");
253
+ if (!entry) return null;
254
+ var xmlPromise = entry.method === 0 ? Promise.resolve(entry.data) : inflateRaw(entry.data);
255
+ return xmlPromise.then(function (xmlBytes) { return docxXmlToHtml(utf8(xmlBytes)); });
256
+ });
257
+ }
258
+
259
+ function docxXmlToHtml(xml) {
260
+ var doc = new DOMParser().parseFromString(xml, "application/xml");
261
+ var W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
262
+ function els(parent, ln) { return parent.getElementsByTagNameNS(W, ln); }
263
+ function firstChildEl(parent, ln) {
264
+ for (var i = 0; i < parent.childNodes.length; i++) {
265
+ var c = parent.childNodes[i];
266
+ if (c.nodeType === 1 && c.localName === ln && c.namespaceURI === W) return c;
267
+ }
268
+ return null;
269
+ }
270
+ function runText(r) {
271
+ var t = "", kids = r.childNodes;
272
+ for (var i = 0; i < kids.length; i++) {
273
+ var c = kids[i];
274
+ if (c.nodeType !== 1) continue;
275
+ if (c.localName === "t") t += c.textContent || "";
276
+ else if (c.localName === "tab") t += "\t";
277
+ else if (c.localName === "br" || c.localName === "cr") t += "\n";
278
+ }
279
+ return t;
280
+ }
281
+ function runHtml(r) {
282
+ var txt = esc(runText(r));
283
+ if (!txt) return "";
284
+ txt = txt.replace(/\n/g, "<br>");
285
+ var rpr = firstChildEl(r, "rPr");
286
+ if (rpr) {
287
+ if (firstChildEl(rpr, "b")) txt = "<strong>" + txt + "</strong>";
288
+ if (firstChildEl(rpr, "i")) txt = "<em>" + txt + "</em>";
289
+ if (firstChildEl(rpr, "u")) txt = "<u>" + txt + "</u>";
290
+ if (firstChildEl(rpr, "strike")) txt = "<s>" + txt + "</s>";
291
+ }
292
+ return txt;
293
+ }
294
+ function paraInner(p) {
295
+ var h = "", kids = p.childNodes;
296
+ for (var i = 0; i < kids.length; i++) {
297
+ var c = kids[i];
298
+ if (c.nodeType === 1 && c.localName === "r" && c.namespaceURI === W) h += runHtml(c);
299
+ else if (c.nodeType === 1 && c.localName === "hyperlink") {
300
+ for (var j = 0; j < c.childNodes.length; j++) if (c.childNodes[j].localName === "r") h += runHtml(c.childNodes[j]);
301
+ }
302
+ }
303
+ return h;
304
+ }
305
+ function paraStyle(p) {
306
+ var ppr = firstChildEl(p, "pPr"); if (!ppr) return {};
307
+ var info = {};
308
+ var ps = firstChildEl(ppr, "pStyle");
309
+ if (ps) { var v = ps.getAttributeNS(W, "val") || ps.getAttribute("w:val") || ""; var m = /heading(\d)/i.exec(v); if (m) info.heading = Math.min(6, parseInt(m[1], 10)); }
310
+ if (firstChildEl(ppr, "numPr")) info.list = true;
311
+ return info;
312
+ }
313
+ var body = els(doc, "body")[0];
314
+ if (!body) return "";
315
+ var out = [], inList = false;
316
+ function closeList() { if (inList) { out.push("</ul>"); inList = false; } }
317
+ for (var i = 0; i < body.childNodes.length; i++) {
318
+ var node = body.childNodes[i];
319
+ if (node.nodeType !== 1 || node.namespaceURI !== W) continue;
320
+ if (node.localName === "p") {
321
+ var st = paraStyle(node);
322
+ var inner = paraInner(node);
323
+ if (st.list) { if (!inList) { out.push("<ul>"); inList = true; } out.push("<li>" + (inner || "") + "</li>"); continue; }
324
+ closeList();
325
+ if (st.heading) out.push("<h" + st.heading + ">" + (inner || "") + "</h" + st.heading + ">");
326
+ else out.push("<p>" + (inner || "<br>") + "</p>");
327
+ } else if (node.localName === "tbl") {
328
+ closeList();
329
+ out.push(tableHtml(node));
330
+ }
331
+ }
332
+ closeList();
333
+ return out.join("\n");
334
+
335
+ function tableHtml(tbl) {
336
+ var rows = [], kids = tbl.childNodes;
337
+ for (var i = 0; i < kids.length; i++) {
338
+ var tr = kids[i];
339
+ if (tr.nodeType !== 1 || tr.localName !== "tr") continue;
340
+ var cells = [];
341
+ for (var j = 0; j < tr.childNodes.length; j++) {
342
+ var tc = tr.childNodes[j];
343
+ if (tc.nodeType !== 1 || tc.localName !== "tc") continue;
344
+ var cellHtml = "";
345
+ for (var k = 0; k < tc.childNodes.length; k++) {
346
+ if (tc.childNodes[k].localName === "p") cellHtml += "<p>" + (paraInner(tc.childNodes[k]) || "<br>") + "</p>";
347
+ }
348
+ cells.push("<td>" + cellHtml + "</td>");
349
+ }
350
+ rows.push("<tr>" + cells.join("") + "</tr>");
351
+ }
352
+ return "<table>" + rows.join("") + "</table>";
353
+ }
354
+ }
355
+
356
+ function notify(msg) {
357
+ try {
358
+ if (editor && typeof editor.createDialog === "function") {
359
+ var d = editor.createDialog((editor.getLangText && editor.getLangText("importtitle")) || "Import document", "rte-dialog-import");
360
+ var w = d.ownerDocument.createElement("div");
361
+ w.style.cssText = "padding:16px;max-width:420px;font:13px -apple-system,Segoe UI,sans-serif;line-height:1.5";
362
+ w.textContent = msg;
363
+ d.appendChild(w);
364
+ return;
365
+ }
366
+ } catch (e) {}
367
+ if (window.console) console.warn("documentimport:", msg);
368
+ }
369
+ }