@richscripts/richtexteditor 2.7.2 → 2.9.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 (48) hide show
  1. package/package.json +1 -1
  2. package/richtexteditor/all_plugins.js +2583 -719
  3. package/richtexteditor/all_plugins.min.js +59 -58
  4. package/richtexteditor/plugins/accessibilitychecker.js +7 -0
  5. package/richtexteditor/plugins/all_plugins.js +2583 -719
  6. package/richtexteditor/plugins/all_plugins.min.js +59 -58
  7. package/richtexteditor/plugins/autoembed.js +7 -0
  8. package/richtexteditor/plugins/blocktypes.js +7 -0
  9. package/richtexteditor/plugins/bookmarkcard.js +7 -0
  10. package/richtexteditor/plugins/charlimit.js +7 -0
  11. package/richtexteditor/plugins/comments.js +14 -0
  12. package/richtexteditor/plugins/contentminimap.js +7 -0
  13. package/richtexteditor/plugins/crossreference.js +7 -0
  14. package/richtexteditor/plugins/dictation.js +7 -0
  15. package/richtexteditor/plugins/documentoutline.js +9 -3
  16. package/richtexteditor/plugins/draghandle.js +7 -0
  17. package/richtexteditor/plugins/foldheadings.js +7 -0
  18. package/richtexteditor/plugins/footnotes.js +7 -0
  19. package/richtexteditor/plugins/formatpainter.js +7 -0
  20. package/richtexteditor/plugins/formattingmarks.js +7 -0
  21. package/richtexteditor/plugins/gapcursor.js +7 -0
  22. package/richtexteditor/plugins/ghostcomplete.js +7 -0
  23. package/richtexteditor/plugins/layouttable.js +7 -0
  24. package/richtexteditor/plugins/linenumbers.js +7 -0
  25. package/richtexteditor/plugins/linkchecker.js +7 -0
  26. package/richtexteditor/plugins/mentionplugin.js +14 -0
  27. package/richtexteditor/plugins/mergefields.js +7 -0
  28. package/richtexteditor/plugins/mermaiddiagram.js +7 -0
  29. package/richtexteditor/plugins/multilevellist.js +7 -0
  30. package/richtexteditor/plugins/nodetypes.js +439 -0
  31. package/richtexteditor/plugins/pagination.js +7 -0
  32. package/richtexteditor/plugins/restrictedediting.js +7 -0
  33. package/richtexteditor/plugins/revisionhistory.js +7 -0
  34. package/richtexteditor/plugins/rtlui.js +7 -0
  35. package/richtexteditor/plugins/rubyannotation.js +255 -0
  36. package/richtexteditor/plugins/slashcommand.js +7 -0
  37. package/richtexteditor/plugins/smartchips.js +7 -0
  38. package/richtexteditor/plugins/spellcheck.js +7 -0
  39. package/richtexteditor/plugins/stateinspector.js +866 -0
  40. package/richtexteditor/plugins/tableofcontents.js +7 -0
  41. package/richtexteditor/plugins/textdirection.js +7 -0
  42. package/richtexteditor/plugins/textpartlanguage.js +7 -0
  43. package/richtexteditor/plugins/todolist.js +7 -0
  44. package/richtexteditor/plugins/trackedchanges.js +730 -716
  45. package/richtexteditor/plugins/typewriter.js +7 -0
  46. package/richtexteditor/plugins/watermark.js +15 -0
  47. package/richtexteditor/plugins/yjscollab.js +7 -0
  48. package/richtexteditor/rte.js +2 -2
