@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.
- package/README.md +360 -0
- package/admin/index.d.ts +100 -0
- package/admin/index.js +94 -0
- package/angular/component.ts +284 -0
- package/angular/index.d.ts +80 -0
- package/angular/index.ts +24 -0
- package/headless/index.d.ts +65 -0
- package/headless/index.js +272 -0
- package/index.d.ts +1412 -0
- package/integrations/shared.d.ts +138 -0
- package/integrations/shared.js +5182 -0
- package/package.json +130 -0
- package/react/index.d.ts +104 -0
- package/react/index.js +310 -0
- package/richtexteditor/CHANGELOG.md +1876 -0
- package/richtexteditor/all_plugins.js +31125 -0
- package/richtexteditor/all_plugins.min.js +78 -0
- package/richtexteditor/lang/rte-lang-af.js +193 -0
- package/richtexteditor/lang/rte-lang-ar.js +193 -0
- package/richtexteditor/lang/rte-lang-bg.js +193 -0
- package/richtexteditor/lang/rte-lang-bn.js +193 -0
- package/richtexteditor/lang/rte-lang-bs.js +193 -0
- package/richtexteditor/lang/rte-lang-ca.js +193 -0
- package/richtexteditor/lang/rte-lang-cs.js +193 -0
- package/richtexteditor/lang/rte-lang-cy.js +193 -0
- package/richtexteditor/lang/rte-lang-da.js +193 -0
- package/richtexteditor/lang/rte-lang-de.js +193 -0
- package/richtexteditor/lang/rte-lang-el.js +193 -0
- package/richtexteditor/lang/rte-lang-en.js +6 -0
- package/richtexteditor/lang/rte-lang-es.js +193 -0
- package/richtexteditor/lang/rte-lang-et.js +193 -0
- package/richtexteditor/lang/rte-lang-fa.js +193 -0
- package/richtexteditor/lang/rte-lang-fi.js +193 -0
- package/richtexteditor/lang/rte-lang-fil.js +193 -0
- package/richtexteditor/lang/rte-lang-fj.js +193 -0
- package/richtexteditor/lang/rte-lang-fr.js +193 -0
- package/richtexteditor/lang/rte-lang-ga.js +193 -0
- package/richtexteditor/lang/rte-lang-gu.js +193 -0
- package/richtexteditor/lang/rte-lang-he.js +193 -0
- package/richtexteditor/lang/rte-lang-hi.js +193 -0
- package/richtexteditor/lang/rte-lang-hr.js +193 -0
- package/richtexteditor/lang/rte-lang-ht.js +193 -0
- package/richtexteditor/lang/rte-lang-hu.js +193 -0
- package/richtexteditor/lang/rte-lang-id.js +193 -0
- package/richtexteditor/lang/rte-lang-is.js +193 -0
- package/richtexteditor/lang/rte-lang-it.js +193 -0
- package/richtexteditor/lang/rte-lang-ja.js +193 -0
- package/richtexteditor/lang/rte-lang-kn.js +193 -0
- package/richtexteditor/lang/rte-lang-ko.js +193 -0
- package/richtexteditor/lang/rte-lang-lt.js +193 -0
- package/richtexteditor/lang/rte-lang-lv.js +193 -0
- package/richtexteditor/lang/rte-lang-mg.js +193 -0
- package/richtexteditor/lang/rte-lang-mi.js +193 -0
- package/richtexteditor/lang/rte-lang-ml.js +193 -0
- package/richtexteditor/lang/rte-lang-mr.js +193 -0
- package/richtexteditor/lang/rte-lang-ms.js +193 -0
- package/richtexteditor/lang/rte-lang-mt.js +193 -0
- package/richtexteditor/lang/rte-lang-mww.js +193 -0
- package/richtexteditor/lang/rte-lang-nb.js +193 -0
- package/richtexteditor/lang/rte-lang-nl.js +193 -0
- package/richtexteditor/lang/rte-lang-otq.js +193 -0
- package/richtexteditor/lang/rte-lang-pa.js +193 -0
- package/richtexteditor/lang/rte-lang-pl.js +193 -0
- package/richtexteditor/lang/rte-lang-pt-pt.js +193 -0
- package/richtexteditor/lang/rte-lang-pt.js +193 -0
- package/richtexteditor/lang/rte-lang-ro.js +193 -0
- package/richtexteditor/lang/rte-lang-ru.js +193 -0
- package/richtexteditor/lang/rte-lang-sk.js +193 -0
- package/richtexteditor/lang/rte-lang-sl.js +193 -0
- package/richtexteditor/lang/rte-lang-sm.js +193 -0
- package/richtexteditor/lang/rte-lang-sr-Cyrl.js +193 -0
- package/richtexteditor/lang/rte-lang-sr-Latn.js +193 -0
- package/richtexteditor/lang/rte-lang-sv.js +193 -0
- package/richtexteditor/lang/rte-lang-sw.js +193 -0
- package/richtexteditor/lang/rte-lang-ta.js +193 -0
- package/richtexteditor/lang/rte-lang-te.js +193 -0
- package/richtexteditor/lang/rte-lang-th.js +193 -0
- package/richtexteditor/lang/rte-lang-tlh-Latn.js +193 -0
- package/richtexteditor/lang/rte-lang-tlh-Piqd.js +193 -0
- package/richtexteditor/lang/rte-lang-to.js +193 -0
- package/richtexteditor/lang/rte-lang-tr.js +193 -0
- package/richtexteditor/lang/rte-lang-ty.js +193 -0
- package/richtexteditor/lang/rte-lang-uk.js +193 -0
- package/richtexteditor/lang/rte-lang-ur.js +193 -0
- package/richtexteditor/lang/rte-lang-vi.js +193 -0
- package/richtexteditor/lang/rte-lang-yua.js +193 -0
- package/richtexteditor/lang/rte-lang-zh-Hans.js +193 -0
- package/richtexteditor/lang/rte-lang-zh-Hant.js +193 -0
- package/richtexteditor/lang/rte-lang.js +1 -0
- package/richtexteditor/plugins/-combine-plugin.txt +2 -0
- package/richtexteditor/plugins/-combine-plugins.bat +20 -0
- package/richtexteditor/plugins/a11yenhance.js +90 -0
- package/richtexteditor/plugins/accessibilitychecker.js +579 -0
- package/richtexteditor/plugins/aitoolkit.css +15830 -0
- package/richtexteditor/plugins/aitoolkit.js +19171 -0
- package/richtexteditor/plugins/all_plugins.js +31125 -0
- package/richtexteditor/plugins/all_plugins.min.js +78 -0
- package/richtexteditor/plugins/autoembed.js +174 -0
- package/richtexteditor/plugins/blocktypes.js +175 -0
- package/richtexteditor/plugins/bookmarkcard.js +226 -0
- package/richtexteditor/plugins/charlimit.js +195 -0
- package/richtexteditor/plugins/comments.js +672 -0
- package/richtexteditor/plugins/contentminimap.js +369 -0
- package/richtexteditor/plugins/crdt-engine.js +8905 -0
- package/richtexteditor/plugins/crdt-engine.min.js +5 -0
- package/richtexteditor/plugins/dialoga11y.js +174 -0
- package/richtexteditor/plugins/dictation.js +268 -0
- package/richtexteditor/plugins/documentimport.js +369 -0
- package/richtexteditor/plugins/documentoutline.js +341 -0
- package/richtexteditor/plugins/emailtoolkit.js +165 -0
- package/richtexteditor/plugins/emojiautocomplete.js +241 -0
- package/richtexteditor/plugins/foldheadings.js +181 -0
- package/richtexteditor/plugins/ghostcomplete.js +297 -0
- package/richtexteditor/plugins/html2pdf.js +158 -0
- package/richtexteditor/plugins/imageeditor.js +159 -0
- package/richtexteditor/plugins/insertcode.js +319 -0
- package/richtexteditor/plugins/insertemoji.js +229 -0
- package/richtexteditor/plugins/insertgallery.js +362 -0
- package/richtexteditor/plugins/inserttemplate.js +261 -0
- package/richtexteditor/plugins/localdraft.js +213 -0
- package/richtexteditor/plugins/markdownexport.js +110 -0
- package/richtexteditor/plugins/mathdialog.js +153 -0
- package/richtexteditor/plugins/mentionplugin.js +534 -0
- package/richtexteditor/plugins/mermaiddiagram.js +294 -0
- package/richtexteditor/plugins/readabilitystats.js +148 -0
- package/richtexteditor/plugins/readaloud.js +243 -0
- package/richtexteditor/plugins/restrictedediting.js +208 -0
- package/richtexteditor/plugins/revisionhistory.js +670 -0
- package/richtexteditor/plugins/samples/insertcode_v1.js +76 -0
- package/richtexteditor/plugins/slashcommand.js +705 -0
- package/richtexteditor/plugins/smartchips.js +250 -0
- package/richtexteditor/plugins/spellcheck.js +181 -0
- package/richtexteditor/plugins/todolist.js +137 -0
- package/richtexteditor/plugins/trackedchanges.js +658 -0
- package/richtexteditor/plugins/tui.image-editor/fabric.min.js +8 -0
- package/richtexteditor/plugins/tui.image-editor/note.txt +5 -0
- package/richtexteditor/plugins/tui.image-editor/svg/icon-a.svg +235 -0
- package/richtexteditor/plugins/tui.image-editor/svg/icon-b.svg +224 -0
- package/richtexteditor/plugins/tui.image-editor/svg/icon-c.svg +224 -0
- package/richtexteditor/plugins/tui.image-editor/svg/icon-d.svg +224 -0
- package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +7 -0
- package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.css +6 -0
- package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +7 -0
- package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.css +7 -0
- package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +15 -0
- package/richtexteditor/plugins/typewriter.js +203 -0
- package/richtexteditor/plugins/wordexport.js +0 -0
- package/richtexteditor/plugins/yjscollab.js +693 -0
- package/richtexteditor/plugins/zz_richtextbox_dialog_style.js +335 -0
- package/richtexteditor/rte-config.js +933 -0
- package/richtexteditor/rte.js +943 -0
- package/richtexteditor/rte_mobile.css +234 -0
- package/richtexteditor/rte_theme_default.css +4819 -0
- package/richtexteditor/runtime/colorpicker_more_ns.htm +2613 -0
- package/richtexteditor/runtime/help.htm +162 -0
- package/richtexteditor/runtime/images/1x1.gif +0 -0
- package/richtexteditor/runtime/images/cpie_Color.cur +0 -0
- package/richtexteditor/runtime/images/cpie_GradientPositionDark.gif +0 -0
- package/richtexteditor/runtime/images/cpie_GradientPositionLight.gif +0 -0
- package/richtexteditor/runtime/images/cpie_VerticalPosition.gif +0 -0
- package/richtexteditor/runtime/images/cpie_WebSafe.gif +0 -0
- package/richtexteditor/runtime/images/cpie_gradients.png +0 -0
- package/richtexteditor/runtime/images/cpns_Color.cur +0 -0
- package/richtexteditor/runtime/images/cpns_ColorSpace1.png +0 -0
- package/richtexteditor/runtime/images/cpns_ColorSpace2.png +0 -0
- package/richtexteditor/runtime/images/cpns_GradientPositionDark.gif +0 -0
- package/richtexteditor/runtime/images/cpns_GradientPositionLight.gif +0 -0
- package/richtexteditor/runtime/images/cpns_Vertical1.png +0 -0
- package/richtexteditor/runtime/images/cpns_Vertical2.png +0 -0
- package/richtexteditor/runtime/images/cpns_VerticalPosition.gif +0 -0
- package/richtexteditor/runtime/images/cpns_WebSafe.gif +0 -0
- package/richtexteditor/runtime/images/cpns_gradients.png +0 -0
- package/richtexteditor/runtime/images/cpns_hue2.png +0 -0
- package/richtexteditor/runtime/images/formbn.gif +0 -0
- package/richtexteditor/runtime/images/multiclavier.gif +0 -0
- package/richtexteditor/runtime/richtexteditor_content.css +362 -0
- package/richtexteditor/runtime/richtexteditor_preview.css +302 -0
- package/richtexteditor/runtime/richtexteditor_preview.js +1 -0
- package/server/index.d.ts +21 -0
- package/server/index.js +23 -0
- package/svelte/index.d.ts +19 -0
- package/svelte/index.js +59 -0
- package/vue/index.d.ts +94 -0
- package/vue/index.js +291 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
|
|
2
|
+
|
|
3
|
+
RTE_DefaultConfig.plugin_slashcommand = RTE_Plugin_SlashCommand;
|
|
4
|
+
|
|
5
|
+
function RTE_Plugin_SlashCommand() {
|
|
6
|
+
var obj = this;
|
|
7
|
+
var config;
|
|
8
|
+
var editor;
|
|
9
|
+
var popupHost = null;
|
|
10
|
+
var popupEl = null;
|
|
11
|
+
|
|
12
|
+
var trigger = null;
|
|
13
|
+
var commands = [];
|
|
14
|
+
var filtered = [];
|
|
15
|
+
var activeIndex = 0;
|
|
16
|
+
var composing = false;
|
|
17
|
+
var suppressNextOpen = false;
|
|
18
|
+
|
|
19
|
+
obj.PluginName = "SlashCommand";
|
|
20
|
+
|
|
21
|
+
obj.InitConfig = function (argconfig) {
|
|
22
|
+
config = argconfig;
|
|
23
|
+
if (config.slashCommandEnabled === false) return;
|
|
24
|
+
|
|
25
|
+
config.slashCommandTrigger = config.slashCommandTrigger || "/";
|
|
26
|
+
config.slashCommandMaxItems = config.slashCommandMaxItems || 40;
|
|
27
|
+
config.slashCommandIncludeAi = config.slashCommandIncludeAi !== false;
|
|
28
|
+
config.slashCommands = config.slashCommands || null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
obj.InitEditor = function (argeditor) {
|
|
32
|
+
editor = argeditor;
|
|
33
|
+
if (config.slashCommandEnabled === false) return;
|
|
34
|
+
|
|
35
|
+
editor.slashCommands = {
|
|
36
|
+
register: registerCommand,
|
|
37
|
+
remove: removeCommand,
|
|
38
|
+
list: function () { return commands.slice(); },
|
|
39
|
+
open: function () { openPopup(true); },
|
|
40
|
+
close: closePopup,
|
|
41
|
+
isOpen: function () { return !!popupEl; }
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
commands = buildDefaultCommands();
|
|
45
|
+
if (Array.isArray(config.slashCommands)) {
|
|
46
|
+
for (var i = 0; i < config.slashCommands.length; i++) {
|
|
47
|
+
registerCommand(config.slashCommands[i]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
injectStyles();
|
|
52
|
+
|
|
53
|
+
var editdoc = editor.getDocument();
|
|
54
|
+
editdoc.addEventListener("keydown", onEditDocKeyDown, true);
|
|
55
|
+
editdoc.addEventListener("input", onEditDocInput, true);
|
|
56
|
+
editdoc.addEventListener("compositionstart", function () { composing = true; });
|
|
57
|
+
editdoc.addEventListener("compositionend", function () { composing = false; });
|
|
58
|
+
editdoc.addEventListener("mousedown", function () { closePopup(); }, true);
|
|
59
|
+
editdoc.addEventListener("blur", function () { setTimeout(closePopup, 150); }, true);
|
|
60
|
+
|
|
61
|
+
window.addEventListener("scroll", onHostScrollOrResize, true);
|
|
62
|
+
window.addEventListener("resize", onHostScrollOrResize, true);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function registerCommand(def) {
|
|
66
|
+
if (!def || !def.id) return;
|
|
67
|
+
var existing = findCommandIndex(def.id);
|
|
68
|
+
if (existing >= 0) commands[existing] = normalizeCommand(def);
|
|
69
|
+
else commands.push(normalizeCommand(def));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function removeCommand(id) {
|
|
73
|
+
var idx = findCommandIndex(id);
|
|
74
|
+
if (idx >= 0) commands.splice(idx, 1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function findCommandIndex(id) {
|
|
78
|
+
for (var i = 0; i < commands.length; i++) if (commands[i].id === id) return i;
|
|
79
|
+
return -1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeCommand(def) {
|
|
83
|
+
return {
|
|
84
|
+
id: def.id,
|
|
85
|
+
section: def.section || "Blocks",
|
|
86
|
+
title: def.title || def.id,
|
|
87
|
+
description: def.description || "",
|
|
88
|
+
keywords: (def.keywords || []).slice(),
|
|
89
|
+
icon: def.icon || "",
|
|
90
|
+
iconSvg: def.iconSvg || "",
|
|
91
|
+
// Accept `action` as an alias for `run` — external registrations
|
|
92
|
+
// (and reasonable guesses at the API) shouldn't yield a menu item
|
|
93
|
+
// that silently does nothing.
|
|
94
|
+
run: def.run || def.action
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var BLOCK_TAGS = { P:1, DIV:1, H1:1, H2:1, H3:1, H4:1, H5:1, H6:1, BLOCKQUOTE:1, PRE:1, LI:1 };
|
|
99
|
+
|
|
100
|
+
function findCurrentBlock() {
|
|
101
|
+
var sel = editor.getSelection();
|
|
102
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
103
|
+
var node = sel.getRangeAt(0).startContainer;
|
|
104
|
+
var editable = editor.getEditable();
|
|
105
|
+
while (node && node !== editable) {
|
|
106
|
+
if (node.nodeType === 1 && BLOCK_TAGS[node.nodeName]) return node;
|
|
107
|
+
node = node.parentNode;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function formatBlockSafe(tagName) {
|
|
113
|
+
var block = findCurrentBlock();
|
|
114
|
+
tagName = tagName.toUpperCase();
|
|
115
|
+
// For empty blocks the built-in formatblock picks the wrong node — replace the
|
|
116
|
+
// element directly. For non-empty blocks, delegate to the editor so undo/selection
|
|
117
|
+
// semantics match the rest of the toolbar.
|
|
118
|
+
if (block && !block.textContent.trim()) {
|
|
119
|
+
var editdoc = editor.getDocument();
|
|
120
|
+
var newEl = editdoc.createElement(tagName);
|
|
121
|
+
newEl.appendChild(editdoc.createElement("br"));
|
|
122
|
+
block.parentNode.replaceChild(newEl, block);
|
|
123
|
+
var range = editdoc.createRange();
|
|
124
|
+
range.setStart(newEl, 0);
|
|
125
|
+
range.setEnd(newEl, 0);
|
|
126
|
+
var sel = editor.getSelection();
|
|
127
|
+
sel.removeAllRanges();
|
|
128
|
+
sel.addRange(range);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
editor.execCommand("formatblock", tagName);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildDefaultCommands() {
|
|
135
|
+
var list = [];
|
|
136
|
+
|
|
137
|
+
function push(section, id, title, description, keywords, iconSvg, run) {
|
|
138
|
+
list.push(normalizeCommand({
|
|
139
|
+
id: id, section: section, title: title,
|
|
140
|
+
description: description, keywords: keywords,
|
|
141
|
+
iconSvg: iconSvg, run: run
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
push("Blocks", "heading1", "Heading 1", "Large section heading", ["h1", "title"], iconHeading("1"),
|
|
146
|
+
function () { formatBlockSafe("H1"); });
|
|
147
|
+
push("Blocks", "heading2", "Heading 2", "Medium section heading", ["h2", "subtitle"], iconHeading("2"),
|
|
148
|
+
function () { formatBlockSafe("H2"); });
|
|
149
|
+
push("Blocks", "heading3", "Heading 3", "Small section heading", ["h3"], iconHeading("3"),
|
|
150
|
+
function () { formatBlockSafe("H3"); });
|
|
151
|
+
push("Blocks", "paragraph", "Paragraph", "Plain body text", ["p", "text", "body"], iconParagraph(),
|
|
152
|
+
function () { formatBlockSafe("P"); });
|
|
153
|
+
push("Blocks", "bulletlist", "Bulleted list", "Unordered list", ["ul", "bullets", "list"], iconBullets(),
|
|
154
|
+
function () { editor.execCommand("insertunorderedlist"); });
|
|
155
|
+
push("Blocks", "numberlist", "Numbered list", "Ordered list", ["ol", "numbered"], iconNumbered(),
|
|
156
|
+
function () { editor.execCommand("insertorderedlist"); });
|
|
157
|
+
push("Blocks", "quote", "Quote", "Block quotation", ["blockquote"], iconQuote(),
|
|
158
|
+
function () { editor.execCommand("insertblockquote"); });
|
|
159
|
+
push("Blocks", "code", "Code block", "Monospaced code block", ["pre", "snippet"], iconCode(),
|
|
160
|
+
function () {
|
|
161
|
+
if (editor.isCommandEnabled && editor.isCommandEnabled("insertcode")) {
|
|
162
|
+
editor.execCommand("insertcode");
|
|
163
|
+
} else {
|
|
164
|
+
editor.execCommand("formatblock", "PRE");
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
push("Blocks", "divider", "Divider", "Horizontal rule", ["hr", "line", "separator"], iconDivider(),
|
|
168
|
+
function () { editor.execCommand("inserthorizontalrule"); });
|
|
169
|
+
|
|
170
|
+
// 2026-06-04 Modern block types (callout / columns / toggle). Each is
|
|
171
|
+
// only offered when the blocktypes plugin is loaded (isCommandEnabled
|
|
172
|
+
// guard), so the slash menu degrades gracefully without it.
|
|
173
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("insertcallout")) {
|
|
174
|
+
push("Blocks", "callout", "Callout", "Highlighted info / warning box", ["admonition", "info", "warning", "note", "tip"], iconCallout(),
|
|
175
|
+
function () { editor.execCommand("insertcallout"); });
|
|
176
|
+
}
|
|
177
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("insertcolumns")) {
|
|
178
|
+
push("Blocks", "columns", "Columns", "Multi-column layout", ["layout", "grid", "column"], iconColumns(),
|
|
179
|
+
function () { editor.execCommand("insertcolumns"); });
|
|
180
|
+
}
|
|
181
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("inserttoggle")) {
|
|
182
|
+
push("Blocks", "toggle", "Toggle list", "Collapsible toggle block", ["collapse", "details", "accordion", "expand"], iconToggle(),
|
|
183
|
+
function () { editor.execCommand("inserttoggle"); });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("insertmath")) {
|
|
187
|
+
push("Insert", "equation", "Equation", "Insert a math equation (TeX)", ["math", "latex", "tex", "formula", "katex"], iconEquation(),
|
|
188
|
+
function () { editor.execCommand("insertmath"); });
|
|
189
|
+
}
|
|
190
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("inserttodolist")) {
|
|
191
|
+
push("Blocks", "todolist", "To-do list", "Checklist with clickable checkboxes", ["task", "todo", "checklist", "checkbox", "check"], iconTodo(),
|
|
192
|
+
function () { editor.execCommand("inserttodolist"); });
|
|
193
|
+
}
|
|
194
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("insertdiagram")) {
|
|
195
|
+
push("Insert", "diagram", "Diagram", "Insert a Mermaid diagram", ["mermaid", "flowchart", "sequence", "gantt", "graph", "uml"], iconDiagram(),
|
|
196
|
+
function () { editor.execCommand("insertdiagram"); });
|
|
197
|
+
}
|
|
198
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("insertbookmark")) {
|
|
199
|
+
push("Insert", "bookmark", "Bookmark", "Insert a link preview card", ["link", "url", "embed", "preview", "card"], iconBookmark(),
|
|
200
|
+
function () { editor.execCommand("insertbookmark"); });
|
|
201
|
+
}
|
|
202
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("spellcheck")) {
|
|
203
|
+
push("Tools", "spellcheck", "Spell check", "Check spelling", ["spelling", "grammar", "proofread", "typo"], iconSpell(),
|
|
204
|
+
function () { editor.execCommand("spellcheck"); });
|
|
205
|
+
}
|
|
206
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("insertdatechip")) {
|
|
207
|
+
push("Insert", "datechip", "Date chip", "Insert an interactive date chip", ["date", "calendar", "chip", "today"], iconChip(),
|
|
208
|
+
function () { editor.execCommand("insertdatechip"); });
|
|
209
|
+
}
|
|
210
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("insertchip")) {
|
|
211
|
+
push("Insert", "chip", "Smart chip", "Insert a date / person / link chip", ["chip", "person", "mention", "link", "pill"], iconChip(),
|
|
212
|
+
function () { editor.execCommand("insertchip"); });
|
|
213
|
+
}
|
|
214
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("emailexport")) {
|
|
215
|
+
push("Tools", "emailexport", "Export email HTML", "Inline-styled HTML for email", ["email", "inline", "mail", "newsletter", "html"], iconEmail(),
|
|
216
|
+
function () { editor.execCommand("emailexport"); });
|
|
217
|
+
}
|
|
218
|
+
if (typeof editor.openFindDialog === "function") {
|
|
219
|
+
push("Tools", "findreplace", "Find & replace", "Search the document and replace text", ["find", "replace", "search", "query", "substitute"], iconFind(),
|
|
220
|
+
function () { editor.openFindDialog(); });
|
|
221
|
+
}
|
|
222
|
+
if (typeof editor.showShortcuts === "function") {
|
|
223
|
+
push("Tools", "shortcuts", "Keyboard shortcuts", "Show the keyboard shortcut cheat sheet (Ctrl+/)", ["keyboard", "shortcuts", "hotkeys", "keys", "help", "cheat"], iconKeyboard(),
|
|
224
|
+
function () { editor.showShortcuts(); });
|
|
225
|
+
}
|
|
226
|
+
if (typeof editor.foldAtCaret === "function") {
|
|
227
|
+
push("Tools", "foldsection", "Fold section", "Collapse the section under the current heading", ["fold", "collapse", "heading", "outline", "section"], iconFold(),
|
|
228
|
+
function () { editor.foldAtCaret(); });
|
|
229
|
+
push("Tools", "unfoldall", "Unfold all", "Expand every collapsed heading section", ["unfold", "expand", "headings", "collapse"], iconFold(),
|
|
230
|
+
function () { if (editor.unfoldAll) editor.unfoldAll(); });
|
|
231
|
+
}
|
|
232
|
+
if (!editor.isCommandEnabled || editor.isCommandEnabled("readaloud")) {
|
|
233
|
+
push("Tools", "readaloud", "Read aloud", "Speak the selection or document (text-to-speech)", ["tts", "speak", "speech", "voice", "accessibility", "narrate"], iconReadAloud(),
|
|
234
|
+
function () { editor.execCommand("readaloud"); });
|
|
235
|
+
}
|
|
236
|
+
if (typeof editor.requestGhostText === "function") {
|
|
237
|
+
push("Tools", "aicomplete", "AI complete", "Suggest an inline AI completion of the current sentence", ["ai", "complete", "autocomplete", "ghost", "suggest", "continue", "copilot"], iconGhost(),
|
|
238
|
+
function () { editor.requestGhostText(); });
|
|
239
|
+
}
|
|
240
|
+
if (typeof editor.copyAsMarkdown === "function") {
|
|
241
|
+
push("Tools", "copymarkdown", "Copy as Markdown", "Copy the whole document to the clipboard as Markdown", ["markdown", "md", "copy", "clipboard", "export"], iconMarkdown(),
|
|
242
|
+
function () { editor.copyAsMarkdown(); });
|
|
243
|
+
push("Tools", "downloadmarkdown", "Download as Markdown", "Save the document as a .md file", ["markdown", "md", "download", "export", "save", "file"], iconMarkdown(),
|
|
244
|
+
function () { editor.downloadMarkdown(); });
|
|
245
|
+
}
|
|
246
|
+
if (typeof editor.toggleTypewriterMode === "function") {
|
|
247
|
+
push("Tools", "typewriter", "Typewriter mode", "Keep the caret line centred as you type", ["typewriter", "center", "scroll", "focus", "distraction", "zen"], iconTypewriter(),
|
|
248
|
+
function () { editor.toggleTypewriterMode(); });
|
|
249
|
+
push("Tools", "focusmode", "Focus mode", "Dim everything except the current paragraph", ["focus", "dim", "highlight", "distraction", "zen", "concentrate"], iconFocus(),
|
|
250
|
+
function () { editor.toggleFocusMode(); });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
push("Insert", "table", "Table", "Insert a table", ["grid", "rows", "columns"], iconTable(),
|
|
254
|
+
function () { editor.execCommand("inserttable"); });
|
|
255
|
+
push("Insert", "image", "Image", "Upload or embed an image", ["picture", "photo", "img"], iconImage(),
|
|
256
|
+
function () { editor.execCommand("imageupload"); });
|
|
257
|
+
push("Insert", "link", "Link", "Insert a hyperlink", ["url", "anchor"], iconLink(),
|
|
258
|
+
function () { editor.execCommand("insertlink"); });
|
|
259
|
+
push("Insert", "emoji", "Emoji", "Insert an emoji", ["smiley", "icon"], iconEmoji(),
|
|
260
|
+
function () { editor.execCommand("insertemoji"); });
|
|
261
|
+
push("Insert", "template", "Template", "Insert from template gallery", ["snippet"], iconTemplate(),
|
|
262
|
+
function () { editor.execCommand("inserttemplate"); });
|
|
263
|
+
push("Insert", "date", "Today's date", "Insert the current date", ["time", "now"], iconDate(),
|
|
264
|
+
function () { editor.execCommand("insertdate"); });
|
|
265
|
+
|
|
266
|
+
if (config.slashCommandIncludeAi && Array.isArray(config.aiToolkitActions)) {
|
|
267
|
+
for (var i = 0; i < config.aiToolkitActions.length; i++) {
|
|
268
|
+
(function (action) {
|
|
269
|
+
list.push(normalizeCommand({
|
|
270
|
+
id: "ai-" + action.id,
|
|
271
|
+
section: "AI",
|
|
272
|
+
title: action.title || action.id,
|
|
273
|
+
description: action.description || "",
|
|
274
|
+
keywords: ["ai", action.id],
|
|
275
|
+
iconSvg: config["svgCode_aiassist_" + (action.icon || action.id)] || config.svgCode_aiassist || "",
|
|
276
|
+
run: function () {
|
|
277
|
+
// resolveAction() only RESOLVES a definition (returns a
|
|
278
|
+
// promise with the result spec) — it never opens UI or
|
|
279
|
+
// touches the document, so these entries were silent
|
|
280
|
+
// no-ops. Route to the EXECUTING entry points instead.
|
|
281
|
+
var tk = editor.aiToolkit;
|
|
282
|
+
if (!tk) return;
|
|
283
|
+
if (action.id === "chat-panel" && typeof tk.openChatPanel === "function") { tk.openChatPanel(); return; }
|
|
284
|
+
if (action.id === "review-panel" && typeof tk.openReviewPanel === "function") { tk.openReviewPanel(); return; }
|
|
285
|
+
if (action.id === "open-dialog" && typeof tk.openDialog === "function") { tk.openDialog(); return; }
|
|
286
|
+
if (typeof tk.runQuickAction === "function") { tk.runQuickAction(action.id); return; }
|
|
287
|
+
if (typeof tk.resolveAction === "function") tk.resolveAction(action.id);
|
|
288
|
+
}
|
|
289
|
+
}));
|
|
290
|
+
})(config.aiToolkitActions[i]);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return list;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function onEditDocKeyDown(e) {
|
|
298
|
+
if (composing) return;
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
if (popupEl) {
|
|
302
|
+
switch (e.key) {
|
|
303
|
+
case "ArrowDown":
|
|
304
|
+
e.preventDefault();
|
|
305
|
+
e.stopPropagation();
|
|
306
|
+
moveActive(1);
|
|
307
|
+
return;
|
|
308
|
+
case "ArrowUp":
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
e.stopPropagation();
|
|
311
|
+
moveActive(-1);
|
|
312
|
+
return;
|
|
313
|
+
case "Enter":
|
|
314
|
+
case "Tab":
|
|
315
|
+
if (filtered.length) {
|
|
316
|
+
e.preventDefault();
|
|
317
|
+
e.stopPropagation();
|
|
318
|
+
selectActive();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
closePopup();
|
|
322
|
+
return;
|
|
323
|
+
case "Escape":
|
|
324
|
+
case "Esc":
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
e.stopPropagation();
|
|
327
|
+
closePopup();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (e.key === config.slashCommandTrigger && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
333
|
+
if (!isAtTriggerBoundary()) return;
|
|
334
|
+
suppressNextOpen = false;
|
|
335
|
+
setTimeout(function () {
|
|
336
|
+
if (!suppressNextOpen) openPopup(false);
|
|
337
|
+
}, 0);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function onEditDocInput() {
|
|
342
|
+
if (!popupEl) return;
|
|
343
|
+
updateTriggerState();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function isAtTriggerBoundary() {
|
|
347
|
+
var sel = editor.getSelection();
|
|
348
|
+
if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
|
|
349
|
+
var range = sel.getRangeAt(0);
|
|
350
|
+
var node = range.startContainer;
|
|
351
|
+
var offset = range.startOffset;
|
|
352
|
+
|
|
353
|
+
// Suppress inside code/pre — users typing "/" in a code snippet mean the literal slash.
|
|
354
|
+
var editable = editor.getEditable();
|
|
355
|
+
var walk = node.nodeType === 1 ? node : node.parentNode;
|
|
356
|
+
while (walk && walk !== editable) {
|
|
357
|
+
var name = walk.nodeName;
|
|
358
|
+
if (name === "CODE" || name === "PRE") return false;
|
|
359
|
+
walk = walk.parentNode;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (node.nodeType === 3) {
|
|
363
|
+
if (offset === 0) return true;
|
|
364
|
+
var ch = node.nodeValue.charAt(offset - 1);
|
|
365
|
+
return /\s/.test(ch);
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function openPopup(manual) {
|
|
371
|
+
closePopup();
|
|
372
|
+
var sel = editor.getSelection();
|
|
373
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
374
|
+
var range = sel.getRangeAt(0).cloneRange();
|
|
375
|
+
|
|
376
|
+
// For automatic trigger, the "/" was typed before setTimeout(0) fires, so the
|
|
377
|
+
// caret already sits just past it — startOffset is where the query begins.
|
|
378
|
+
trigger = {
|
|
379
|
+
range: range,
|
|
380
|
+
startNode: range.startContainer,
|
|
381
|
+
startOffset: range.startOffset,
|
|
382
|
+
triggerCharCount: manual ? 0 : 1,
|
|
383
|
+
query: ""
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
renderPopup();
|
|
387
|
+
positionPopup();
|
|
388
|
+
applyFilter("");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function closePopup() {
|
|
392
|
+
if (popupEl && popupEl.parentNode) {
|
|
393
|
+
popupEl.parentNode.removeChild(popupEl);
|
|
394
|
+
}
|
|
395
|
+
popupEl = null;
|
|
396
|
+
trigger = null;
|
|
397
|
+
filtered = [];
|
|
398
|
+
activeIndex = 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function updateTriggerState() {
|
|
402
|
+
if (!trigger) return;
|
|
403
|
+
var sel = editor.getSelection();
|
|
404
|
+
if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) { closePopup(); return; }
|
|
405
|
+
var range = sel.getRangeAt(0);
|
|
406
|
+
var node = range.startContainer;
|
|
407
|
+
if (node !== trigger.startNode) { closePopup(); return; }
|
|
408
|
+
var caret = range.startOffset;
|
|
409
|
+
if (caret < trigger.startOffset) { closePopup(); return; }
|
|
410
|
+
|
|
411
|
+
var text = (node.nodeType === 3 ? node.nodeValue : "") || "";
|
|
412
|
+
var query = text.substring(trigger.startOffset, caret);
|
|
413
|
+
if (/\s/.test(query)) { closePopup(); return; }
|
|
414
|
+
trigger.query = query;
|
|
415
|
+
applyFilter(query);
|
|
416
|
+
positionPopup();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function applyFilter(query) {
|
|
420
|
+
var q = (query || "").toLowerCase();
|
|
421
|
+
filtered = commands.filter(function (cmd) {
|
|
422
|
+
if (!q) return true;
|
|
423
|
+
if (cmd.title.toLowerCase().indexOf(q) !== -1) return true;
|
|
424
|
+
if (cmd.description && cmd.description.toLowerCase().indexOf(q) !== -1) return true;
|
|
425
|
+
for (var i = 0; i < cmd.keywords.length; i++) {
|
|
426
|
+
if (String(cmd.keywords[i]).toLowerCase().indexOf(q) !== -1) return true;
|
|
427
|
+
}
|
|
428
|
+
return false;
|
|
429
|
+
}).slice(0, config.slashCommandMaxItems);
|
|
430
|
+
activeIndex = 0;
|
|
431
|
+
renderItems();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function renderPopup() {
|
|
435
|
+
popupHost = editor.iframe.ownerDocument;
|
|
436
|
+
popupEl = popupHost.createElement("div");
|
|
437
|
+
popupEl.className = "rte-slash-popup";
|
|
438
|
+
// The popup is body-appended (outside the editor container), so the
|
|
439
|
+
// container's rte-dark class can't reach it via CSS. Mirror forced
|
|
440
|
+
// dark mode here; automatic (prefers-color-scheme) dark is handled by
|
|
441
|
+
// the media query in the injected stylesheet.
|
|
442
|
+
try {
|
|
443
|
+
var host = editor.container || (editor.getEditable && editor.getEditable().closest && editor.getEditable().closest(".richtexteditor"));
|
|
444
|
+
if (host && host.classList && host.classList.contains("rte-dark")) popupEl.className += " rte-slash-popup-dark";
|
|
445
|
+
} catch (e) {}
|
|
446
|
+
popupEl.setAttribute("role", "listbox");
|
|
447
|
+
popupEl.setAttribute("aria-label", "Slash commands");
|
|
448
|
+
popupHost.body.appendChild(popupEl);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function renderItems() {
|
|
452
|
+
if (!popupEl) return;
|
|
453
|
+
popupEl.innerHTML = "";
|
|
454
|
+
|
|
455
|
+
if (!filtered.length) {
|
|
456
|
+
var empty = popupHost.createElement("div");
|
|
457
|
+
empty.className = "rte-slash-empty";
|
|
458
|
+
empty.textContent = "No matching commands";
|
|
459
|
+
popupEl.appendChild(empty);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
var lastSection = "";
|
|
464
|
+
for (var i = 0; i < filtered.length; i++) {
|
|
465
|
+
(function (cmd, index) {
|
|
466
|
+
if (cmd.section !== lastSection) {
|
|
467
|
+
var sectionEl = popupHost.createElement("div");
|
|
468
|
+
sectionEl.className = "rte-slash-section";
|
|
469
|
+
sectionEl.textContent = cmd.section;
|
|
470
|
+
popupEl.appendChild(sectionEl);
|
|
471
|
+
lastSection = cmd.section;
|
|
472
|
+
}
|
|
473
|
+
var item = popupHost.createElement("button");
|
|
474
|
+
item.type = "button";
|
|
475
|
+
item.className = "rte-slash-item" + (index === activeIndex ? " rte-slash-item-active" : "");
|
|
476
|
+
item.setAttribute("role", "option");
|
|
477
|
+
item.setAttribute("aria-selected", index === activeIndex ? "true" : "false");
|
|
478
|
+
item.setAttribute("data-index", index);
|
|
479
|
+
|
|
480
|
+
var icon = popupHost.createElement("span");
|
|
481
|
+
icon.className = "rte-slash-item-icon";
|
|
482
|
+
icon.innerHTML = cmd.iconSvg || iconDot();
|
|
483
|
+
item.appendChild(icon);
|
|
484
|
+
|
|
485
|
+
var body = popupHost.createElement("span");
|
|
486
|
+
body.className = "rte-slash-item-body";
|
|
487
|
+
var title = popupHost.createElement("span");
|
|
488
|
+
title.className = "rte-slash-item-title";
|
|
489
|
+
title.textContent = cmd.title;
|
|
490
|
+
body.appendChild(title);
|
|
491
|
+
if (cmd.description) {
|
|
492
|
+
var desc = popupHost.createElement("span");
|
|
493
|
+
desc.className = "rte-slash-item-desc";
|
|
494
|
+
desc.textContent = cmd.description;
|
|
495
|
+
body.appendChild(desc);
|
|
496
|
+
}
|
|
497
|
+
item.appendChild(body);
|
|
498
|
+
|
|
499
|
+
// mousedown runs synchronously before focus shifts, so the editor's
|
|
500
|
+
// selection stays intact. preventDefault stops the button from stealing
|
|
501
|
+
// focus in the first place. click() is a fallback for programmatic
|
|
502
|
+
// element.click() callers that don't dispatch a mousedown.
|
|
503
|
+
item.addEventListener("mousedown", function (e) {
|
|
504
|
+
e.preventDefault();
|
|
505
|
+
e.stopPropagation();
|
|
506
|
+
activeIndex = index;
|
|
507
|
+
selectActive();
|
|
508
|
+
});
|
|
509
|
+
item.addEventListener("click", function (e) {
|
|
510
|
+
if (!popupEl) return; // mousedown already handled it
|
|
511
|
+
e.preventDefault();
|
|
512
|
+
e.stopPropagation();
|
|
513
|
+
activeIndex = index;
|
|
514
|
+
selectActive();
|
|
515
|
+
});
|
|
516
|
+
item.addEventListener("mouseenter", function () {
|
|
517
|
+
if (activeIndex !== index) {
|
|
518
|
+
activeIndex = index;
|
|
519
|
+
updateActiveClass();
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
popupEl.appendChild(item);
|
|
524
|
+
})(filtered[i], i);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function updateActiveClass() {
|
|
529
|
+
if (!popupEl) return;
|
|
530
|
+
var items = popupEl.querySelectorAll(".rte-slash-item");
|
|
531
|
+
for (var i = 0; i < items.length; i++) {
|
|
532
|
+
var idx = +items[i].getAttribute("data-index");
|
|
533
|
+
var active = idx === activeIndex;
|
|
534
|
+
items[i].classList.toggle("rte-slash-item-active", active);
|
|
535
|
+
items[i].setAttribute("aria-selected", active ? "true" : "false");
|
|
536
|
+
}
|
|
537
|
+
scrollActiveIntoView();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function scrollActiveIntoView() {
|
|
541
|
+
if (!popupEl) return;
|
|
542
|
+
var active = popupEl.querySelector(".rte-slash-item-active");
|
|
543
|
+
if (!active) return;
|
|
544
|
+
var top = active.offsetTop;
|
|
545
|
+
var bot = top + active.offsetHeight;
|
|
546
|
+
if (top < popupEl.scrollTop) popupEl.scrollTop = top;
|
|
547
|
+
else if (bot > popupEl.scrollTop + popupEl.clientHeight) popupEl.scrollTop = bot - popupEl.clientHeight;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function moveActive(delta) {
|
|
551
|
+
if (!filtered.length) return;
|
|
552
|
+
activeIndex = (activeIndex + delta + filtered.length) % filtered.length;
|
|
553
|
+
updateActiveClass();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function selectActive() {
|
|
557
|
+
if (!filtered.length) { closePopup(); return; }
|
|
558
|
+
var cmd = filtered[activeIndex];
|
|
559
|
+
var triggerSnapshot = trigger;
|
|
560
|
+
closePopup();
|
|
561
|
+
if (!cmd || !triggerSnapshot) return;
|
|
562
|
+
deleteTriggerText(triggerSnapshot);
|
|
563
|
+
// Only re-focus if focus actually left the iframe — avoids editwin.focus()
|
|
564
|
+
// nuking our freshly-placed selection when we were already focused.
|
|
565
|
+
var editdoc = editor.getDocument();
|
|
566
|
+
if (editdoc.activeElement !== editor.getEditable() && !editdoc.hasFocus()) {
|
|
567
|
+
editor.focus();
|
|
568
|
+
}
|
|
569
|
+
try { cmd.run(editor); } catch (err) { console.warn("Slash command failed:", err); }
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function deleteTriggerText(triggerSnapshot) {
|
|
573
|
+
var node = triggerSnapshot.startNode;
|
|
574
|
+
if (!node) return;
|
|
575
|
+
var start = triggerSnapshot.startOffset - (triggerSnapshot.triggerCharCount || 0);
|
|
576
|
+
if (start < 0) start = 0;
|
|
577
|
+
var sel = editor.getSelection();
|
|
578
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
579
|
+
var currentRange = sel.getRangeAt(0);
|
|
580
|
+
var endNode, endOffset;
|
|
581
|
+
if (node.nodeType === 3 && currentRange.startContainer === node) {
|
|
582
|
+
endNode = node;
|
|
583
|
+
endOffset = currentRange.startOffset;
|
|
584
|
+
} else {
|
|
585
|
+
endNode = currentRange.startContainer;
|
|
586
|
+
endOffset = currentRange.startOffset;
|
|
587
|
+
}
|
|
588
|
+
var delRange = editor.getDocument().createRange();
|
|
589
|
+
delRange.setStart(node, start);
|
|
590
|
+
delRange.setEnd(endNode, endOffset);
|
|
591
|
+
try { delRange.deleteContents(); } catch (err) { return; }
|
|
592
|
+
// After deleteContents(), delRange is collapsed at the deletion start — reuse it.
|
|
593
|
+
sel.removeAllRanges();
|
|
594
|
+
sel.addRange(delRange);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function positionPopup() {
|
|
598
|
+
if (!popupEl || !trigger) return;
|
|
599
|
+
var iframe = editor.iframe;
|
|
600
|
+
var ir = iframe.getBoundingClientRect();
|
|
601
|
+
var rects = trigger.range.getClientRects();
|
|
602
|
+
var r = rects && rects.length ? rects[rects.length - 1] : null;
|
|
603
|
+
if (!r) r = trigger.range.getBoundingClientRect();
|
|
604
|
+
|
|
605
|
+
var left = ir.left + (r.left || 0) + window.pageXOffset;
|
|
606
|
+
var top = ir.top + (r.bottom || r.top || 0) + window.pageYOffset + 4;
|
|
607
|
+
|
|
608
|
+
popupEl.style.position = "absolute";
|
|
609
|
+
popupEl.style.left = left + "px";
|
|
610
|
+
popupEl.style.top = top + "px";
|
|
611
|
+
|
|
612
|
+
// Flip up if would overflow viewport
|
|
613
|
+
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
614
|
+
var popupRect = popupEl.getBoundingClientRect();
|
|
615
|
+
if (popupRect.bottom > vh - 8 && (ir.top + (r.top || 0)) > popupRect.height + 16) {
|
|
616
|
+
popupEl.style.top = (ir.top + (r.top || 0) + window.pageYOffset - popupRect.height - 4) + "px";
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function onHostScrollOrResize() {
|
|
621
|
+
if (popupEl) positionPopup();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function injectStyles() {
|
|
625
|
+
var host = (editor && editor.iframe && editor.iframe.ownerDocument) || document;
|
|
626
|
+
if (host.querySelector("style[data-rte-slashcommand]")) return;
|
|
627
|
+
var style = host.createElement("style");
|
|
628
|
+
style.setAttribute("data-rte-slashcommand", "1");
|
|
629
|
+
style.textContent = [
|
|
630
|
+
"/* 2026-07-03 slash command polish */",
|
|
631
|
+
".rte-slash-popup{position:absolute;z-index:2147483000;min-width:280px;max-width:min(390px,calc(100vw - 24px));max-height:min(360px,64vh);overflow-y:auto;overscroll-behavior:contain;padding:6px;background:linear-gradient(180deg,rgba(255,255,255,.98),rgba(248,251,255,.96));background-clip:padding-box;border:1px solid rgba(148,163,184,.22);box-shadow:0 16px 36px rgba(29,78,216,.14),0 2px 8px rgba(15,23,42,.08);border-radius:8px;font-family:Aptos,'Segoe UI',sans-serif;font-size:13px;color:#172033;scrollbar-width:thin}",
|
|
632
|
+
".rte-slash-section{position:sticky;top:0;z-index:1;margin:4px 0 2px;padding:6px 8px 4px;background:linear-gradient(180deg,rgba(248,251,255,.98),rgba(248,251,255,.86));color:#52657e;font-size:10px;font-weight:850;letter-spacing:.08em;text-transform:uppercase}",
|
|
633
|
+
".rte-slash-item{display:grid;grid-template-columns:28px minmax(0,1fr);align-items:center;gap:8px;width:100%;min-height:40px;padding:6px 8px;border:0;background:transparent;text-align:left;cursor:pointer;color:inherit;font:inherit;border-radius:8px}",
|
|
634
|
+
".rte-slash-item:hover,.rte-slash-item-active{background:#eef4ff;color:#1559d6}",
|
|
635
|
+
".rte-slash-item:focus-visible{outline:2px solid rgba(37,99,235,.22);outline-offset:2px}",
|
|
636
|
+
".rte-slash-item-icon{flex:0 0 28px;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;color:#315277;background:#ffffff;border-radius:8px;box-shadow:inset 0 0 0 1px rgba(148,163,184,.18)}",
|
|
637
|
+
".rte-slash-item-active .rte-slash-item-icon{color:#1559d6;background:#ffffff}",
|
|
638
|
+
".rte-slash-item-icon svg{display:block;width:16px;height:16px;pointer-events:none}",
|
|
639
|
+
".rte-slash-item-body{display:flex;flex-direction:column;min-width:0;flex:1}",
|
|
640
|
+
".rte-slash-item-title{font-weight:780;font-size:13px;line-height:1.24;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
641
|
+
".rte-slash-item-desc{font-size:11px;color:#64748b;line-height:1.24;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
642
|
+
".rte-slash-item-active .rte-slash-item-desc{color:#315277}",
|
|
643
|
+
".rte-slash-empty{padding:16px;color:#52657e;font-size:12px;font-weight:700;text-align:center}",
|
|
644
|
+
// Dark mode — applied automatically via prefers-color-scheme, and via
|
|
645
|
+
// an explicit .rte-slash-popup-dark class set when the editor is in
|
|
646
|
+
// forced dark mode (the popup is body-appended, outside the editor
|
|
647
|
+
// container, so the container's .rte-dark class can't reach it).
|
|
648
|
+
"@media (prefers-color-scheme:dark){",
|
|
649
|
+
" .rte-slash-popup{background:linear-gradient(180deg,#1e293b,#0f172a);border-color:#334155;color:#e2e8f0;box-shadow:0 20px 46px rgba(0,0,0,.5),0 2px 8px rgba(0,0,0,.4)}",
|
|
650
|
+
" .rte-slash-section{background:linear-gradient(180deg,rgba(30,41,59,.98),rgba(30,41,59,.86));color:#94a3b8}",
|
|
651
|
+
" .rte-slash-item:hover,.rte-slash-item-active{background:#1d4ed8;color:#fff}",
|
|
652
|
+
" .rte-slash-item-icon{color:#cbd5e1;background:#0f172a;box-shadow:inset 0 0 0 1px rgba(148,163,184,.25)}",
|
|
653
|
+
" .rte-slash-item-active .rte-slash-item-icon{color:#fff;background:#1e3a8a}",
|
|
654
|
+
" .rte-slash-item-desc{color:#94a3b8}",
|
|
655
|
+
" .rte-slash-item-active .rte-slash-item-desc{color:#dbeafe}",
|
|
656
|
+
" .rte-slash-empty{color:#94a3b8}",
|
|
657
|
+
"}",
|
|
658
|
+
".rte-slash-popup-dark{background:linear-gradient(180deg,#1e293b,#0f172a);border-color:#334155;color:#e2e8f0;box-shadow:0 20px 46px rgba(0,0,0,.5),0 2px 8px rgba(0,0,0,.4)}",
|
|
659
|
+
".rte-slash-popup-dark .rte-slash-section{background:linear-gradient(180deg,rgba(30,41,59,.98),rgba(30,41,59,.86));color:#94a3b8}",
|
|
660
|
+
".rte-slash-popup-dark .rte-slash-item:hover,.rte-slash-popup-dark .rte-slash-item-active{background:#1d4ed8;color:#fff}",
|
|
661
|
+
".rte-slash-popup-dark .rte-slash-item-icon{color:#cbd5e1;background:#0f172a;box-shadow:inset 0 0 0 1px rgba(148,163,184,.25)}",
|
|
662
|
+
".rte-slash-popup-dark .rte-slash-item-active .rte-slash-item-icon{color:#fff;background:#1e3a8a}",
|
|
663
|
+
".rte-slash-popup-dark .rte-slash-item-desc{color:#94a3b8}",
|
|
664
|
+
".rte-slash-popup-dark .rte-slash-item-active .rte-slash-item-desc{color:#dbeafe}",
|
|
665
|
+
".rte-slash-popup-dark .rte-slash-empty{color:#94a3b8}"
|
|
666
|
+
].join("\n");
|
|
667
|
+
host.head.appendChild(style);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// --- Icons (minimal inline SVGs) ---
|
|
671
|
+
function iconHeading(n) {
|
|
672
|
+
return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4v16"/><path d="M14 4v16"/><path d="M6 12h8"/><text x="16" y="18" font-size="9" font-weight="700" fill="currentColor" stroke="none" font-family="-apple-system,Segoe UI,sans-serif">' + n + '</text></svg>';
|
|
673
|
+
}
|
|
674
|
+
function iconParagraph() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 4h5"/><path d="M13 4v16"/><path d="M17 4v16"/><path d="M13 4a4 4 0 000 8h0"/></svg>'; }
|
|
675
|
+
function iconBullets() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="7" r="1"/><circle cx="5" cy="12" r="1"/><circle cx="5" cy="17" r="1"/><path d="M9 7h11"/><path d="M9 12h11"/><path d="M9 17h11"/></svg>'; }
|
|
676
|
+
function iconNumbered() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h1v3"/><path d="M4 9h2"/><path d="M4 14h2a1 1 0 010 2H4v1h2"/><path d="M9 7h11"/><path d="M9 12h11"/><path d="M9 17h11"/></svg>'; }
|
|
677
|
+
function iconQuote() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 8c-2 0-3 1.5-3 4s1 4 3 4V8z"/><path d="M16 8c-2 0-3 1.5-3 4s1 4 3 4V8z"/></svg>'; }
|
|
678
|
+
function iconCode() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 8l-4 4 4 4"/><path d="M15 8l4 4-4 4"/></svg>'; }
|
|
679
|
+
function iconDivider() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16"/><path d="M4 6h10"/><path d="M4 18h10"/></svg>'; }
|
|
680
|
+
function iconTable() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="5" width="16" height="14" rx="1"/><path d="M4 10h16"/><path d="M4 15h16"/><path d="M10 5v14"/><path d="M16 5v14"/></svg>'; }
|
|
681
|
+
function iconImage() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="10" r="1.5"/><path d="M21 16l-5-5-8 8"/></svg>'; }
|
|
682
|
+
function iconLink() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 14a4 4 0 005.7 0l3-3a4 4 0 10-5.7-5.7L11 7"/><path d="M14 10a4 4 0 00-5.7 0l-3 3a4 4 0 105.7 5.7L13 17"/></svg>'; }
|
|
683
|
+
function iconEmoji() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M8 14c1 1.5 2.5 2 4 2s3-.5 4-2"/><circle cx="9" cy="10" r=".8"/><circle cx="15" cy="10" r=".8"/></svg>'; }
|
|
684
|
+
function iconTemplate() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 9h16"/><path d="M9 9v11"/></svg>'; }
|
|
685
|
+
function iconDate() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="6" width="16" height="14" rx="1.5"/><path d="M4 10h16"/><path d="M9 3v4"/><path d="M15 3v4"/></svg>'; }
|
|
686
|
+
function iconDot() { return '<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="3"/></svg>'; }
|
|
687
|
+
function iconCallout() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M12 9v4"/><path d="M12 16h.01"/></svg>'; }
|
|
688
|
+
function iconColumns() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="7" height="16" rx="1"/><rect x="14" y="4" width="7" height="16" rx="1"/></svg>'; }
|
|
689
|
+
function iconToggle() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></svg>'; }
|
|
690
|
+
function iconEquation() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5h9l-5 14"/><path d="M14 11h6"/><path d="M14 16h6"/></svg>'; }
|
|
691
|
+
function iconBookmark() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 10h18"/><circle cx="7" cy="7.5" r=".6" fill="currentColor"/></svg>'; }
|
|
692
|
+
function iconSpell() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17l4-10 4 10"/><path d="M5.5 13.5h5"/><path d="M14 14a2.5 2.5 0 105 0v-3"/><path d="M14 20l2 2 4-4"/></svg>'; }
|
|
693
|
+
function iconChip() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="10" rx="5"/><circle cx="8" cy="12" r="1.6"/><path d="M12 12h6"/></svg>'; }
|
|
694
|
+
function iconEmail() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M4 7l8 6 8-6"/></svg>'; }
|
|
695
|
+
function iconDiagram() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="6" height="5" rx="1"/><rect x="15" y="3" width="6" height="5" rx="1"/><rect x="9" y="16" width="6" height="5" rx="1"/><path d="M6 8v3h12V8M12 11v5"/></svg>'; }
|
|
696
|
+
function iconReadAloud() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9v6h4l5 4V5L8 9H4z"/><path d="M16 8.5a4 4 0 010 7"/><path d="M19 6a7 7 0 010 12"/></svg>'; }
|
|
697
|
+
function iconTodo() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="7" height="7" rx="1.5"/><path d="M4.5 7.5 6 9l2.5-3"/><line x1="13" y1="6" x2="21" y2="6"/><line x1="13" y1="13" x2="21" y2="13"/><line x1="3" y1="17" x2="21" y2="17"/><line x1="3" y1="20.5" x2="14" y2="20.5"/></svg>'; }
|
|
698
|
+
function iconFold() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l3 3 3-3"/><path d="M9 18l3-3 3 3"/><line x1="4" y1="12" x2="20" y2="12"/></svg>'; }
|
|
699
|
+
function iconKeyboard() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M6 14h.01M18 14h.01M9 14h6"/></svg>'; }
|
|
700
|
+
function iconFind() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="6.5"/><line x1="15.5" y1="15.5" x2="21" y2="21"/></svg>'; }
|
|
701
|
+
function iconTypewriter() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="9" width="18" height="7" rx="1"/><path d="M7 9V5h10v4"/><line x1="7" y1="20" x2="17" y2="20"/></svg>'; }
|
|
702
|
+
function iconFocus() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M3 12h3M18 12h3M12 3v3M12 18v3"/></svg>'; }
|
|
703
|
+
function iconMarkdown() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M6 15V9l3 3 3-3v6"/><path d="M17 9v6M14.5 12.5L17 15l2.5-2.5"/></svg>'; }
|
|
704
|
+
function iconGhost() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 21V9a7 7 0 0114 0v12l-3-2-2 2-2-2-2 2-3-2z"/><circle cx="9.5" cy="10" r="1" fill="currentColor"/><circle cx="14.5" cy="10" r="1" fill="currentColor"/></svg>'; }
|
|
705
|
+
}
|