@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,243 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-05 Read-aloud (text-to-speech). The speech-OUT complement to the
4
+ // dictation plugin's speech-IN, and an accessibility win alongside the
5
+ // accessibility checker. Uses the browser-native Web Speech API
6
+ // (window.speechSynthesis) — no library bundled, no backend, no API key.
7
+ //
8
+ // editor.execCommand("readaloud") toggles a non-modal control bar (play/pause,
9
+ // stop, voice, rate). It reads the current selection if there is one, else the
10
+ // whole document, sentence by sentence, and highlights each sentence in the
11
+ // editor as it is spoken (via the native selection, so document content is
12
+ // never modified). Public API: editor.readAloud(opts) / editor.stopReadAloud().
13
+ RTE_DefaultConfig.plugin_readaloud = RTE_Plugin_ReadAloud;
14
+ RTE_DefaultConfig.readAloudRate = RTE_DefaultConfig.readAloudRate || 1.0;
15
+ RTE_DefaultConfig.readAloudHighlight = (typeof RTE_DefaultConfig.readAloudHighlight === "undefined")
16
+ ? true : RTE_DefaultConfig.readAloudHighlight;
17
+
18
+ function RTE_Plugin_ReadAloud() {
19
+ var obj = this;
20
+ var config, editor;
21
+ var bar = null, playBtn, statusEl, voiceSel, rateSel;
22
+ var queue = [], qIndex = 0, speaking = false, paused = false;
23
+ var savedRange = null;
24
+
25
+ obj.PluginName = "ReadAloud";
26
+
27
+ function synth() { return window.speechSynthesis || null; }
28
+ function supported() { return !!synth() && typeof window.SpeechSynthesisUtterance === "function"; }
29
+
30
+ obj.InitConfig = function (argconfig) { config = argconfig; };
31
+
32
+ obj.InitEditor = function (argeditor) {
33
+ editor = argeditor;
34
+ editor.readAloud = function (opts) { obj.Start(opts || {}); };
35
+ editor.stopReadAloud = function () { obj.Stop(); };
36
+ editor.attachEvent("exec_command_readaloud", function (state) {
37
+ state.returnValue = true;
38
+ if (!supported()) { obj.NotifyUnsupported(); return; }
39
+ if (bar) obj.CloseBar(); else obj.OpenBar();
40
+ });
41
+ // Stop speech if the editor is torn down / page hidden.
42
+ try {
43
+ window.addEventListener("pagehide", function () { obj.Stop(); });
44
+ document.addEventListener("visibilitychange", function () { if (document.hidden) obj.Pause(); });
45
+ } catch (e) { /* ignore */ }
46
+ };
47
+
48
+ obj.NotifyUnsupported = function () {
49
+ try {
50
+ var d = editor.createDialog((editor.getLangText && editor.getLangText("readaloudtitle")) || "Read aloud", "rte-dialog-readaloud");
51
+ var w = d.ownerDocument.createElement("div");
52
+ w.style.cssText = "padding:16px;min-width:320px;font:13px -apple-system,Segoe UI,sans-serif";
53
+ w.textContent = "This browser does not provide a speech synthesizer (Web Speech API). Read-aloud is unavailable here.";
54
+ d.appendChild(w);
55
+ } catch (e) { /* ignore */ }
56
+ };
57
+
58
+ // ---- text + offset map over the editable (for sentence highlighting) ----
59
+ function buildMap() {
60
+ var editable = editor.getEditable();
61
+ var doc = editable.ownerDocument;
62
+ var walker = doc.createTreeWalker(editable, NodeFilter.SHOW_TEXT, {
63
+ acceptNode: function (n) {
64
+ var p = n.parentNode;
65
+ while (p && p !== editable) {
66
+ var nn = p.nodeName;
67
+ if (nn === "SCRIPT" || nn === "STYLE" || nn === "NOSCRIPT") return NodeFilter.FILTER_REJECT;
68
+ p = p.parentNode;
69
+ }
70
+ return n.data && n.data.length ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
71
+ }
72
+ });
73
+ var nodes = [], full = "", n;
74
+ while ((n = walker.nextNode())) { nodes.push({ node: n, start: full.length, len: n.data.length }); full += n.data; }
75
+ function locate(offset) {
76
+ for (var i = 0; i < nodes.length; i++) {
77
+ var e = nodes[i];
78
+ if (offset <= e.start + e.len) return { node: e.node, offset: Math.max(0, offset - e.start) };
79
+ }
80
+ var last = nodes[nodes.length - 1];
81
+ return last ? { node: last.node, offset: last.len } : null;
82
+ }
83
+ function globalOffset(node, off) {
84
+ for (var i = 0; i < nodes.length; i++) if (nodes[i].node === node) return nodes[i].start + off;
85
+ return -1;
86
+ }
87
+ return { full: full, locate: locate, globalOffset: globalOffset, hasNodes: nodes.length > 0 };
88
+ }
89
+
90
+ // Split [text] into sentence segments with absolute offsets (base added).
91
+ function splitSentences(text, base) {
92
+ var segs = [], re = /[^.!?\n。!?]*[.!?\n。!?]+|\S[^.!?\n]*$/g, m;
93
+ while ((m = re.exec(text)) !== null) {
94
+ if (m[0] && /\S/.test(m[0])) segs.push({ text: m[0], start: base + m.index, end: base + m.index + m[0].length });
95
+ if (m.index === re.lastIndex) re.lastIndex++; // guard against zero-length
96
+ }
97
+ if (!segs.length && /\S/.test(text)) segs.push({ text: text, start: base, end: base + text.length });
98
+ return segs;
99
+ }
100
+
101
+ // ---- speak queue ----
102
+ obj.Start = function (opts) {
103
+ if (!supported()) { obj.NotifyUnsupported(); return; }
104
+ obj.Stop(); // reset any prior run
105
+ var map = buildMap();
106
+ if (!map.hasNodes) { setStatus("Nothing to read"); return; }
107
+ // Selection range -> read just the selection; else whole doc.
108
+ var sel = editor.getSelection ? editor.getSelection() : null;
109
+ var rStart = 0, rEnd = map.full.length;
110
+ try {
111
+ if (sel && sel.rangeCount && !sel.isCollapsed) {
112
+ var rg = sel.getRangeAt(0);
113
+ var gs = map.globalOffset(rg.startContainer, rg.startOffset);
114
+ var ge = map.globalOffset(rg.endContainer, rg.endOffset);
115
+ if (gs >= 0 && ge >= 0 && ge > gs) { rStart = gs; rEnd = ge; }
116
+ }
117
+ } catch (e) { /* full doc */ }
118
+ savedRange = (sel && sel.rangeCount) ? sel.getRangeAt(0).cloneRange() : null;
119
+ var slice = map.full.substring(rStart, rEnd);
120
+ queue = splitSentences(slice, rStart);
121
+ if (!queue.length) { setStatus("Nothing to read"); return; }
122
+ qIndex = 0; speaking = true; paused = false;
123
+ obj._map = map; obj._opts = opts || {};
124
+ updatePlayBtn();
125
+ speakNext();
126
+ };
127
+
128
+ function speakNext() {
129
+ var s = synth();
130
+ if (!s || qIndex >= queue.length) { obj.Stop(); return; }
131
+ var seg = queue[qIndex];
132
+ var u = new SpeechSynthesisUtterance(seg.text);
133
+ var rate = (obj._opts && obj._opts.rate) || (rateSel && parseFloat(rateSel.value)) || config.readAloudRate || 1.0;
134
+ u.rate = Math.min(2, Math.max(0.5, rate));
135
+ var v = chosenVoice();
136
+ if (v) u.voice = v;
137
+ u.onstart = function () { if (config.readAloudHighlight !== false) highlight(seg); setStatus("Reading… (" + (qIndex + 1) + "/" + queue.length + ")"); };
138
+ u.onend = function () { if (!speaking) return; qIndex++; speakNext(); };
139
+ u.onerror = function () { if (!speaking) return; qIndex++; speakNext(); };
140
+ try { s.speak(u); } catch (e) { qIndex++; speakNext(); }
141
+ }
142
+
143
+ function highlight(seg) {
144
+ try {
145
+ var map = obj._map; if (!map) return;
146
+ var a = map.locate(seg.start), b = map.locate(seg.end);
147
+ if (!a || !b) return;
148
+ var doc = editor.getDocument();
149
+ var r = doc.createRange();
150
+ r.setStart(a.node, a.offset); r.setEnd(b.node, b.offset);
151
+ var sel = editor.getSelection();
152
+ if (sel) { sel.removeAllRanges(); sel.addRange(r); }
153
+ } catch (e) { /* ignore */ }
154
+ }
155
+
156
+ obj.Pause = function () {
157
+ var s = synth(); if (!s || !speaking) return;
158
+ if (!paused) { try { s.pause(); } catch (e) {} paused = true; updatePlayBtn(); setStatus("Paused"); }
159
+ };
160
+ obj.Resume = function () {
161
+ var s = synth(); if (!s || !speaking) return;
162
+ if (paused) { try { s.resume(); } catch (e) {} paused = false; updatePlayBtn(); setStatus("Reading…"); }
163
+ };
164
+ obj.Stop = function () {
165
+ var s = synth();
166
+ speaking = false; paused = false; queue = []; qIndex = 0;
167
+ if (s) { try { s.cancel(); } catch (e) {} }
168
+ // restore the user's caret/selection
169
+ try {
170
+ if (savedRange) { var sel = editor.getSelection(); if (sel) { sel.removeAllRanges(); sel.addRange(savedRange); } }
171
+ } catch (e) { /* ignore */ }
172
+ savedRange = null;
173
+ updatePlayBtn(); setStatus("");
174
+ };
175
+
176
+ // ---- voices ----
177
+ function loadVoices() {
178
+ var s = synth(); if (!s || !voiceSel) return;
179
+ var voices = s.getVoices() || [];
180
+ if (!voices.length) return;
181
+ var cur = voiceSel.value;
182
+ voiceSel.innerHTML = "";
183
+ voices.forEach(function (v, i) {
184
+ var o = voiceSel.ownerDocument.createElement("option");
185
+ o.value = v.name; o.textContent = v.name + " (" + v.lang + ")";
186
+ if (v.default) o.textContent += " (default)";
187
+ voiceSel.appendChild(o);
188
+ });
189
+ if (cur) voiceSel.value = cur;
190
+ }
191
+ function chosenVoice() {
192
+ var s = synth(); if (!s) return null;
193
+ var voices = s.getVoices() || [];
194
+ if (obj._opts && obj._opts.voice) { for (var i = 0; i < voices.length; i++) if (voices[i].name === obj._opts.voice) return voices[i]; }
195
+ if (voiceSel && voiceSel.value) { for (var j = 0; j < voices.length; j++) if (voices[j].name === voiceSel.value) return voices[j]; }
196
+ return null;
197
+ }
198
+
199
+ // ---- non-modal control bar ----
200
+ function btn(parent, label, title) {
201
+ var b = parent.ownerDocument.createElement("button");
202
+ b.type = "button"; b.textContent = label; if (title) b.title = title;
203
+ b.className = "rte-readaloud-button";
204
+ parent.appendChild(b); return b;
205
+ }
206
+ obj.OpenBar = function () {
207
+ if (bar) return;
208
+ var d = document;
209
+ bar = d.createElement("div");
210
+ bar.className = "rte-readaloud-bar";
211
+ var title = d.createElement("span"); title.className = "rte-readaloud-title"; title.textContent = "Read"; bar.appendChild(title);
212
+ playBtn = btn(bar, "Play", "Play / Pause");
213
+ var stopBtn = btn(bar, "Stop", "Stop");
214
+ voiceSel = d.createElement("select"); voiceSel.className = "rte-readaloud-voice"; voiceSel.title = "Voice"; bar.appendChild(voiceSel);
215
+ rateSel = d.createElement("select"); rateSel.className = "rte-readaloud-rate"; rateSel.title = "Speed";
216
+ ["0.75", "1", "1.25", "1.5", "2"].forEach(function (r) { var o = d.createElement("option"); o.value = r; o.textContent = r + "x"; if (r === "1") o.selected = true; rateSel.appendChild(o); });
217
+ bar.appendChild(rateSel);
218
+ statusEl = d.createElement("span"); statusEl.className = "rte-readaloud-status"; bar.appendChild(statusEl);
219
+ var closeBtn = btn(bar, "Close", "Close"); closeBtn.className += " is-close";
220
+ d.body.appendChild(bar);
221
+
222
+ loadVoices();
223
+ var s = synth(); if (s && typeof s.addEventListener === "function") s.addEventListener("voiceschanged", loadVoices);
224
+
225
+ playBtn.onclick = function () {
226
+ if (!speaking) { obj.Start({}); }
227
+ else if (paused) { obj.Resume(); }
228
+ else { obj.Pause(); }
229
+ };
230
+ stopBtn.onclick = function () { obj.Stop(); };
231
+ closeBtn.onclick = function () { obj.CloseBar(); };
232
+ // Auto-start reading immediately on open.
233
+ obj.Start({});
234
+ };
235
+ obj.CloseBar = function () {
236
+ obj.Stop();
237
+ if (bar && bar.parentNode) bar.parentNode.removeChild(bar);
238
+ bar = null; playBtn = statusEl = voiceSel = rateSel = null;
239
+ };
240
+
241
+ function updatePlayBtn() { if (playBtn) playBtn.textContent = (speaking && !paused) ? "Pause" : "Play"; }
242
+ function setStatus(t) { if (statusEl) statusEl.textContent = t; }
243
+ }
@@ -0,0 +1,208 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // Restricted editing — Word-style "protect document with editable regions".
4
+ // When `restrictedEditingMode` is true on the editor's config, all content
5
+ // is read-only EXCEPT spans tagged with `data-rte-editable="true"`. Use it
6
+ // for templates with fillable fields, contracts with author-only edits, etc.
7
+ //
8
+ // var ed = new RichTextEditor("#editor", { restrictedEditingMode: true });
9
+ // ed.restrictedEditing.markSelection(); // mark current selection editable
10
+ // ed.restrictedEditing.enable(); // turn on lockdown
11
+ // ed.restrictedEditing.disable(); // turn off lockdown
12
+ // ed.restrictedEditing.goToNext(); // move caret to next editable region
13
+ // ed.restrictedEditing.goToPrev(); // move caret to previous editable region
14
+ // ed.restrictedEditing.list(); // -> Array<Element>
15
+
16
+ if (!RTE_DefaultConfig.svgCode_restrictedediting) {
17
+ RTE_DefaultConfig.svgCode_restrictedediting = '<svg viewBox="0 0 24 24" fill="none" stroke="#5F6368" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>';
18
+ }
19
+
20
+ RTE_DefaultConfig.plugin_restrictedediting = RTE_Plugin_RestrictedEditing;
21
+
22
+ function RTE_Plugin_RestrictedEditing() {
23
+ var obj = this;
24
+ var config;
25
+ var editor;
26
+ var enabled = false;
27
+ var editableAttr = "data-rte-editable";
28
+
29
+ obj.PluginName = "RestrictedEditing";
30
+
31
+ obj.InitConfig = function (argconfig) {
32
+ config = argconfig;
33
+ config.text_restrictedediting = config.text_restrictedediting || "Restricted editing";
34
+ config.text_restrictedediting_mark = config.text_restrictedediting_mark || "Mark as editable";
35
+ config.text_restrictedediting_next = config.text_restrictedediting_next || "Next editable region";
36
+ config.text_restrictedediting_hint = config.text_restrictedediting_hint || "Lock the document; only marked regions accept input.";
37
+ };
38
+
39
+ obj.InitEditor = function (argeditor) {
40
+ editor = argeditor;
41
+ enabled = !!config.restrictedEditingMode;
42
+
43
+ editor.restrictedEditing = {
44
+ isEnabled: function () { return enabled; },
45
+ enable: enable,
46
+ disable: disable,
47
+ toggle: function () { return enabled ? disable() : enable(); },
48
+ markSelection: markSelection,
49
+ unmark: unmark,
50
+ list: list,
51
+ goToNext: function () { return navigate(1); },
52
+ goToPrev: function () { return navigate(-1); }
53
+ };
54
+
55
+ editor.attachEvent("exec_command_restrictedediting", function (state) {
56
+ state.returnValue = true;
57
+ state.stopBubble = true;
58
+ // Toolbar click toggles the lockdown.
59
+ editor.restrictedEditing.toggle();
60
+ });
61
+
62
+ if (enabled) enable();
63
+
64
+ // Refresh styles when content changes (newly-pasted marked spans inherit visuals).
65
+ editor.attachEvent("change", refreshStyles);
66
+ refreshStyles();
67
+ };
68
+
69
+ function getEditable() {
70
+ return editor.getEditable ? editor.getEditable() : null;
71
+ }
72
+
73
+ function refreshStyles() {
74
+ var body = getEditable();
75
+ if (!body) return;
76
+ var doc = body.ownerDocument;
77
+ if (!doc || doc.getElementById("__rte_restrictedediting_styles")) return;
78
+ var s = doc.createElement("style");
79
+ s.id = "__rte_restrictedediting_styles";
80
+ s.textContent = [
81
+ "[" + editableAttr + "='true']{background:rgba(34,197,94,.10);outline:1px dashed rgba(34,197,94,.55);outline-offset:1px;border-radius:2px;padding:1px 2px}",
82
+ "body.rte-restricted [contenteditable='false']{cursor:not-allowed}",
83
+ "body.rte-restricted [" + editableAttr + "='true']{background:rgba(34,197,94,.16)}"
84
+ ].join("\n");
85
+ (doc.head || doc.getElementsByTagName("head")[0]).appendChild(s);
86
+ }
87
+
88
+ function enable() {
89
+ var body = getEditable();
90
+ if (!body) return false;
91
+ enabled = true;
92
+ body.classList.add("rte-restricted");
93
+ // Walk top-level children — anything WITHOUT a descendant marked region
94
+ // becomes contenteditable=false. Marked regions stay editable.
95
+ for (var i = 0; i < body.children.length; i++) {
96
+ var node = body.children[i];
97
+ if (node.querySelector("[" + editableAttr + "='true']")) {
98
+ node.setAttribute("contenteditable", "true");
99
+ // Lock the wrapper itself but keep the marked descendants editable.
100
+ node.setAttribute(editableAttr + "-wrapper", "true");
101
+ } else {
102
+ node.setAttribute("contenteditable", "false");
103
+ }
104
+ }
105
+ // Make the body itself NOT editable; rely on per-element contenteditable.
106
+ body.setAttribute("contenteditable", "false");
107
+ refreshStyles();
108
+ return true;
109
+ }
110
+
111
+ function disable() {
112
+ var body = getEditable();
113
+ if (!body) return false;
114
+ enabled = false;
115
+ body.classList.remove("rte-restricted");
116
+ var locked = body.querySelectorAll("[contenteditable]");
117
+ for (var i = 0; i < locked.length; i++) {
118
+ locked[i].removeAttribute("contenteditable");
119
+ locked[i].removeAttribute(editableAttr + "-wrapper");
120
+ }
121
+ body.setAttribute("contenteditable", "true");
122
+ return true;
123
+ }
124
+
125
+ // Mark the current selection as an editable region. Returns the wrapper element.
126
+ function markSelection(options) {
127
+ options = options || {};
128
+ var body = getEditable();
129
+ if (!body) return null;
130
+ var doc = body.ownerDocument;
131
+ var sel = doc.getSelection();
132
+ if (!sel || sel.rangeCount === 0) return null;
133
+ var range = sel.getRangeAt(0);
134
+ if (range.collapsed) return null;
135
+ var span = doc.createElement("span");
136
+ span.setAttribute(editableAttr, "true");
137
+ if (options.label) span.setAttribute("aria-label", options.label);
138
+ try {
139
+ range.surroundContents(span);
140
+ } catch (e) {
141
+ // surroundContents fails on partial-element selections; fall back
142
+ // to extract → wrap → insert.
143
+ var frag = range.extractContents();
144
+ span.appendChild(frag);
145
+ range.insertNode(span);
146
+ }
147
+ // Restore selection inside the new wrapper.
148
+ var r2 = doc.createRange();
149
+ r2.selectNodeContents(span);
150
+ sel.removeAllRanges();
151
+ sel.addRange(r2);
152
+ refreshStyles();
153
+ if (enabled) enable(); // recompute lockdown around new editable region
154
+ return span;
155
+ }
156
+
157
+ function unmark(node) {
158
+ node = node || (function () {
159
+ var body = getEditable();
160
+ var sel = body && body.ownerDocument.getSelection();
161
+ if (!sel || sel.rangeCount === 0) return null;
162
+ var n = sel.anchorNode;
163
+ while (n && n !== body) {
164
+ if (n.nodeType === 1 && n.getAttribute && n.getAttribute(editableAttr) === "true") return n;
165
+ n = n.parentNode;
166
+ }
167
+ return null;
168
+ })();
169
+ if (!node) return false;
170
+ // Replace the wrapper with its children (preserve content).
171
+ var parent = node.parentNode;
172
+ while (node.firstChild) parent.insertBefore(node.firstChild, node);
173
+ parent.removeChild(node);
174
+ if (enabled) enable();
175
+ return true;
176
+ }
177
+
178
+ function list() {
179
+ var body = getEditable();
180
+ if (!body) return [];
181
+ return Array.prototype.slice.call(body.querySelectorAll("[" + editableAttr + "='true']"));
182
+ }
183
+
184
+ function navigate(direction) {
185
+ var regions = list();
186
+ if (regions.length === 0) return null;
187
+ var body = getEditable();
188
+ var sel = body && body.ownerDocument.getSelection();
189
+ var current = null;
190
+ if (sel && sel.anchorNode) {
191
+ var n = sel.anchorNode;
192
+ while (n && n !== body) {
193
+ if (n.nodeType === 1 && n.getAttribute && n.getAttribute(editableAttr) === "true") { current = n; break; }
194
+ n = n.parentNode;
195
+ }
196
+ }
197
+ var idx = current ? regions.indexOf(current) : -1;
198
+ var next = regions[(idx + direction + regions.length) % regions.length];
199
+ if (!next) return null;
200
+ var range = body.ownerDocument.createRange();
201
+ range.selectNodeContents(next);
202
+ range.collapse(false);
203
+ sel.removeAllRanges();
204
+ sel.addRange(range);
205
+ next.scrollIntoView({ block: "center", behavior: "smooth" });
206
+ return next;
207
+ }
208
+ }