@@ -0,0 +1,255 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ RTE_DefaultConfig.plugin_rubyannotation = RTE_Plugin_RubyAnnotation;
4
+
5
+ // 2026-09-04 Ruby annotation (phonetic guides above base text).
6
+ //
7
+ // Furigana in Japanese, pinyin/bopomofo in Chinese, ruby glosses in academic
8
+ // typesetting. Word has it, Lexical ships a RubyExtension, and it was the one
9
+ // item in the Lexical feature gap register worth scheduling: small, self
10
+ // contained, and an i18n/accessibility feature rather than a novelty.
11
+ //
12
+ // The markup is standard HTML, not an invention:
13
+ //
14
+ // <ruby>漢字<rp>(</rp><rt>かんじ</rt><rp>)</rp></ruby>
15
+ //
16
+ // The <rp> parentheses are the accessibility half and are why they are emitted
17
+ // by default: a browser or assistive technology with no ruby support falls back
18
+ // to rendering "漢字(かんじ)" inline instead of running the reading straight
19
+ // into the base text as "漢字かんじ". Copy-paste to a plain-text target gets the
20
+ // same readable fallback.
21
+ //
22
+ // ruby/rt/rp are ALREADY in the sanitizer allowlist (checked, not assumed), so
23
+ // the markup survives sanitisation with no allowlist change.
24
+ function RTE_Plugin_RubyAnnotation() {
25
+ var obj = this;
26
+ var config;
27
+ var editor;
28
+
29
+ obj.PluginName = "RubyAnnotation";
30
+
31
+ obj.InitConfig = function (argconfig) {
32
+ config = argconfig;
33
+ if (config.rubyAnnotationEnabled === false) return;
34
+
35
+ if (typeof config.rubyAnnotationParentheses !== "boolean") config.rubyAnnotationParentheses = true;
36
+
37
+ // BOTH toolbars. config.toolbar defaults to "default", which resolves to
38
+ // toolbar_default, so appending only to toolbar_full would leave the
39
+ // button unreachable for every host that never configured a toolbar -
40
+ // the mistake that shipped inlinecode invisible for a whole release.
41
+ // The default toolbar gets it in the overflow set, where a niche
42
+ // typographic feature belongs; toolbar_full gets it inline.
43
+ appendToolbarCommand("toolbar_full", "#{rubyannotation}");
44
+ appendSubToolbarCommand("subtoolbar_more", "rubyannotation");
45
+ };
46
+
47
+ obj.InitEditor = function (argeditor) {
48
+ editor = argeditor;
49
+ if (config.rubyAnnotationEnabled === false) return;
50
+
51
+ editor.ruby = {
52
+ add: function (reading) { return applyRuby(reading); },
53
+ get: function () { var r = findRubyAtSelection(); return r ? readingOf(r) : null; },
54
+ isRuby: function () { return !!findRubyAtSelection(); },
55
+ open: function () { obj.OpenRubyDialog(); },
56
+ remove: function () { return removeRuby(); }
57
+ };
58
+
59
+ injectStyles();
60
+
61
+ editor.toolbarFactoryMap = editor.toolbarFactoryMap || {};
62
+ editor.toolbarFactoryMap["rubyannotation"] = function (cmd) {
63
+ return editor.createToolbarButton(cmd);
64
+ };
65
+
66
+ editor.attachEvent("exec_command_rubyannotation", function (state) {
67
+ state.returnValue = true;
68
+ state.stopBubble = true;
69
+ obj.OpenRubyDialog();
70
+ });
71
+ };
72
+
73
+ function appendToolbarCommand(toolbar, item) {
74
+ if (!config[toolbar]) return;
75
+ if (config[toolbar].indexOf(item) !== -1) return;
76
+ config[toolbar] = config[toolbar] + item;
77
+ }
78
+
79
+ // subtoolbar_* entries are a braced, comma-separated list rather than the
80
+ // "#{name}" form the main toolbars use.
81
+ function appendSubToolbarCommand(toolbar, name) {
82
+ var val = config[toolbar];
83
+ if (!val || val.indexOf(name) !== -1) return;
84
+ var i = val.indexOf("}");
85
+ if (i === -1) return;
86
+ config[toolbar] = val.substring(0, i) + "," + name + val.substring(i);
87
+ }
88
+
89
+ // Ruby has no default styling worth relying on: browsers vary in whether the
90
+ // reading is sized down at all, and <rp> must be hidden wherever ruby DOES
91
+ // render or the parentheses show up twice.
92
+ function injectStyles() {
93
+ var hostDoc = config.container.ownerDocument;
94
+ if (hostDoc.getElementById("rte-ruby-style")) return;
95
+ var style = hostDoc.createElement("style");
96
+ style.id = "rte-ruby-style";
97
+ style.innerHTML = [
98
+ ".rte-ruby-annotated{ruby-position:over;}",
99
+ ".rte-ruby-annotated>rt{font-size:.55em;line-height:1.15;opacity:.85;user-select:none;}",
100
+ "@supports (display:ruby){.rte-ruby-annotated>rp{display:none;}}"
101
+ ].join("");
102
+ hostDoc.head.appendChild(style);
103
+ }
104
+
105
+ function editableRoot() {
106
+ return editor && editor.getEditable ? editor.getEditable() : null;
107
+ }
108
+
109
+ function currentRange() {
110
+ var doc = editor.getDocument();
111
+ try {
112
+ var sel = doc.getSelection();
113
+ if (!sel || !sel.rangeCount) return null;
114
+ return sel.getRangeAt(0);
115
+ } catch (e) { return null; }
116
+ }
117
+
118
+ function findRubyAtSelection() {
119
+ var range = currentRange();
120
+ var root = editableRoot();
121
+ if (!range || !root) return null;
122
+ var node = range.startContainer;
123
+ while (node && node !== root) {
124
+ if (node.nodeType === 1 && node.nodeName === "RUBY") return node;
125
+ node = node.parentNode;
126
+ }
127
+ return null;
128
+ }
129
+
130
+ function readingOf(rubyEl) {
131
+ var rt = rubyEl.querySelector("rt");
132
+ return rt ? (rt.textContent || "") : "";
133
+ }
134
+
135
+ function baseOf(rubyEl) {
136
+ var out = "";
137
+ for (var i = 0; i < rubyEl.childNodes.length; i++) {
138
+ var n = rubyEl.childNodes[i];
139
+ if (n.nodeType === 3) out += n.nodeValue;
140
+ else if (n.nodeType === 1 && n.nodeName !== "RT" && n.nodeName !== "RP") out += n.textContent || "";
141
+ }
142
+ return out;
143
+ }
144
+
145
+ function esc(s) {
146
+ return String(s == null ? "" : s)
147
+ .replace(/&/g, "&amp;").replace(/</g, "&lt;")
148
+ .replace(/>/g, "&gt;").replace(/"/g, "&quot;");
149
+ }
150
+
151
+ function buildRuby(base, reading) {
152
+ var parens = config.rubyAnnotationParentheses;
153
+ return '<ruby class="rte-ruby-annotated">' + esc(base) +
154
+ (parens ? "<rp>(</rp>" : "") +
155
+ "<rt>" + esc(reading) + "</rt>" +
156
+ (parens ? "<rp>)</rp>" : "") +
157
+ "</ruby>";
158
+ }
159
+
160
+ function applyRuby(reading) {
161
+ var existing = findRubyAtSelection();
162
+ if (existing) {
163
+ var rt = existing.querySelector("rt");
164
+ if (!rt) {
165
+ rt = existing.ownerDocument.createElement("rt");
166
+ existing.appendChild(rt);
167
+ }
168
+ rt.textContent = String(reading == null ? "" : reading);
169
+ editor.focus();
170
+ return existing;
171
+ }
172
+ var range = currentRange();
173
+ if (!range || range.collapsed) return null;
174
+ var base = String(range.toString() || "");
175
+ if (!base) return null;
176
+ editor.insertHTML(buildRuby(base, reading));
177
+ editor.focus();
178
+ return true;
179
+ }
180
+
181
+ // Unwrap back to the base text, discarding the reading and the <rp> fallback
182
+ // parentheses. Without dropping <rp> the text would read "漢字()".
183
+ function removeRuby() {
184
+ var rubyEl = findRubyAtSelection();
185
+ if (!rubyEl) return false;
186
+ var doc = rubyEl.ownerDocument;
187
+ var text = doc.createTextNode(baseOf(rubyEl));
188
+ rubyEl.parentNode.insertBefore(text, rubyEl);
189
+ rubyEl.parentNode.removeChild(rubyEl);
190
+ editor.focus();
191
+ return true;
192
+ }
193
+
194
+ function append(parent, tag, cssText, className) {
195
+ var el = parent.ownerDocument.createElement(tag);
196
+ if (cssText) el.style.cssText = cssText;
197
+ if (className) el.className = className;
198
+ parent.appendChild(el);
199
+ return el;
200
+ }
201
+
202
+ obj.OpenRubyDialog = function () {
203
+ var existing = findRubyAtSelection();
204
+ var range = currentRange();
205
+ var baseText = existing ? baseOf(existing) : (range ? String(range.toString() || "") : "");
206
+
207
+ var dlg = editor.createDialog(
208
+ (editor.getLangText && editor.getLangText("rubyannotationtitle")) || "Ruby annotation",
209
+ "rte-dialog-ruby"
210
+ );
211
+ var close = typeof dlg.close === "function" ? function () { dlg.close(); } : function () { editor.closeCurrentPopup(); };
212
+
213
+ var wrap = append(dlg, "div", "padding:14px;min-width:340px;font:13px -apple-system,Segoe UI,sans-serif");
214
+
215
+ append(wrap, "div", "font-weight:600;margin-bottom:4px").innerText = "Base text";
216
+ var baseView = append(wrap, "div",
217
+ "padding:8px 10px;border:1px solid #e2e8f0;border-radius:8px;background:#f8fafc;min-height:20px;word-break:break-word");
218
+ baseView.textContent = baseText;
219
+
220
+ append(wrap, "div", "font-weight:600;margin:10px 0 4px").innerText = "Reading";
221
+ var input = append(wrap, "input",
222
+ "width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid #cbd5e1;border-radius:8px;font:13px inherit");
223
+ input.type = "text";
224
+ input.value = existing ? readingOf(existing) : "";
225
+ input.placeholder = "e.g. かんじ";
226
+
227
+ var note = append(wrap, "div", "font-size:11px;color:#94a3b8;margin-top:6px");
228
+ note.innerText = baseText
229
+ ? "Shown above the base text. Parentheses are emitted for readers without ruby support."
230
+ : "Select the text to annotate first, then reopen this dialog.";
231
+
232
+ var footer = append(wrap, "div", "display:flex;justify-content:space-between;gap:8px;margin-top:14px");
233
+ var left = append(footer, "div", "display:flex;gap:8px");
234
+ if (existing) {
235
+ var rm = append(left, "button", "padding:6px 14px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;color:#b91c1c;cursor:pointer");
236
+ rm.type = "button"; rm.textContent = "Remove";
237
+ rm.onclick = function () { removeRuby(); close(); };
238
+ }
239
+ var right = append(footer, "div", "display:flex;gap:8px");
240
+ var cancel = append(right, "button", "padding:6px 14px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;cursor:pointer");
241
+ cancel.type = "button"; cancel.textContent = "Cancel"; cancel.onclick = close;
242
+ var ok = append(right, "button", "padding:6px 14px;border:1px solid #1d67ba;border-radius:8px;background:#1d67ba;color:#fff;cursor:pointer");
243
+ ok.type = "button"; ok.textContent = existing ? "Update" : "Add";
244
+ ok.disabled = !baseText;
245
+
246
+ ok.onclick = function () {
247
+ var reading = input.value.trim();
248
+ if (!reading || !baseText) { close(); return; }
249
+ applyRuby(reading);
250
+ close();
251
+ };
252
+
253
+ setTimeout(function () { input.focus(); }, 0);
254
+ };
255
+ }
@@ -850,6 +850,13 @@ function RTE_Plugin_SlashCommand() {
850
850
  ".rte-slash-popup-dark .rte-slash-empty{color:#94a3b8}"
851
851
  ].join("\n");
852
852
  host.head.appendChild(style);
853
+ // A <style> ELEMENT is governed by style-src: under a strict policy the browser
854
+ // drops its rules, leaving this plugin's UI unstyled. The element above stays
855
+ // the path everyone else takes; this re-injects through the CSSOM only when
856
+ // the policy actually emptied it.
857
+ if (editor && typeof editor.ensureStyleSheetLive === "function") {
858
+ editor.ensureStyleSheetLive(host, style, "rte-slashcommand-style", style.textContent);
859
+ }
853
860
  }
