@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,250 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-04 Smart chips — Google-Docs-style inline data chips. The most-used
4
+ // chip (and the one with no good equivalent here) is the interactive DATE chip:
5
+ // the existing `insertdate` drops plain text, whereas this inserts an atomic
6
+ // pill with a picker and a machine-readable value. A generic chip menu also
7
+ // supports Person / Link chips; rich data (avatars, presence, file metadata)
8
+ // comes from an optional BYOK resolver — same shape as the bookmark / spell /
9
+ // AI resolvers — so the chip system needs no backend of ours to be useful.
10
+ //
11
+ // config.chipResolver = function (query, type) {
12
+ // // type: "person" | "link"
13
+ // return Promise.resolve([{ label, value, icon, href }]);
14
+ // };
15
+ RTE_DefaultConfig.plugin_smartchips = RTE_Plugin_SmartChips;
16
+ RTE_DefaultConfig.chipResolver = RTE_DefaultConfig.chipResolver || null;
17
+ RTE_DefaultConfig.chipDateFormat = RTE_DefaultConfig.chipDateFormat || null; // optional fn(Date)->string
18
+
19
+ function RTE_Plugin_SmartChips() {
20
+ var obj = this;
21
+ var config, editor;
22
+
23
+ obj.PluginName = "SmartChips";
24
+
25
+ obj.InitConfig = function (argconfig) { config = argconfig; };
26
+
27
+ obj.InitEditor = function (argeditor) {
28
+ editor = argeditor;
29
+ editor.attachEvent("exec_command_insertdatechip", function (state) { state.returnValue = true; obj.OpenDateChip(); });
30
+ editor.attachEvent("exec_command_insertchip", function (state) { state.returnValue = true; obj.OpenChipMenu(); });
31
+ injectStyles();
32
+ };
33
+
34
+ function append(parent, tag, cssText, className) {
35
+ var el = parent.ownerDocument.createElement(tag);
36
+ if (cssText) el.style.cssText = cssText;
37
+ if (className) el.className = className;
38
+ parent.appendChild(el);
39
+ return el;
40
+ }
41
+ function esc(s) {
42
+ return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
43
+ }
44
+
45
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
46
+ function fmtDate(d) {
47
+ if (config.chipDateFormat && typeof config.chipDateFormat === "function") {
48
+ try { return String(config.chipDateFormat(d)); } catch (e) { /* fall through */ }
49
+ }
50
+ return MONTHS[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
51
+ }
52
+ function isoDate(d) {
53
+ function pad(n) { return (n < 10 ? "0" : "") + n; }
54
+ return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
55
+ }
56
+
57
+ var ICONS = {
58
+ date: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="2" y="3" width="12" height="11" rx="1.5"/><path d="M2 6.5h12"/><path d="M5 1.5v3"/><path d="M11 1.5v3"/></svg>',
59
+ person: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="8" cy="5" r="2.6"/><path d="M3 13.5c0-2.5 2.2-4 5-4s5 1.5 5 4"/></svg>',
60
+ link: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M6.5 9.5a3 3 0 004.2 0l2-2a3 3 0 10-4.2-4.2L7.4 4.3"/><path d="M9.5 6.5a3 3 0 00-4.2 0l-2 2a3 3 0 104.2 4.2l1-1"/></svg>'
61
+ };
62
+
63
+ function chipHtml(type, label, value, href) {
64
+ var icon = ICONS[type] || "";
65
+ var attrs = 'class="rte-chip rte-chip-' + esc(type) + '" data-rte-chip="' + esc(type) + '" data-rte-chip-value="' + esc(value || label) + '" contenteditable="false"';
66
+ var inner = '<span class="rte-chip-icon">' + icon + "</span><span class=\"rte-chip-label\">" + esc(label) + "</span>";
67
+ if (type === "link" && href) {
68
+ var safeHref = /^(javascript|data|vbscript):/i.test(href) ? "#" : href;
69
+ return '<a ' + attrs + ' href="' + esc(safeHref) + '" target="_blank" rel="noopener noreferrer">' + inner + "</a>";
70
+ }
71
+ return "<span " + attrs + ">" + inner + "</span>";
72
+ }
73
+
74
+ function insertChip(type, label, value, href) {
75
+ injectStyles();
76
+ // trailing zero-width space so the caret lands after the atomic pill
77
+ editor.insertHTML(chipHtml(type, label, value, href) + "​");
78
+ editor.focus();
79
+ }
80
+
81
+ // --- Date chip --------------------------------------------------------
82
+
83
+ obj.OpenDateChip = function () {
84
+ var dlg = editor.createDialog((editor.getLangText && editor.getLangText("insertdatechiptitle")) || "Date", "rte-dialog-datechip");
85
+ var close = typeof dlg.close === "function" ? function () { dlg.close(); } : function () { editor.closeCurrentPopup(); };
86
+ var wrap = append(dlg, "div", "", "rte-chip-dialog rte-chip-date-dialog");
87
+ append(wrap, "div", "", "rte-chip-dialog-copy").textContent = "Choose a quick date or insert a custom date chip.";
88
+
89
+ function quick(label, d) {
90
+ var b = append(wrap, "button", "", "rte-chip-quick");
91
+ b.type = "button"; b.textContent = label + " — " + fmtDate(d);
92
+ b.onmouseover = function () { b.style.background = "#eef2ff"; };
93
+ b.onmouseout = function () { b.style.background = "transparent"; };
94
+ b.textContent = "";
95
+ append(b, "span", "", "rte-chip-quick-label").textContent = label;
96
+ append(b, "span", "", "rte-chip-quick-value").textContent = fmtDate(d);
97
+ b.onmouseover = null;
98
+ b.onmouseout = null;
99
+ b.setAttribute("aria-label", "Insert " + label + ", " + fmtDate(d));
100
+ b.onclick = function () { close(); insertChip("date", fmtDate(d), isoDate(d)); };
101
+ return b;
102
+ }
103
+ var now = new Date(editor.__chipNow || Date.now());
104
+ function addDays(base, n) { var x = new Date(base.getTime()); x.setDate(x.getDate() + n); return x; }
105
+ quick("Today", now);
106
+ quick("Tomorrow", addDays(now, 1));
107
+ quick("Yesterday", addDays(now, -1));
108
+ quick("Next week", addDays(now, 7));
109
+
110
+ var pickRow = append(wrap, "div", "", "rte-chip-pick-row");
111
+ var di = append(pickRow, "input", "", "rte-chip-input");
112
+ di.type = "date"; di.value = isoDate(now);
113
+ di.setAttribute("aria-label", "Custom date");
114
+ var go = append(pickRow, "button", "", "rte-chip-action rte-chip-action-primary");
115
+ go.type = "button"; go.textContent = "Insert";
116
+ go.onclick = function () {
117
+ var parts = (di.value || "").split("-");
118
+ if (parts.length === 3) {
119
+ var d = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
120
+ close(); insertChip("date", fmtDate(d), di.value);
121
+ } else { close(); }
122
+ };
123
+ setTimeout(function () { di.focus(); }, 0);
124
+ };
125
+
126
+ // --- Generic chip menu (Date / Person / Link) -------------------------
127
+
128
+ obj.OpenChipMenu = function () {
129
+ var dlg = editor.createDialog((editor.getLangText && editor.getLangText("insertchiptitle")) || "Insert chip", "rte-dialog-chip");
130
+ var close = typeof dlg.close === "function" ? function () { dlg.close(); } : function () { editor.closeCurrentPopup(); };
131
+ var wrap = append(dlg, "div", "", "rte-chip-dialog rte-chip-menu-dialog");
132
+ append(wrap, "div", "", "rte-chip-dialog-copy").textContent = "Insert a date, person, or link chip into the document.";
133
+
134
+ var typeRow = append(wrap, "div", "", "rte-chip-type-tabs");
135
+ typeRow.setAttribute("role", "tablist");
136
+ typeRow.setAttribute("aria-label", "Chip type");
137
+ var types = [["date", "Date"], ["person", "Person"], ["link", "Link"]];
138
+ var current = "date";
139
+ var body = append(wrap, "div", "", "rte-chip-dialog-body");
140
+ var typeButtons = {};
141
+ types.forEach(function (t) {
142
+ var b = append(typeRow, "button", "", "rte-chip-type-tab");
143
+ b.type = "button"; b.textContent = t[1];
144
+ b.setAttribute("role", "tab");
145
+ b.setAttribute("aria-selected", t[0] === current ? "true" : "false");
146
+ typeButtons[t[0]] = b;
147
+ b.onclick = function () { current = t[0]; highlight(); renderBody(); };
148
+ });
149
+ function highlight() {
150
+ types.forEach(function (t) {
151
+ var active = t[0] === current;
152
+ typeButtons[t[0]].classList.toggle("is-selected", active);
153
+ typeButtons[t[0]].setAttribute("aria-selected", active ? "true" : "false");
154
+ });
155
+ }
156
+
157
+ function renderBody() {
158
+ body.innerHTML = "";
159
+ if (current === "date") {
160
+ var note = append(body, "div", "", "rte-chip-field-label");
161
+ note.textContent = "Insert a date chip:";
162
+ var di = append(body, "input", "", "rte-chip-input");
163
+ di.type = "date"; di.value = isoDate(new Date(editor.__chipNow || Date.now()));
164
+ di.setAttribute("aria-label", "Date chip value");
165
+ addInsert(function () {
166
+ var p = (di.value || "").split("-");
167
+ if (p.length === 3) { var d = new Date(+p[0], +p[1] - 1, +p[2]); close(); insertChip("date", fmtDate(d), di.value); }
168
+ });
169
+ } else {
170
+ var lbl = append(body, "div", "", "rte-chip-field-label");
171
+ lbl.textContent = current === "person" ? "Name (or search):" : "URL:";
172
+ var input = append(body, "input", "", "rte-chip-input");
173
+ input.type = current === "link" ? "url" : "text";
174
+ input.placeholder = current === "person" ? "Jane Doe" : "https://example.com";
175
+ input.setAttribute("aria-label", current === "person" ? "Person chip name" : "Link chip URL");
176
+ var results = append(body, "div", "", "rte-chip-results");
177
+ results.setAttribute("role", "listbox");
178
+ results.setAttribute("aria-label", "Chip suggestions");
179
+ // BYOK resolver: live suggestions
180
+ if (config.chipResolver && typeof config.chipResolver === "function") {
181
+ input.oninput = function () {
182
+ var q = input.value.trim();
183
+ results.innerHTML = "";
184
+ if (!q) return;
185
+ try {
186
+ var p = config.chipResolver(q, current);
187
+ if (p && p.then) p.then(function (list) {
188
+ results.innerHTML = "";
189
+ (list || []).slice(0, 6).forEach(function (it) {
190
+ var r = append(results, "button", "", "rte-chip-suggestion");
191
+ r.type = "button"; r.textContent = it.label || it.value;
192
+ r.setAttribute("role", "option");
193
+ r.onclick = function () { close(); insertChip(current, it.label || it.value, it.value || it.label, it.href); };
194
+ });
195
+ });
196
+ } catch (e) { /* ignore */ }
197
+ };
198
+ }
199
+ addInsert(function () {
200
+ var v = input.value.trim(); if (!v) { close(); return; }
201
+ if (current === "link") {
202
+ // Validate the URL: reject javascript:/data:/vbscript:/file:,
203
+ // prefix https:// only on bare domains, else refuse.
204
+ if (/^(javascript|data|vbscript|file):/i.test(v)) {
205
+ input.style.borderColor = "#dc2626"; input.placeholder = "Enter a valid http(s) URL"; input.value = ""; return;
206
+ }
207
+ if (!/^https?:\/\//i.test(v)) {
208
+ if (/^[a-z0-9.-]+\.[a-z]{2,}([\/?#].*)?$/i.test(v)) v = "https://" + v;
209
+ else { input.style.borderColor = "#dc2626"; input.placeholder = "Enter a valid http(s) URL"; input.value = ""; return; }
210
+ }
211
+ close();
212
+ insertChip("link", v.replace(/^https?:\/\//i, "").replace(/\/$/, ""), v, v);
213
+ } else {
214
+ close();
215
+ insertChip("person", v, v);
216
+ }
217
+ });
218
+ setTimeout(function () { input.focus(); }, 0);
219
+ }
220
+ }
221
+ function addInsert(fn) {
222
+ var foot = append(body, "div", "", "rte-chip-dialog-footer");
223
+ var ok = append(foot, "button", "", "rte-chip-action rte-chip-action-primary");
224
+ ok.type = "button"; ok.textContent = "Insert"; ok.onclick = fn;
225
+ }
226
+ highlight(); renderBody();
227
+ };
228
+
229
+ function injectStyles() {
230
+ var css = [
231
+ ".rte-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px 2px 6px;margin:0 1px;border:1px solid rgba(100,116,139,.16);border-radius:999px;background:linear-gradient(180deg,#f8fbff,#eef4ff);color:#315277;font-size:.92em;line-height:1.45;white-space:nowrap;text-decoration:none;vertical-align:baseline;cursor:default;user-select:none;box-shadow:0 1px 2px rgba(15,23,42,.06)}",
232
+ ".rte-chip-icon{display:inline-flex;align-items:center;opacity:.9}",
233
+ ".rte-chip-date{background:linear-gradient(180deg,#f0fdff,#e6f8fb);color:#0e7490;border-color:rgba(14,116,144,.18)}",
234
+ ".rte-chip-person{background:linear-gradient(180deg,#f6f7ff,#eef2ff);color:#3730a3;border-color:rgba(55,48,163,.18)}",
235
+ ".rte-chip-link{background:linear-gradient(180deg,#f5fff8,#ecfdf4);color:#15803d;border-color:rgba(21,128,61,.18);cursor:pointer}",
236
+ ".rte-chip-link:hover{text-decoration:underline}"
237
+ ].join("\n");
238
+ try {
239
+ var editdoc = editor.getDocument();
240
+ if (!editdoc) return;
241
+ var head = editdoc.head || (editdoc.getElementsByTagName && editdoc.getElementsByTagName("head")[0]) || editdoc.documentElement;
242
+ if (head && !editdoc.querySelector("style[data-rte-smartchips]")) {
243
+ var st = editdoc.createElement("style");
244
+ st.setAttribute("data-rte-smartchips", "1");
245
+ st.textContent = css;
246
+ head.appendChild(st);
247
+ }
248
+ } catch (e) { /* ignore */ }
249
+ }
250
+ }
@@ -0,0 +1,181 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-04 Spell-check. Closes the "no spell-check UI" gap vs TinyMCE /
4
+ // CKEditor — but without bundling a dictionary or running a server of ours.
5
+ //
6
+ // Two layers:
7
+ // 1. Native: ensure the editable carries spellcheck="true" so the browser's
8
+ // built-in checker shows red squiggles (zero infra). Toggle with
9
+ // config.spellCheckEnabled (default true).
10
+ // 2. BYOK panel: wire config.spellCheckResolver to a service (LanguageTool,
11
+ // Hunspell-on-server, an LLM, …) and the `spellcheck` command opens a
12
+ // review panel listing each issue with click-to-replace suggestions:
13
+ // config.spellCheckResolver = function (text) {
14
+ // return fetch('/spell?t=' + encodeURIComponent(text))
15
+ // .then(function (r) { return r.json(); });
16
+ // // -> [{ word, offset, length, suggestions: ["...", ...] }]
17
+ // };
18
+ RTE_DefaultConfig.plugin_spellcheck = RTE_Plugin_SpellCheck;
19
+ RTE_DefaultConfig.spellCheckResolver = RTE_DefaultConfig.spellCheckResolver || null;
20
+ if (typeof RTE_DefaultConfig.spellCheckEnabled === "undefined") RTE_DefaultConfig.spellCheckEnabled = true;
21
+
22
+ function RTE_Plugin_SpellCheck() {
23
+ var obj = this;
24
+ var config, editor;
25
+
26
+ obj.PluginName = "SpellCheck";
27
+
28
+ obj.InitConfig = function (argconfig) { config = argconfig; };
29
+
30
+ obj.InitEditor = function (argeditor) {
31
+ editor = argeditor;
32
+
33
+ // Native spellcheck attribute on the editable.
34
+ try {
35
+ var editable = editor.getEditable && editor.getEditable();
36
+ if (editable) editable.setAttribute("spellcheck", config.spellCheckEnabled === false ? "false" : "true");
37
+ } catch (e) { /* ignore */ }
38
+
39
+ editor.attachEvent("exec_command_spellcheck", function (state) {
40
+ state.returnValue = true;
41
+ obj.RunSpellCheck();
42
+ });
43
+ injectStyles();
44
+ };
45
+
46
+ function append(parent, tag, cssText, className) {
47
+ var el = parent.ownerDocument.createElement(tag);
48
+ if (cssText) el.style.cssText = cssText;
49
+ if (className) el.className = className;
50
+ parent.appendChild(el);
51
+ return el;
52
+ }
53
+
54
+ function esc(s) {
55
+ return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
56
+ }
57
+
58
+ function notify(msg) {
59
+ try { if (!editor.InvokeEventHook || !editor.InvokeEventHook("customdialog", "spellcheck", msg)) { /* fall through to toast */ } } catch (e) { }
60
+ try {
61
+ var editable = editor.getEditable();
62
+ var host = editable.ownerDocument;
63
+ var t = host.createElement("div");
64
+ t.className = "rte-spell-toast";
65
+ t.textContent = msg;
66
+ host.body.appendChild(t);
67
+ setTimeout(function () { if (t.parentNode) t.parentNode.removeChild(t); }, 2600);
68
+ } catch (e) { /* ignore */ }
69
+ }
70
+
71
+ // Replace the first text-node occurrence of `word` with `replacement`.
72
+ // Returns true on success. Skips CODE/PRE.
73
+ function replaceFirst(word, replacement) {
74
+ try {
75
+ var editable = editor.getEditable();
76
+ var editdoc = editor.getDocument();
77
+ var walker = editdoc.createTreeWalker(editable, NodeFilter.SHOW_TEXT, null);
78
+ var n;
79
+ var re = new RegExp("\\b" + word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\b");
80
+ while ((n = walker.nextNode())) {
81
+ var p = n.parentNode;
82
+ if (p && (p.nodeName === "CODE" || p.nodeName === "PRE")) continue;
83
+ var idx = n.data.search(re);
84
+ if (idx >= 0) {
85
+ n.data = n.data.substring(0, idx) + replacement + n.data.substring(idx + word.length);
86
+ return true;
87
+ }
88
+ }
89
+ } catch (e) { /* ignore */ }
90
+ return false;
91
+ }
92
+
93
+ obj.RunSpellCheck = function () {
94
+ // No resolver → native browser spell-check only.
95
+ if (!config.spellCheckResolver || typeof config.spellCheckResolver !== "function") {
96
+ try {
97
+ var editable = editor.getEditable();
98
+ editable.setAttribute("spellcheck", "true");
99
+ editable.focus();
100
+ } catch (e) { }
101
+ notify((editor.getLangText && editor.getLangText("spellchecknative")) || "Browser spell-check is on — misspellings are underlined as you type.");
102
+ return;
103
+ }
104
+
105
+ var text;
106
+ try { text = editor.getEditable().innerText || ""; } catch (e) { text = ""; }
107
+ if (!text.trim()) { notify("Nothing to check."); return; }
108
+
109
+ var dlg = editor.createDialog(
110
+ (editor.getLangText && editor.getLangText("spellchecktitle")) || "Spelling",
111
+ "rte-dialog-spell"
112
+ );
113
+ var close = typeof dlg.close === "function" ? function () { dlg.close(); } : function () { editor.closeCurrentPopup(); };
114
+ var wrap = append(dlg, "div", "padding:14px;min-width:420px;max-height:60vh;overflow:auto;font:13px -apple-system,Segoe UI,sans-serif");
115
+ var status = append(wrap, "div", "color:#64748b;margin-bottom:8px");
116
+ status.innerText = "Checking…";
117
+ var list = append(wrap, "div", "");
118
+
119
+ function renderIssues(issues) {
120
+ list.innerHTML = "";
121
+ if (!issues || !issues.length) {
122
+ status.innerText = (editor.getLangText && editor.getLangText("spellcheckclean")) || "No spelling issues found.";
123
+ return;
124
+ }
125
+ status.innerText = issues.length + " issue" + (issues.length === 1 ? "" : "s") + " found.";
126
+ issues.forEach(function (iss) {
127
+ if (!iss || !iss.word) return;
128
+ var row = append(list, "div", "padding:8px 0;border-bottom:1px solid rgba(15,23,42,.08)", "rte-spell-row");
129
+ var head = append(row, "div", "font-weight:600;color:#b91c1c;margin-bottom:4px");
130
+ head.textContent = iss.word;
131
+ var sug = append(row, "div", "display:flex;flex-wrap:wrap;gap:6px");
132
+ var suggestions = (iss.suggestions || []).slice(0, 6);
133
+ if (!suggestions.length) {
134
+ append(sug, "span", "font-size:12px;color:#94a3b8").textContent = "(no suggestions)";
135
+ }
136
+ suggestions.forEach(function (s) {
137
+ var chip = append(sug, "button", "font-size:12px;padding:3px 10px;border:1px solid #cbd5e1;border-radius:14px;background:#f8fafc;cursor:pointer");
138
+ chip.type = "button";
139
+ chip.textContent = s;
140
+ chip.onclick = function () {
141
+ if (replaceFirst(iss.word, s)) {
142
+ row.style.opacity = "0.45";
143
+ head.innerHTML = esc(iss.word) + ' &rarr; <span style="color:#16a34a">' + esc(s) + "</span>";
144
+ sug.innerHTML = "";
145
+ }
146
+ };
147
+ });
148
+ });
149
+ }
150
+
151
+ var done = false;
152
+ function finish(issues) { if (done) return; done = true; renderIssues(issues); }
153
+ try {
154
+ var p = config.spellCheckResolver(text);
155
+ if (p && typeof p.then === "function") {
156
+ p.then(function (r) { finish(r || []); }, function () { status.innerText = "Spell-check service error."; });
157
+ setTimeout(function () { if (!done) { status.innerText = "Spell-check timed out."; done = true; } }, 12000);
158
+ } else { finish(p || []); }
159
+ } catch (e) { status.innerText = "Spell-check error."; }
160
+
161
+ var footer = append(wrap, "div", "display:flex;justify-content:flex-end;margin-top:12px");
162
+ var closeBtn = append(footer, "button", "padding:6px 14px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;cursor:pointer");
163
+ closeBtn.type = "button"; closeBtn.textContent = "Done"; closeBtn.onclick = function () { close(); editor.focus(); };
164
+ };
165
+
166
+ function injectStyles() {
167
+ var css = [
168
+ ".rte-spell-toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);background:#0f172a;color:#fff;padding:8px 16px;border-radius:8px;font:13px -apple-system,Segoe UI,sans-serif;z-index:2147483600;box-shadow:0 8px 24px rgba(15,23,42,.28)}",
169
+ ".rte-spell-row:last-child{border-bottom:0}"
170
+ ].join("\n");
171
+ try {
172
+ var host = (editor && editor.iframe && editor.iframe.ownerDocument) || document;
173
+ if (!host.querySelector("style[data-rte-spellcheck]")) {
174
+ var st = host.createElement("style");
175
+ st.setAttribute("data-rte-spellcheck", "1");
176
+ st.textContent = css;
177
+ (host.head || host.documentElement).appendChild(st);
178
+ }
179
+ } catch (e) { /* ignore */ }
180
+ }
181
+ }
@@ -0,0 +1,137 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-06-05 Interactive task / checklist. Notion / GitHub / Google-Docs style:
4
+ // a list whose items each carry a clickable checkbox; clicking toggles the item
5
+ // between open and done (a check + strike-through). Closes a real gap &mdash; the
6
+ // editor shipped an orphan `inserttodolist` toolbar icon with no command behind
7
+ // it. Checkboxes are styled `contenteditable="false"` spans (not <input>s, which
8
+ // misbehave in contenteditable); the done state lives in `data-checked`, so the
9
+ // list round-trips through getHTMLCode and re-binds on load via event delegation.
10
+ RTE_DefaultConfig.plugin_todolist = RTE_Plugin_TodoList;
11
+
12
+ function RTE_Plugin_TodoList() {
13
+ var obj = this;
14
+ var config, editor, editable, editdoc;
15
+
16
+ obj.PluginName = "TodoList";
17
+
18
+ obj.InitConfig = function (argconfig) { config = argconfig; };
19
+
20
+ obj.InitEditor = function (argeditor) {
21
+ editor = argeditor;
22
+ editable = editor.getEditable();
23
+ editdoc = editor.getDocument();
24
+ editor.attachEvent("exec_command_inserttodolist", function (state) {
25
+ state.returnValue = true;
26
+ obj.InsertTaskList();
27
+ });
28
+ if (!editable) return;
29
+ // Toggle on checkbox click (delegated -> works for loaded/pasted lists too).
30
+ editable.addEventListener("click", function (e) {
31
+ var cb = closestClass(e.target, "rte-task-checkbox", editable);
32
+ if (!cb) return;
33
+ var li = cb.parentNode;
34
+ if (!li || !li.classList || !li.classList.contains("rte-task-item")) return;
35
+ var checked = li.getAttribute("data-checked") === "true";
36
+ li.setAttribute("data-checked", checked ? "false" : "true");
37
+ e.preventDefault();
38
+ fireChange();
39
+ });
40
+ // Keep new items (created by Enter) well-formed: a checkbox + an unchecked state.
41
+ 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
+ }
47
+ });
48
+ // Inject CSS once the iframe head exists.
49
+ setTimeout(function () { injectStyles(editdoc); }, 0);
50
+ };
51
+
52
+ function closestClass(node, cls, root) {
53
+ while (node && node !== root) {
54
+ if (node.nodeType === 1 && node.classList && node.classList.contains(cls)) return node;
55
+ node = node.parentNode;
56
+ }
57
+ return null;
58
+ }
59
+ function fireChange() { if (typeof editor.fireChange === "function") { try { editor.fireChange(); } catch (e) {} } }
60
+
61
+ function currentTaskItem() {
62
+ try {
63
+ var s = editor.getSelection();
64
+ if (!s || s.rangeCount === 0) return null;
65
+ var n = s.getRangeAt(0).startContainer;
66
+ return closestClass(n, "rte-task-item", editable);
67
+ } catch (e) { return null; }
68
+ }
69
+
70
+ function injectStyles(doc) {
71
+ if (!doc || doc.getElementById("rte-todolist-styles")) return;
72
+ var css =
73
+ "ul.rte-task-list{list-style:none;margin:.4em 0;padding-left:0;}" +
74
+ "ul.rte-task-list li.rte-task-item{position:relative;padding-left:28px;margin:3px 0;min-height:20px;}" +
75
+ "li.rte-task-item>.rte-task-checkbox{position:absolute;left:0;top:1px;width:18px;height:18px;border:2px solid #94a3b8;" +
76
+ "border-radius:5px;box-sizing:border-box;cursor:pointer;user-select:none;transition:background .12s,border-color .12s;}" +
77
+ "li.rte-task-item>.rte-task-checkbox:hover{border-color:#1d67ba;}" +
78
+ "li.rte-task-item[data-checked='true']>.rte-task-checkbox{background:#1d67ba;border-color:#1d67ba;}" +
79
+ "li.rte-task-item[data-checked='true']>.rte-task-checkbox::after{content:'';position:absolute;left:4px;top:0px;width:6px;height:11px;" +
80
+ "border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);}" +
81
+ "li.rte-task-item[data-checked='true']{color:#94a3b8;text-decoration:line-through;}";
82
+ var st = doc.createElement("style");
83
+ st.id = "rte-todolist-styles";
84
+ st.appendChild(doc.createTextNode(css));
85
+ (doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement).appendChild(st);
86
+ }
87
+
88
+ function makeCheckbox() {
89
+ var cb = editdoc.createElement("span");
90
+ cb.className = "rte-task-checkbox";
91
+ cb.setAttribute("contenteditable", "false");
92
+ return cb;
93
+ }
94
+
95
+ // Ensure every <li> in a task list has a checkbox span + a data-checked value.
96
+ obj.Normalize = function () {
97
+ if (!editable) return;
98
+ var lists = editable.querySelectorAll("ul.rte-task-list");
99
+ for (var i = 0; i < lists.length; i++) {
100
+ var lis = lists[i].children;
101
+ for (var j = 0; j < lis.length; j++) {
102
+ var li = lis[j];
103
+ if (li.nodeName !== "LI") continue;
104
+ li.classList.add("rte-task-item");
105
+ if (li.getAttribute("data-checked") !== "true" && li.getAttribute("data-checked") !== "false")
106
+ li.setAttribute("data-checked", "false");
107
+ var first = li.firstChild;
108
+ if (!(first && first.nodeType === 1 && first.classList && first.classList.contains("rte-task-checkbox")))
109
+ li.insertBefore(makeCheckbox(), li.firstChild);
110
+ }
111
+ }
112
+ };
113
+
114
+ obj.InsertTaskList = function () {
115
+ injectStyles(editdoc);
116
+ var pendingId = "rte-todo-pending";
117
+ var html = '<ul class="rte-task-list"><li class="rte-task-item" data-checked="false" id="' + pendingId + '">' +
118
+ '<span class="rte-task-checkbox" contenteditable="false"></span>&#8203;</li></ul>';
119
+ editor.insertHTML(html);
120
+ try {
121
+ var li = editdoc.getElementById(pendingId);
122
+ if (li) {
123
+ li.removeAttribute("id");
124
+ // place caret right after the checkbox (in the zero-width text node)
125
+ var r = editdoc.createRange();
126
+ var textNode = li.lastChild; // the &#8203; text node
127
+ if (textNode && textNode.nodeType === 3) r.setStart(textNode, textNode.length);
128
+ else r.setStart(li, li.childNodes.length);
129
+ r.collapse(true);
130
+ var s = editor.getSelection();
131
+ if (s) { s.removeAllRanges(); s.addRange(r); }
132
+ }
133
+ } catch (e) { /* ignore */ }
134
+ editor.focus();
135
+ fireChange();
136
+ };
137
+ }