@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.
- package/package.json +1 -1
- package/richtexteditor/all_plugins.js +2583 -719
- package/richtexteditor/all_plugins.min.js +59 -58
- package/richtexteditor/plugins/accessibilitychecker.js +7 -0
- package/richtexteditor/plugins/all_plugins.js +2583 -719
- package/richtexteditor/plugins/all_plugins.min.js +59 -58
- package/richtexteditor/plugins/autoembed.js +7 -0
- package/richtexteditor/plugins/blocktypes.js +7 -0
- package/richtexteditor/plugins/bookmarkcard.js +7 -0
- package/richtexteditor/plugins/charlimit.js +7 -0
- package/richtexteditor/plugins/comments.js +14 -0
- package/richtexteditor/plugins/contentminimap.js +7 -0
- package/richtexteditor/plugins/crossreference.js +7 -0
- package/richtexteditor/plugins/dictation.js +7 -0
- package/richtexteditor/plugins/documentoutline.js +9 -3
- package/richtexteditor/plugins/draghandle.js +7 -0
- package/richtexteditor/plugins/foldheadings.js +7 -0
- package/richtexteditor/plugins/footnotes.js +7 -0
- package/richtexteditor/plugins/formatpainter.js +7 -0
- package/richtexteditor/plugins/formattingmarks.js +7 -0
- package/richtexteditor/plugins/gapcursor.js +7 -0
- package/richtexteditor/plugins/ghostcomplete.js +7 -0
- package/richtexteditor/plugins/layouttable.js +7 -0
- package/richtexteditor/plugins/linenumbers.js +7 -0
- package/richtexteditor/plugins/linkchecker.js +7 -0
- package/richtexteditor/plugins/mentionplugin.js +14 -0
- package/richtexteditor/plugins/mergefields.js +7 -0
- package/richtexteditor/plugins/mermaiddiagram.js +7 -0
- package/richtexteditor/plugins/multilevellist.js +7 -0
- package/richtexteditor/plugins/nodetypes.js +439 -0
- package/richtexteditor/plugins/pagination.js +7 -0
- package/richtexteditor/plugins/restrictedediting.js +7 -0
- package/richtexteditor/plugins/revisionhistory.js +7 -0
- package/richtexteditor/plugins/rtlui.js +7 -0
- package/richtexteditor/plugins/rubyannotation.js +255 -0
- package/richtexteditor/plugins/slashcommand.js +7 -0
- package/richtexteditor/plugins/smartchips.js +7 -0
- package/richtexteditor/plugins/spellcheck.js +7 -0
- package/richtexteditor/plugins/stateinspector.js +866 -0
- package/richtexteditor/plugins/tableofcontents.js +7 -0
- package/richtexteditor/plugins/textdirection.js +7 -0
- package/richtexteditor/plugins/textpartlanguage.js +7 -0
- package/richtexteditor/plugins/todolist.js +7 -0
- package/richtexteditor/plugins/trackedchanges.js +730 -716
- package/richtexteditor/plugins/typewriter.js +7 -0
- package/richtexteditor/plugins/watermark.js +15 -0
- package/richtexteditor/plugins/yjscollab.js +7 -0
- 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, "&").replace(/</g, "<")
|
|
148
|
+
.replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
}
|