854
861
 
855
862
  // --- Icons (minimal inline SVGs) ---
@@ -244,6 +244,13 @@ function RTE_Plugin_SmartChips() {
244
244
  st.setAttribute("data-rte-smartchips", "1");
245
245
  st.textContent = css;
246
246
  head.appendChild(st);
247
+ // A <style> ELEMENT is governed by style-src: under a strict policy the browser
248
+ // drops its rules, leaving this plugin's UI unstyled. The element above stays
249
+ // the path everyone else takes; this re-injects through the CSSOM only when
250
+ // the policy actually emptied it.
251
+ if (editor && typeof editor.ensureStyleSheetLive === "function") {
252
+ editor.ensureStyleSheetLive(editdoc, st, "rte-smartchips-st", st.textContent);
253
+ }
247
254
  }
248
255
  } catch (e) { /* ignore */ }
249
256
  }
@@ -178,6 +178,13 @@ function RTE_Plugin_SpellCheck() {
178
178
  st.setAttribute("data-rte-spellcheck", "1");
179
179
  st.textContent = css;
180
180
  (host.head || host.documentElement).appendChild(st);
181
+ // A <style> ELEMENT is governed by style-src: under a strict policy the browser
182
+ // drops its rules, leaving this plugin's UI unstyled. The element above stays
183
+ // the path everyone else takes; this re-injects through the CSSOM only when
184
+ // the policy actually emptied it.
185
+ if (editor && typeof editor.ensureStyleSheetLive === "function") {
186
+ editor.ensureStyleSheetLive(host, st, "rte-spellcheck-st", st.textContent);
187
+ }
181
188
  }
182
189
  } catch (e) { /* ignore */ }
183
190
  }