mardora 1.2.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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/chunk-3OCUX4OO.js +7690 -0
- package/dist/chunk-3OCUX4OO.js.map +1 -0
- package/dist/chunk-3ZOCCFDL.cjs +74 -0
- package/dist/chunk-3ZOCCFDL.cjs.map +1 -0
- package/dist/chunk-7JOEPNEV.cjs +7740 -0
- package/dist/chunk-7JOEPNEV.cjs.map +1 -0
- package/dist/chunk-BIKZQZ6W.js +33 -0
- package/dist/chunk-BIKZQZ6W.js.map +1 -0
- package/dist/chunk-EQJESPP2.js +234 -0
- package/dist/chunk-EQJESPP2.js.map +1 -0
- package/dist/chunk-G4SE26YY.js +70 -0
- package/dist/chunk-G4SE26YY.js.map +1 -0
- package/dist/chunk-KNDWF2DP.cjs +35 -0
- package/dist/chunk-KNDWF2DP.cjs.map +1 -0
- package/dist/chunk-MLBEBFHB.cjs +2971 -0
- package/dist/chunk-MLBEBFHB.cjs.map +1 -0
- package/dist/chunk-P7JFCYU3.js +905 -0
- package/dist/chunk-P7JFCYU3.js.map +1 -0
- package/dist/chunk-SWFUKJDO.cjs +243 -0
- package/dist/chunk-SWFUKJDO.cjs.map +1 -0
- package/dist/chunk-WFVCG4LD.cjs +926 -0
- package/dist/chunk-WFVCG4LD.cjs.map +1 -0
- package/dist/chunk-XL6WFGJT.js +2901 -0
- package/dist/chunk-XL6WFGJT.js.map +1 -0
- package/dist/editor/index.cjs +277 -0
- package/dist/editor/index.cjs.map +1 -0
- package/dist/editor/index.d.cts +186 -0
- package/dist/editor/index.d.ts +186 -0
- package/dist/editor/index.js +4 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/index.cjs +405 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/index.cjs +12 -0
- package/dist/lib/index.cjs.map +1 -0
- package/dist/lib/index.d.cts +16 -0
- package/dist/lib/index.d.ts +16 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/mardora-DCwjomil.d.cts +640 -0
- package/dist/mardora-DCwjomil.d.ts +640 -0
- package/dist/plugins/index.cjs +104 -0
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.d.cts +740 -0
- package/dist/plugins/index.d.ts +740 -0
- package/dist/plugins/index.js +7 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/preview/index.cjs +38 -0
- package/dist/preview/index.cjs.map +1 -0
- package/dist/preview/index.d.cts +101 -0
- package/dist/preview/index.d.ts +101 -0
- package/dist/preview/index.js +5 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/types-NBsaxl4d.d.cts +71 -0
- package/dist/types-Pw2SWWAR.d.ts +71 -0
- package/package.json +92 -0
- package/src/editor/attachments/extension.ts +181 -0
- package/src/editor/attachments/format.ts +63 -0
- package/src/editor/attachments/index.ts +3 -0
- package/src/editor/attachments/types.ts +37 -0
- package/src/editor/heading-fold/config.ts +25 -0
- package/src/editor/heading-fold/extension.ts +268 -0
- package/src/editor/heading-fold/extract.ts +88 -0
- package/src/editor/heading-fold/index.ts +5 -0
- package/src/editor/heading-fold/theme.ts +85 -0
- package/src/editor/heading-fold/types.ts +24 -0
- package/src/editor/i18n.ts +13 -0
- package/src/editor/icons/index.ts +367 -0
- package/src/editor/index.ts +16 -0
- package/src/editor/mardora.ts +257 -0
- package/src/editor/media-lightbox-theme.ts +146 -0
- package/src/editor/media-lightbox.ts +125 -0
- package/src/editor/plugin.ts +294 -0
- package/src/editor/selection-toolbar/activation.ts +123 -0
- package/src/editor/selection-toolbar/commands.ts +279 -0
- package/src/editor/selection-toolbar/extension.ts +564 -0
- package/src/editor/selection-toolbar/i18n.ts +164 -0
- package/src/editor/selection-toolbar/index.ts +7 -0
- package/src/editor/selection-toolbar/menu.ts +252 -0
- package/src/editor/selection-toolbar/position.ts +43 -0
- package/src/editor/selection-toolbar/theme.ts +195 -0
- package/src/editor/selection-toolbar/types.ts +155 -0
- package/src/editor/slash/default-commands.ts +190 -0
- package/src/editor/slash/extension.ts +319 -0
- package/src/editor/slash/index.ts +7 -0
- package/src/editor/slash/insertions.ts +26 -0
- package/src/editor/slash/menu.ts +123 -0
- package/src/editor/slash/position.ts +61 -0
- package/src/editor/slash/query.ts +33 -0
- package/src/editor/slash/theme.ts +113 -0
- package/src/editor/slash/types.ts +40 -0
- package/src/editor/table-of-contents/extension.ts +202 -0
- package/src/editor/table-of-contents/extract.ts +53 -0
- package/src/editor/table-of-contents/index.ts +7 -0
- package/src/editor/table-of-contents/panel.ts +83 -0
- package/src/editor/table-of-contents/slug.ts +50 -0
- package/src/editor/table-of-contents/storage.ts +35 -0
- package/src/editor/table-of-contents/theme.ts +153 -0
- package/src/editor/table-of-contents/types.ts +44 -0
- package/src/editor/theme.ts +72 -0
- package/src/editor/utils.ts +176 -0
- package/src/editor/view-plugin.ts +189 -0
- package/src/index.ts +5 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/input-handler.ts +47 -0
- package/src/plugins/code-plugin.theme.ts +545 -0
- package/src/plugins/code-plugin.ts +1892 -0
- package/src/plugins/emoji-plugin.ts +140 -0
- package/src/plugins/heading-plugin.ts +194 -0
- package/src/plugins/hr-plugin.ts +102 -0
- package/src/plugins/html-plugin.ts +353 -0
- package/src/plugins/image-plugin.ts +806 -0
- package/src/plugins/index.ts +71 -0
- package/src/plugins/inline-plugin.ts +311 -0
- package/src/plugins/link-plugin.ts +509 -0
- package/src/plugins/list-plugin.ts +492 -0
- package/src/plugins/math-plugin.ts +526 -0
- package/src/plugins/mermaid-plugin.ts +513 -0
- package/src/plugins/paragraph-plugin.ts +38 -0
- package/src/plugins/quote-plugin.ts +733 -0
- package/src/plugins/table-controls-theme.ts +126 -0
- package/src/plugins/table-controls.ts +423 -0
- package/src/plugins/table-model.ts +661 -0
- package/src/plugins/table-plugin.ts +2111 -0
- package/src/preview/context.ts +45 -0
- package/src/preview/css-generator.ts +64 -0
- package/src/preview/default-renderers.ts +29 -0
- package/src/preview/index.ts +29 -0
- package/src/preview/preview.ts +41 -0
- package/src/preview/renderer.ts +184 -0
- package/src/preview/syntax-theme.ts +112 -0
- package/src/preview/toc.ts +23 -0
- package/src/preview/types.ts +89 -0
|
@@ -0,0 +1,2971 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkWFVCG4LD_cjs = require('./chunk-WFVCG4LD.cjs');
|
|
4
|
+
var state = require('@codemirror/state');
|
|
5
|
+
var view = require('@codemirror/view');
|
|
6
|
+
var langMarkdown = require('@codemirror/lang-markdown');
|
|
7
|
+
var language = require('@codemirror/language');
|
|
8
|
+
var highlight = require('@lezer/highlight');
|
|
9
|
+
var commands = require('@codemirror/commands');
|
|
10
|
+
var languageData = require('@codemirror/language-data');
|
|
11
|
+
var styleMod = require('style-mod');
|
|
12
|
+
|
|
13
|
+
var mardoraBaseTheme = view.EditorView.theme({
|
|
14
|
+
// Container styles - only apply when view plugin is enabled
|
|
15
|
+
"&.cm-mardora": {
|
|
16
|
+
fontSize: "16px",
|
|
17
|
+
lineHeight: "1.6",
|
|
18
|
+
minHeight: "100%",
|
|
19
|
+
backgroundColor: "transparent !important"
|
|
20
|
+
},
|
|
21
|
+
"&.cm-mardora.cm-focused": {
|
|
22
|
+
outline: "none"
|
|
23
|
+
},
|
|
24
|
+
"&.cm-mardora .cm-scroller": {
|
|
25
|
+
minHeight: "100%"
|
|
26
|
+
},
|
|
27
|
+
"&.cm-mardora .cm-content": {
|
|
28
|
+
width: "100%",
|
|
29
|
+
maxWidth: "48rem",
|
|
30
|
+
padding: "0 0.5rem",
|
|
31
|
+
margin: "0 auto",
|
|
32
|
+
fontFamily: "var(--font-sans, sans-serif)",
|
|
33
|
+
fontSize: "16px",
|
|
34
|
+
lineHeight: "1.6"
|
|
35
|
+
},
|
|
36
|
+
"&.cm-mardora .cm-content .cm-line": {
|
|
37
|
+
paddingInline: 0
|
|
38
|
+
},
|
|
39
|
+
"&.cm-mardora .cm-content .cm-widgetBuffer": {
|
|
40
|
+
display: "none !important"
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
var markdownResetStyle = language.HighlightStyle.define([
|
|
44
|
+
{
|
|
45
|
+
tag: [
|
|
46
|
+
highlight.tags.heading,
|
|
47
|
+
highlight.tags.strong,
|
|
48
|
+
highlight.tags.emphasis,
|
|
49
|
+
highlight.tags.strikethrough,
|
|
50
|
+
highlight.tags.link,
|
|
51
|
+
highlight.tags.url,
|
|
52
|
+
highlight.tags.quote,
|
|
53
|
+
highlight.tags.list,
|
|
54
|
+
highlight.tags.meta,
|
|
55
|
+
highlight.tags.contentSeparator,
|
|
56
|
+
highlight.tags.labelName
|
|
57
|
+
],
|
|
58
|
+
color: "inherit",
|
|
59
|
+
fontWeight: "inherit",
|
|
60
|
+
fontStyle: "inherit",
|
|
61
|
+
textDecoration: "none"
|
|
62
|
+
}
|
|
63
|
+
]);
|
|
64
|
+
var markdownResetExtension = language.syntaxHighlighting(markdownResetStyle, { fallback: false });
|
|
65
|
+
|
|
66
|
+
// src/editor/view-plugin.ts
|
|
67
|
+
var MardoraPluginsFacet = state.Facet.define({
|
|
68
|
+
combine: (values) => values.flat()
|
|
69
|
+
});
|
|
70
|
+
var mardoraOnNodesChangeFacet = state.Facet.define({
|
|
71
|
+
combine: (values) => values.find((v) => v !== void 0)
|
|
72
|
+
});
|
|
73
|
+
var mardoraThemeFacet = state.Facet.define({
|
|
74
|
+
combine: (values) => values.find((v) => v !== void 0) || "auto" /* AUTO */
|
|
75
|
+
});
|
|
76
|
+
function buildDecorations(view, plugins = []) {
|
|
77
|
+
const builder = new state.RangeSetBuilder();
|
|
78
|
+
const decorations = [];
|
|
79
|
+
if (plugins.length > 0) {
|
|
80
|
+
const ctx = {
|
|
81
|
+
view,
|
|
82
|
+
decorations,
|
|
83
|
+
selectionOverlapsRange: (from, to) => chunkWFVCG4LD_cjs.selectionOverlapsRange(view, from, to),
|
|
84
|
+
cursorInRange: (from, to) => chunkWFVCG4LD_cjs.cursorInRange(view, from, to)
|
|
85
|
+
};
|
|
86
|
+
const sortedPlugins = [...plugins].sort((a, b) => a.decorationPriority - b.decorationPriority);
|
|
87
|
+
for (const plugin of sortedPlugins) {
|
|
88
|
+
try {
|
|
89
|
+
plugin.buildDecorations(ctx);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
decorations.sort((a, b) => a.from - b.from || a.value.startSide - b.value.startSide);
|
|
95
|
+
for (const decoration of decorations) {
|
|
96
|
+
builder.add(decoration.from, decoration.to, decoration.value);
|
|
97
|
+
}
|
|
98
|
+
return builder.finish();
|
|
99
|
+
}
|
|
100
|
+
var mardoraViewPluginClass = class {
|
|
101
|
+
decorations;
|
|
102
|
+
plugins;
|
|
103
|
+
onNodesChange;
|
|
104
|
+
constructor(view) {
|
|
105
|
+
this.plugins = view.state.facet(MardoraPluginsFacet);
|
|
106
|
+
this.onNodesChange = view.state.facet(mardoraOnNodesChangeFacet);
|
|
107
|
+
this.decorations = buildDecorations(view, this.plugins);
|
|
108
|
+
for (const plugin of this.plugins) {
|
|
109
|
+
plugin.onViewReady(view);
|
|
110
|
+
}
|
|
111
|
+
if (this.onNodesChange && typeof this.onNodesChange === "function") {
|
|
112
|
+
this.onNodesChange(this.buildNodes(view));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
update(update) {
|
|
116
|
+
this.plugins = update.view.state.facet(MardoraPluginsFacet);
|
|
117
|
+
this.onNodesChange = update.view.state.facet(mardoraOnNodesChangeFacet);
|
|
118
|
+
for (const plugin of this.plugins) {
|
|
119
|
+
plugin.onViewUpdate(update);
|
|
120
|
+
}
|
|
121
|
+
if (update.docChanged || update.selectionSet || update.viewportChanged) {
|
|
122
|
+
this.decorations = buildDecorations(update.view, this.plugins);
|
|
123
|
+
if (this.onNodesChange) {
|
|
124
|
+
this.onNodesChange(this.buildNodes(update.view));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
buildNodes(view) {
|
|
129
|
+
const tree = language.syntaxTree(view.state);
|
|
130
|
+
const roots = [];
|
|
131
|
+
const stack = [];
|
|
132
|
+
tree.iterate({
|
|
133
|
+
enter: (nodeRef) => {
|
|
134
|
+
const node = {
|
|
135
|
+
from: nodeRef.from,
|
|
136
|
+
to: nodeRef.to,
|
|
137
|
+
name: nodeRef.name,
|
|
138
|
+
children: [],
|
|
139
|
+
isSelected: chunkWFVCG4LD_cjs.selectionOverlapsRange(view, nodeRef.from, nodeRef.to)
|
|
140
|
+
};
|
|
141
|
+
if (stack.length > 0) {
|
|
142
|
+
stack[stack.length - 1].children.push(node);
|
|
143
|
+
} else {
|
|
144
|
+
roots.push(node);
|
|
145
|
+
}
|
|
146
|
+
stack.push(node);
|
|
147
|
+
},
|
|
148
|
+
leave: () => {
|
|
149
|
+
stack.pop();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return roots;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var mardoraViewPlugin = view.ViewPlugin.fromClass(mardoraViewPluginClass, {
|
|
156
|
+
decorations: (v) => v.decorations,
|
|
157
|
+
provide: () => []
|
|
158
|
+
});
|
|
159
|
+
var mardoraEditorClass = view.EditorView.editorAttributes.of({ class: "cm-mardora" });
|
|
160
|
+
function createMardoraViewExtension(theme = "auto" /* AUTO */, baseStyles = true, plugins = [], onNodesChange) {
|
|
161
|
+
return [
|
|
162
|
+
mardoraEditorClass,
|
|
163
|
+
MardoraPluginsFacet.of(plugins),
|
|
164
|
+
mardoraOnNodesChangeFacet.of(onNodesChange),
|
|
165
|
+
mardoraThemeFacet.of(theme),
|
|
166
|
+
mardoraViewPlugin,
|
|
167
|
+
...baseStyles ? [mardoraBaseTheme] : []
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/editor/slash/query.ts
|
|
172
|
+
var slashLinePattern = /^\/([^\s]*)$/;
|
|
173
|
+
function detectSlashQuery(documentText, cursorPosition) {
|
|
174
|
+
const safeCursor = Math.max(0, Math.min(cursorPosition, documentText.length));
|
|
175
|
+
const lineStart = documentText.lastIndexOf("\n", safeCursor - 1) + 1;
|
|
176
|
+
const lineTextBeforeCursor = documentText.slice(lineStart, safeCursor);
|
|
177
|
+
const match = lineTextBeforeCursor.match(slashLinePattern);
|
|
178
|
+
if (!match) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
from: lineStart,
|
|
183
|
+
to: safeCursor,
|
|
184
|
+
query: match[1] ?? ""
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function filterSlashCommands(commands, query) {
|
|
188
|
+
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
189
|
+
if (!normalizedQuery) {
|
|
190
|
+
return [...commands];
|
|
191
|
+
}
|
|
192
|
+
return commands.filter((command) => {
|
|
193
|
+
const searchable = [command.title, command.id, command.hint, ...command.aliases].join(" ").toLocaleLowerCase();
|
|
194
|
+
return searchable.includes(normalizedQuery);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/editor/slash/insertions.ts
|
|
199
|
+
function buildSlashReplacement(template, query) {
|
|
200
|
+
return {
|
|
201
|
+
changes: {
|
|
202
|
+
from: query.from,
|
|
203
|
+
to: query.to,
|
|
204
|
+
insert: template.marker
|
|
205
|
+
},
|
|
206
|
+
selectionAnchor: query.from + template.cursorOffset
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/editor/slash/default-commands.ts
|
|
211
|
+
var commandCopy = {
|
|
212
|
+
"zh-CN": {
|
|
213
|
+
paragraph: { title: "\u6587\u672C", aliases: ["text", "plain", "wenben"] },
|
|
214
|
+
"heading-1": { title: "\u6807\u9898 1", aliases: ["h1", "heading", "heading1", "biaoti", "\u6807\u9898"] },
|
|
215
|
+
"heading-2": { title: "\u6807\u9898 2", aliases: ["h2", "heading", "heading2", "biaoti", "\u6807\u9898"] },
|
|
216
|
+
"heading-3": { title: "\u6807\u9898 3", aliases: ["h3", "heading", "heading3", "biaoti", "\u6807\u9898"] },
|
|
217
|
+
"heading-4": { title: "\u6807\u9898 4", aliases: ["h4", "heading", "heading4", "biaoti", "\u6807\u9898"] },
|
|
218
|
+
"heading-5": { title: "\u6807\u9898 5", aliases: ["h5", "heading", "heading5", "biaoti", "\u6807\u9898"] },
|
|
219
|
+
"heading-6": { title: "\u6807\u9898 6", aliases: ["h6", "heading", "heading6", "biaoti", "\u6807\u9898"] },
|
|
220
|
+
quote: { title: "\u5F15\u7528", aliases: ["quote", "blockquote", "yinyong"] },
|
|
221
|
+
"callout-note": {
|
|
222
|
+
title: "\u8BF4\u660E Callout",
|
|
223
|
+
aliases: ["callout", "note", "info", "\u544A\u8B66", "\u8BF4\u660E", "\u63D0\u793A", "shuoming", "tishi"]
|
|
224
|
+
},
|
|
225
|
+
"callout-tip": {
|
|
226
|
+
title: "\u6280\u5DE7 Callout",
|
|
227
|
+
aliases: ["callout", "tip", "hint", "\u544A\u8B66", "\u6280\u5DE7", "\u63D0\u793A", "jiqiao", "tishi"]
|
|
228
|
+
},
|
|
229
|
+
"callout-important": { title: "\u91CD\u8981 Callout", aliases: ["callout", "important", "\u544A\u8B66", "\u91CD\u8981", "zhongyao"] },
|
|
230
|
+
"callout-warning": { title: "\u8B66\u544A Callout", aliases: ["callout", "warning", "warn", "\u544A\u8B66", "\u8B66\u544A", "jinggao"] },
|
|
231
|
+
"callout-caution": {
|
|
232
|
+
title: "\u4E25\u91CD\u8B66\u544A Callout",
|
|
233
|
+
aliases: ["callout", "caution", "danger", "\u544A\u8B66", "\u4E25\u91CD", "\u98CE\u9669", "yanzhong", "fengxian"]
|
|
234
|
+
},
|
|
235
|
+
"code-block": { title: "\u4EE3\u7801\u5757", aliases: ["code", "codeblock", "fence", "\u4EE3\u7801", "\u4EE3\u7801\u5757", "daima"] },
|
|
236
|
+
"ordered-list": { title: "\u6709\u5E8F\u5217\u8868", aliases: ["ol", "ordered", "numbered", "youxu", "\u6709\u5E8F"] },
|
|
237
|
+
"unordered-list": { title: "\u9879\u76EE\u7B26\u53F7\u5217\u8868", aliases: ["ul", "bullet", "unordered", "bulleted", "wuxu", "\u65E0\u5E8F"] },
|
|
238
|
+
"task-list": { title: "\u5F85\u529E\u6E05\u5355", aliases: ["todo", "task", "check", "daiban", "\u5F85\u529E"] },
|
|
239
|
+
table: { title: "\u8868\u683C", aliases: ["table", "biaoge"] },
|
|
240
|
+
divider: { title: "\u5206\u9694\u7EBF", aliases: ["hr", "divider", "line", "fengexian", "\u5206\u9694"] },
|
|
241
|
+
link: { title: "\u94FE\u63A5", aliases: ["link", "url", "lianjie"] },
|
|
242
|
+
file: { title: "\u6587\u4EF6", aliases: ["file", "wenjian"] },
|
|
243
|
+
image: { title: "\u56FE\u7247", aliases: ["image", "img", "tu", "tupian", "\u56FE\u7247"] },
|
|
244
|
+
video: { title: "\u89C6\u9891", aliases: ["video", "shipin"] },
|
|
245
|
+
audio: { title: "\u97F3\u9891", aliases: ["audio", "music", "yinpin"] }
|
|
246
|
+
},
|
|
247
|
+
"en-US": {
|
|
248
|
+
paragraph: { title: "Text", aliases: ["\u6587\u672C", "text", "plain", "wenben"] },
|
|
249
|
+
"heading-1": { title: "Heading 1", aliases: ["\u6807\u9898", "h1", "heading", "heading1", "biaoti"] },
|
|
250
|
+
"heading-2": { title: "Heading 2", aliases: ["\u6807\u9898", "h2", "heading", "heading2", "biaoti"] },
|
|
251
|
+
"heading-3": { title: "Heading 3", aliases: ["\u6807\u9898", "h3", "heading", "heading3", "biaoti"] },
|
|
252
|
+
"heading-4": { title: "Heading 4", aliases: ["\u6807\u9898", "h4", "heading", "heading4", "biaoti"] },
|
|
253
|
+
"heading-5": { title: "Heading 5", aliases: ["\u6807\u9898", "h5", "heading", "heading5", "biaoti"] },
|
|
254
|
+
"heading-6": { title: "Heading 6", aliases: ["\u6807\u9898", "h6", "heading", "heading6", "biaoti"] },
|
|
255
|
+
quote: { title: "Quote", aliases: ["\u5F15\u7528", "quote", "blockquote", "yinyong"] },
|
|
256
|
+
"callout-note": { title: "Note callout", aliases: ["\u8BF4\u660E", "\u63D0\u793A", "\u544A\u8B66", "callout", "note", "info"] },
|
|
257
|
+
"callout-tip": { title: "Tip callout", aliases: ["\u6280\u5DE7", "\u63D0\u793A", "\u544A\u8B66", "callout", "tip", "hint"] },
|
|
258
|
+
"callout-important": { title: "Important callout", aliases: ["\u91CD\u8981", "\u544A\u8B66", "callout", "important"] },
|
|
259
|
+
"callout-warning": { title: "Warning callout", aliases: ["\u8B66\u544A", "\u544A\u8B66", "callout", "warning", "warn"] },
|
|
260
|
+
"callout-caution": { title: "Caution callout", aliases: ["\u4E25\u91CD", "\u98CE\u9669", "\u544A\u8B66", "callout", "caution", "danger"] },
|
|
261
|
+
"code-block": { title: "Code block", aliases: ["\u4EE3\u7801", "\u4EE3\u7801\u5757", "code", "codeblock", "fence", "daima"] },
|
|
262
|
+
"ordered-list": { title: "Numbered list", aliases: ["\u6709\u5E8F", "ol", "ordered", "numbered", "youxu"] },
|
|
263
|
+
"unordered-list": { title: "Bulleted list", aliases: ["\u65E0\u5E8F", "ul", "bullet", "unordered", "bulleted", "wuxu"] },
|
|
264
|
+
"task-list": { title: "To-do list", aliases: ["\u5F85\u529E", "todo", "task", "check", "daiban"] },
|
|
265
|
+
table: { title: "Table", aliases: ["\u8868\u683C", "table", "biaoge"] },
|
|
266
|
+
divider: { title: "Divider", aliases: ["\u5206\u9694", "hr", "divider", "line", "fengexian"] },
|
|
267
|
+
link: { title: "Link", aliases: ["\u94FE\u63A5", "link", "url", "lianjie"] },
|
|
268
|
+
file: { title: "File", aliases: ["\u6587\u4EF6", "file", "wenjian"] },
|
|
269
|
+
image: { title: "Image", aliases: ["\u56FE\u7247", "image", "img", "tu", "tupian"] },
|
|
270
|
+
video: { title: "Video", aliases: ["\u89C6\u9891", "video", "shipin"] },
|
|
271
|
+
audio: { title: "Audio", aliases: ["\u97F3\u9891", "audio", "music", "yinpin"] }
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
function commandMeta(locale, id, group, icon, hint) {
|
|
275
|
+
const copy = commandCopy[locale][id];
|
|
276
|
+
if (!copy) {
|
|
277
|
+
throw new Error(`Missing slash command copy: ${locale}:${id}`);
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
id,
|
|
281
|
+
group,
|
|
282
|
+
title: copy.title,
|
|
283
|
+
aliases: copy.aliases,
|
|
284
|
+
icon,
|
|
285
|
+
hint
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function markdownCommand(command, marker, cursorOffset = marker.length) {
|
|
289
|
+
return {
|
|
290
|
+
...command,
|
|
291
|
+
run: ({ view, queryRange }) => {
|
|
292
|
+
const replacement = buildSlashReplacement({ marker, cursorOffset }, { ...queryRange});
|
|
293
|
+
view.dispatch({
|
|
294
|
+
changes: replacement.changes,
|
|
295
|
+
selection: { anchor: replacement.selectionAnchor },
|
|
296
|
+
scrollIntoView: true
|
|
297
|
+
});
|
|
298
|
+
view.focus();
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function calloutCommand(locale, id, icon, type) {
|
|
304
|
+
return markdownCommand(commandMeta(locale, id, "basic", icon, `[!${type}]`), `> [!${type}]
|
|
305
|
+
> `);
|
|
306
|
+
}
|
|
307
|
+
function mediaCommand(command, kind) {
|
|
308
|
+
return {
|
|
309
|
+
...command,
|
|
310
|
+
run: (context) => {
|
|
311
|
+
if (context.requestAttachment?.(kind, context)) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
const fallbackByKind = {
|
|
315
|
+
image: "",
|
|
316
|
+
video: '<video src="url" controls></video>',
|
|
317
|
+
audio: '<audio src="url" controls></audio>',
|
|
318
|
+
file: "[filename](url)"
|
|
319
|
+
};
|
|
320
|
+
const fallback = fallbackByKind[kind];
|
|
321
|
+
const replacement = buildSlashReplacement(
|
|
322
|
+
{ marker: fallback, cursorOffset: kind === "file" ? 1 : fallback.indexOf("url") },
|
|
323
|
+
{ ...context.queryRange}
|
|
324
|
+
);
|
|
325
|
+
context.view.dispatch({
|
|
326
|
+
changes: replacement.changes,
|
|
327
|
+
selection: { anchor: replacement.selectionAnchor },
|
|
328
|
+
scrollIntoView: true
|
|
329
|
+
});
|
|
330
|
+
context.view.focus();
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function getDefaultSlashCommands(locale = "zh-CN") {
|
|
336
|
+
return [
|
|
337
|
+
markdownCommand(commandMeta(locale, "paragraph", "basic", "type", ""), "", 0),
|
|
338
|
+
markdownCommand(commandMeta(locale, "heading-1", "basic", "heading-1", "#"), "# "),
|
|
339
|
+
markdownCommand(commandMeta(locale, "heading-2", "basic", "heading-2", "##"), "## "),
|
|
340
|
+
markdownCommand(commandMeta(locale, "heading-3", "basic", "heading-3", "###"), "### "),
|
|
341
|
+
markdownCommand(commandMeta(locale, "heading-4", "basic", "heading-4", "####"), "#### "),
|
|
342
|
+
markdownCommand(commandMeta(locale, "heading-5", "basic", "heading-5", "#####"), "##### "),
|
|
343
|
+
markdownCommand(commandMeta(locale, "heading-6", "basic", "heading-6", "######"), "###### "),
|
|
344
|
+
markdownCommand(commandMeta(locale, "quote", "basic", "text-quote", ">"), "> "),
|
|
345
|
+
calloutCommand(locale, "callout-note", "info", "NOTE"),
|
|
346
|
+
calloutCommand(locale, "callout-tip", "lightbulb", "TIP"),
|
|
347
|
+
calloutCommand(locale, "callout-important", "badge-alert", "IMPORTANT"),
|
|
348
|
+
calloutCommand(locale, "callout-warning", "triangle-alert", "WARNING"),
|
|
349
|
+
calloutCommand(locale, "callout-caution", "octagon-alert", "CAUTION"),
|
|
350
|
+
markdownCommand(commandMeta(locale, "code-block", "basic", "code-xml", "```"), "```\n\n```", 4),
|
|
351
|
+
markdownCommand(commandMeta(locale, "ordered-list", "basic", "list-ordered", "1."), "1. "),
|
|
352
|
+
markdownCommand(commandMeta(locale, "unordered-list", "basic", "list", "-"), "- "),
|
|
353
|
+
markdownCommand(commandMeta(locale, "task-list", "basic", "list-todo", "[]"), "- [ ] "),
|
|
354
|
+
markdownCommand(
|
|
355
|
+
commandMeta(locale, "table", "basic", "table", "| |"),
|
|
356
|
+
"| Column 1 | Column 2 |\n| --- | --- |\n| | |\n",
|
|
357
|
+
2
|
|
358
|
+
),
|
|
359
|
+
markdownCommand(commandMeta(locale, "divider", "basic", "minus", "---"), "---\n"),
|
|
360
|
+
markdownCommand(commandMeta(locale, "link", "basic", "link", "[]()"), "[]()", 1),
|
|
361
|
+
mediaCommand(commandMeta(locale, "file", "media", "file", "file"), "file"),
|
|
362
|
+
mediaCommand(commandMeta(locale, "image", "media", "image", "img"), "image"),
|
|
363
|
+
mediaCommand(commandMeta(locale, "video", "media", "play", "video"), "video"),
|
|
364
|
+
mediaCommand(commandMeta(locale, "audio", "media", "music-2", "audio"), "audio")
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
var defaultSlashCommands = getDefaultSlashCommands("zh-CN");
|
|
368
|
+
|
|
369
|
+
// src/editor/slash/menu.ts
|
|
370
|
+
var slashMessages = {
|
|
371
|
+
"zh-CN": {
|
|
372
|
+
groups: {
|
|
373
|
+
basic: "\u57FA\u672C\u533A\u5757",
|
|
374
|
+
media: "\u5A92\u4F53"
|
|
375
|
+
},
|
|
376
|
+
empty: "\u6CA1\u6709\u5339\u914D\u7684\u547D\u4EE4",
|
|
377
|
+
close: "\u5173\u95ED\u83DC\u5355",
|
|
378
|
+
closeHint: "esc"
|
|
379
|
+
},
|
|
380
|
+
"en-US": {
|
|
381
|
+
groups: {
|
|
382
|
+
basic: "Basic blocks",
|
|
383
|
+
media: "Media"
|
|
384
|
+
},
|
|
385
|
+
empty: "No matching commands",
|
|
386
|
+
close: "Close menu",
|
|
387
|
+
closeHint: "esc"
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
function getSlashMessages(locale) {
|
|
391
|
+
return slashMessages[locale];
|
|
392
|
+
}
|
|
393
|
+
function createSlashMenuElement(state, callbacks) {
|
|
394
|
+
const { messages } = state;
|
|
395
|
+
const root = document.createElement("div");
|
|
396
|
+
root.className = "cm-mardora-slash-menu";
|
|
397
|
+
root.setAttribute("role", "listbox");
|
|
398
|
+
const list = document.createElement("div");
|
|
399
|
+
list.className = "cm-mardora-slash-list";
|
|
400
|
+
root.addEventListener(
|
|
401
|
+
"wheel",
|
|
402
|
+
(event) => {
|
|
403
|
+
list.scrollTop += event.deltaY;
|
|
404
|
+
event.preventDefault();
|
|
405
|
+
event.stopPropagation();
|
|
406
|
+
},
|
|
407
|
+
{ capture: true, passive: false }
|
|
408
|
+
);
|
|
409
|
+
if (state.commands.length === 0) {
|
|
410
|
+
const empty = document.createElement("div");
|
|
411
|
+
empty.className = "cm-mardora-slash-empty";
|
|
412
|
+
empty.textContent = messages.empty;
|
|
413
|
+
list.appendChild(empty);
|
|
414
|
+
root.appendChild(list);
|
|
415
|
+
return root;
|
|
416
|
+
}
|
|
417
|
+
let currentGroup = null;
|
|
418
|
+
for (const [index, command] of state.commands.entries()) {
|
|
419
|
+
if (command.group !== currentGroup) {
|
|
420
|
+
currentGroup = command.group;
|
|
421
|
+
const label = document.createElement("div");
|
|
422
|
+
label.className = "cm-mardora-slash-group";
|
|
423
|
+
label.textContent = messages.groups[currentGroup];
|
|
424
|
+
list.appendChild(label);
|
|
425
|
+
}
|
|
426
|
+
const item = document.createElement("button");
|
|
427
|
+
item.type = "button";
|
|
428
|
+
item.className = index === state.activeIndex ? "cm-mardora-slash-item cm-mardora-slash-item-active" : "cm-mardora-slash-item";
|
|
429
|
+
item.dataset.mardoraSlashIndex = String(index);
|
|
430
|
+
item.setAttribute("role", "option");
|
|
431
|
+
item.setAttribute("aria-selected", String(index === state.activeIndex));
|
|
432
|
+
item.addEventListener("mouseenter", () => callbacks.onHover(index));
|
|
433
|
+
item.addEventListener("mousedown", (event) => {
|
|
434
|
+
event.preventDefault();
|
|
435
|
+
callbacks.onSelect(index);
|
|
436
|
+
});
|
|
437
|
+
const icon = document.createElement("span");
|
|
438
|
+
icon.className = "cm-mardora-slash-icon";
|
|
439
|
+
const svgIcon = chunkWFVCG4LD_cjs.createMardoraIcon(command.icon);
|
|
440
|
+
if (svgIcon) {
|
|
441
|
+
icon.appendChild(svgIcon);
|
|
442
|
+
} else {
|
|
443
|
+
icon.textContent = command.icon;
|
|
444
|
+
}
|
|
445
|
+
const title = document.createElement("span");
|
|
446
|
+
title.className = "cm-mardora-slash-title";
|
|
447
|
+
title.textContent = command.title;
|
|
448
|
+
const hint = document.createElement("span");
|
|
449
|
+
hint.className = "cm-mardora-slash-hint";
|
|
450
|
+
hint.textContent = command.hint;
|
|
451
|
+
item.append(icon, title, hint);
|
|
452
|
+
list.appendChild(item);
|
|
453
|
+
}
|
|
454
|
+
const footer = document.createElement("div");
|
|
455
|
+
footer.className = "cm-mardora-slash-footer";
|
|
456
|
+
footer.innerHTML = `<span>${messages.close}</span><span>${messages.closeHint}</span>`;
|
|
457
|
+
root.append(list, footer);
|
|
458
|
+
return root;
|
|
459
|
+
}
|
|
460
|
+
var slashMenuTheme = view.EditorView.baseTheme({
|
|
461
|
+
".cm-mardora-slash-menu": {
|
|
462
|
+
position: "fixed",
|
|
463
|
+
display: "flex",
|
|
464
|
+
flexDirection: "column",
|
|
465
|
+
zIndex: "1000",
|
|
466
|
+
width: "328px",
|
|
467
|
+
maxHeight: "420px",
|
|
468
|
+
overflow: "hidden",
|
|
469
|
+
border: "1px solid rgba(120, 113, 108, 0.22)",
|
|
470
|
+
borderRadius: "12px",
|
|
471
|
+
background: "var(--mardora-slash-bg, #ffffff)",
|
|
472
|
+
boxShadow: "0 18px 48px rgba(15, 23, 42, 0.16)",
|
|
473
|
+
padding: "8px 0 0",
|
|
474
|
+
caretColor: "transparent",
|
|
475
|
+
cursor: "default",
|
|
476
|
+
fontFamily: "var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif)",
|
|
477
|
+
userSelect: "none"
|
|
478
|
+
},
|
|
479
|
+
".cm-mardora-slash-open .cm-cursor": {
|
|
480
|
+
visibility: "hidden"
|
|
481
|
+
},
|
|
482
|
+
".cm-mardora-slash-open .cm-content": {
|
|
483
|
+
caretColor: "transparent"
|
|
484
|
+
},
|
|
485
|
+
".cm-mardora-slash-list": {
|
|
486
|
+
flex: "1 1 auto",
|
|
487
|
+
minHeight: "0",
|
|
488
|
+
overflowY: "auto"
|
|
489
|
+
},
|
|
490
|
+
".cm-mardora-slash-group": {
|
|
491
|
+
padding: "8px 14px 6px",
|
|
492
|
+
color: "var(--mardora-slash-muted, #a8a29e)",
|
|
493
|
+
fontSize: "12px",
|
|
494
|
+
fontWeight: "500"
|
|
495
|
+
},
|
|
496
|
+
".cm-mardora-slash-item": {
|
|
497
|
+
display: "grid",
|
|
498
|
+
gridTemplateColumns: "34px 1fr auto",
|
|
499
|
+
alignItems: "center",
|
|
500
|
+
width: "100%",
|
|
501
|
+
minHeight: "34px",
|
|
502
|
+
border: "0",
|
|
503
|
+
padding: "0 14px",
|
|
504
|
+
background: "transparent",
|
|
505
|
+
color: "var(--mardora-slash-fg, #27272a)",
|
|
506
|
+
textAlign: "left",
|
|
507
|
+
cursor: "default"
|
|
508
|
+
},
|
|
509
|
+
".cm-mardora-slash-item-active": {
|
|
510
|
+
background: "var(--mardora-slash-active, #f4f4f5)"
|
|
511
|
+
},
|
|
512
|
+
".cm-mardora-slash-icon": {
|
|
513
|
+
display: "inline-flex",
|
|
514
|
+
alignItems: "center",
|
|
515
|
+
justifyContent: "center",
|
|
516
|
+
color: "var(--mardora-slash-icon, #3f3f46)",
|
|
517
|
+
fontSize: "13px",
|
|
518
|
+
fontWeight: "400",
|
|
519
|
+
textAlign: "center"
|
|
520
|
+
},
|
|
521
|
+
".cm-mardora-slash-icon svg": {
|
|
522
|
+
display: "block",
|
|
523
|
+
width: "17px",
|
|
524
|
+
height: "17px",
|
|
525
|
+
strokeWidth: "1.9"
|
|
526
|
+
},
|
|
527
|
+
".cm-mardora-slash-title": {
|
|
528
|
+
overflow: "hidden",
|
|
529
|
+
color: "inherit",
|
|
530
|
+
fontSize: "14px",
|
|
531
|
+
fontWeight: "400",
|
|
532
|
+
textOverflow: "ellipsis",
|
|
533
|
+
whiteSpace: "nowrap"
|
|
534
|
+
},
|
|
535
|
+
".cm-mardora-slash-hint": {
|
|
536
|
+
marginLeft: "12px",
|
|
537
|
+
color: "var(--mardora-slash-muted, #a8a29e)",
|
|
538
|
+
fontSize: "12px",
|
|
539
|
+
fontWeight: "400"
|
|
540
|
+
},
|
|
541
|
+
".cm-mardora-slash-footer": {
|
|
542
|
+
display: "flex",
|
|
543
|
+
flex: "0 0 auto",
|
|
544
|
+
alignItems: "center",
|
|
545
|
+
justifyContent: "space-between",
|
|
546
|
+
minHeight: "40px",
|
|
547
|
+
borderTop: "1px solid rgba(120, 113, 108, 0.18)",
|
|
548
|
+
background: "var(--mardora-slash-bg, #ffffff)",
|
|
549
|
+
padding: "0 14px",
|
|
550
|
+
color: "var(--mardora-slash-fg, #27272a)",
|
|
551
|
+
fontSize: "14px"
|
|
552
|
+
},
|
|
553
|
+
".cm-mardora-slash-footer span:last-child": {
|
|
554
|
+
color: "var(--mardora-slash-muted, #a8a29e)",
|
|
555
|
+
fontSize: "12px",
|
|
556
|
+
fontWeight: "400"
|
|
557
|
+
},
|
|
558
|
+
".cm-mardora-slash-empty": {
|
|
559
|
+
padding: "14px",
|
|
560
|
+
color: "var(--mardora-slash-muted, #a8a29e)",
|
|
561
|
+
fontSize: "13px"
|
|
562
|
+
},
|
|
563
|
+
"&dark .cm-mardora-slash-menu": {
|
|
564
|
+
"--mardora-slash-bg": "#18181b",
|
|
565
|
+
"--mardora-slash-fg": "#f4f4f5",
|
|
566
|
+
"--mardora-slash-muted": "#a1a1aa",
|
|
567
|
+
"--mardora-slash-active": "#27272a",
|
|
568
|
+
"--mardora-slash-icon": "#e4e4e7"
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// src/editor/attachments/format.ts
|
|
573
|
+
function detectAttachmentKind(file) {
|
|
574
|
+
if (file.type.startsWith("image/")) return "image";
|
|
575
|
+
if (file.type.startsWith("video/")) return "video";
|
|
576
|
+
if (file.type.startsWith("audio/")) return "audio";
|
|
577
|
+
return "file";
|
|
578
|
+
}
|
|
579
|
+
function isAcceptedAttachment(file, acceptRules) {
|
|
580
|
+
if (acceptRules.includes("*/*")) return true;
|
|
581
|
+
return acceptRules.some((rule) => {
|
|
582
|
+
if (rule.endsWith("/*")) {
|
|
583
|
+
return file.type.startsWith(rule.slice(0, -1));
|
|
584
|
+
}
|
|
585
|
+
if (rule.startsWith(".")) {
|
|
586
|
+
return file.name.toLocaleLowerCase().endsWith(rule.toLocaleLowerCase());
|
|
587
|
+
}
|
|
588
|
+
return file.type === rule;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function createUploadMarker(input) {
|
|
592
|
+
if (input.state === "failed") {
|
|
593
|
+
return `[Upload failed: ${input.name}](mardora-upload://${input.taskId})`;
|
|
594
|
+
}
|
|
595
|
+
if (input.kind === "image") {
|
|
596
|
+
return ``;
|
|
597
|
+
}
|
|
598
|
+
return `[Uploading ${input.name}](mardora-upload://${input.taskId})`;
|
|
599
|
+
}
|
|
600
|
+
function formatAttachmentMarkdown(kind, result) {
|
|
601
|
+
const name = result.name || "attachment";
|
|
602
|
+
if (kind === "image") {
|
|
603
|
+
return result.title ? `` : ``;
|
|
604
|
+
}
|
|
605
|
+
if (kind === "video") {
|
|
606
|
+
return `<video src="${result.url}" controls></video>`;
|
|
607
|
+
}
|
|
608
|
+
if (kind === "audio") {
|
|
609
|
+
return `<audio src="${result.url}" controls></audio>`;
|
|
610
|
+
}
|
|
611
|
+
return `[${name}](${result.url})`;
|
|
612
|
+
}
|
|
613
|
+
var uploadSequence = 0;
|
|
614
|
+
function nextUploadTaskId() {
|
|
615
|
+
uploadSequence += 1;
|
|
616
|
+
return `task-${Date.now()}-${uploadSequence}`;
|
|
617
|
+
}
|
|
618
|
+
function findMarkerRange(view, taskId) {
|
|
619
|
+
const doc = view.state.doc.toString();
|
|
620
|
+
const marker = `mardora-upload://${taskId}`;
|
|
621
|
+
const markerIndex = doc.indexOf(marker);
|
|
622
|
+
if (markerIndex === -1) {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
const line = view.state.doc.lineAt(markerIndex);
|
|
626
|
+
return { from: line.from, to: line.to };
|
|
627
|
+
}
|
|
628
|
+
async function uploadAttachmentFile(view, file, options) {
|
|
629
|
+
const kind = options.kind ?? detectAttachmentKind(file);
|
|
630
|
+
const range = options.range ?? {
|
|
631
|
+
from: view.state.selection.main.from,
|
|
632
|
+
to: view.state.selection.main.to
|
|
633
|
+
};
|
|
634
|
+
const taskId = nextUploadTaskId();
|
|
635
|
+
const marker = createUploadMarker({ taskId, kind, name: file.name, state: "uploading" });
|
|
636
|
+
view.dispatch({
|
|
637
|
+
changes: { from: range.from, to: range.to, insert: marker },
|
|
638
|
+
selection: { anchor: range.from + marker.length },
|
|
639
|
+
scrollIntoView: true
|
|
640
|
+
});
|
|
641
|
+
try {
|
|
642
|
+
const result = await options.uploader(file, {
|
|
643
|
+
kind,
|
|
644
|
+
source: options.source,
|
|
645
|
+
documentText: view.state.doc.toString(),
|
|
646
|
+
selection: { from: range.from, to: range.from + marker.length }
|
|
647
|
+
});
|
|
648
|
+
const markerRange = findMarkerRange(view, taskId);
|
|
649
|
+
if (!markerRange) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const output = formatAttachmentMarkdown(kind, {
|
|
653
|
+
...result,
|
|
654
|
+
name: result.name ?? file.name,
|
|
655
|
+
mimeType: result.mimeType ?? file.type
|
|
656
|
+
});
|
|
657
|
+
view.dispatch({
|
|
658
|
+
changes: { from: markerRange.from, to: markerRange.to, insert: output },
|
|
659
|
+
selection: { anchor: markerRange.from + output.length },
|
|
660
|
+
scrollIntoView: true
|
|
661
|
+
});
|
|
662
|
+
} catch {
|
|
663
|
+
const markerRange = findMarkerRange(view, taskId);
|
|
664
|
+
if (!markerRange) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const failedMarker = createUploadMarker({ taskId, kind, name: file.name, state: "failed" });
|
|
668
|
+
view.dispatch({
|
|
669
|
+
changes: { from: markerRange.from, to: markerRange.to, insert: failedMarker },
|
|
670
|
+
selection: { anchor: markerRange.from + failedMarker.length },
|
|
671
|
+
scrollIntoView: true
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function getFilesFromEvent(event) {
|
|
676
|
+
const files = event.type === "paste" ? event.clipboardData?.files : event.dataTransfer?.files;
|
|
677
|
+
return files ? Array.from(files) : [];
|
|
678
|
+
}
|
|
679
|
+
function uploadFilesFromEvent(view, event, source, config) {
|
|
680
|
+
const files = getFilesFromEvent(event);
|
|
681
|
+
if (files.length === 0) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
event.preventDefault();
|
|
685
|
+
for (const file of files) {
|
|
686
|
+
const kind = detectAttachmentKind(file);
|
|
687
|
+
const acceptRules = config.accept?.[kind] ?? ["*/*"];
|
|
688
|
+
if (!isAcceptedAttachment(file, acceptRules)) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
void uploadAttachmentFile(view, file, {
|
|
692
|
+
kind,
|
|
693
|
+
source,
|
|
694
|
+
uploader: config.uploader
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
function attachments(config = {}) {
|
|
700
|
+
if (config.enabled === false || !config.uploader) {
|
|
701
|
+
return [];
|
|
702
|
+
}
|
|
703
|
+
const normalizedConfig = {
|
|
704
|
+
enablePaste: true,
|
|
705
|
+
enableDrop: true,
|
|
706
|
+
...config,
|
|
707
|
+
uploader: config.uploader
|
|
708
|
+
};
|
|
709
|
+
return [
|
|
710
|
+
view.EditorView.domEventHandlers({
|
|
711
|
+
paste(event, view) {
|
|
712
|
+
if (!normalizedConfig.enablePaste) {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
return uploadFilesFromEvent(view, event, "paste", normalizedConfig);
|
|
716
|
+
},
|
|
717
|
+
drop(event, view) {
|
|
718
|
+
if (!normalizedConfig.enableDrop) {
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
return uploadFilesFromEvent(view, event, "drop", normalizedConfig);
|
|
722
|
+
}
|
|
723
|
+
})
|
|
724
|
+
];
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/editor/i18n.ts
|
|
728
|
+
var defaultMardoraLocale = "zh-CN";
|
|
729
|
+
var supportedMardoraLocales = /* @__PURE__ */ new Set(["zh-CN", "en-US"]);
|
|
730
|
+
function resolveMardoraLocale(locale) {
|
|
731
|
+
return locale && supportedMardoraLocales.has(locale) ? locale : defaultMardoraLocale;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/editor/slash/position.ts
|
|
735
|
+
var menuWidth = 328;
|
|
736
|
+
var menuMaxHeight = 420;
|
|
737
|
+
var viewportPadding = 8;
|
|
738
|
+
var anchorGap = 6;
|
|
739
|
+
function clamp(value, min, max) {
|
|
740
|
+
return Math.min(Math.max(value, min), max);
|
|
741
|
+
}
|
|
742
|
+
function computeSlashMenuLayout(input) {
|
|
743
|
+
const maxLeft = Math.max(viewportPadding, input.viewport.width - menuWidth - viewportPadding);
|
|
744
|
+
const left = clamp(input.anchor.left, viewportPadding, maxLeft);
|
|
745
|
+
const availableBelow = Math.max(1, input.viewport.height - input.anchor.bottom - anchorGap - viewportPadding);
|
|
746
|
+
const availableAbove = Math.max(1, input.anchor.top - anchorGap - viewportPadding);
|
|
747
|
+
const placement = availableBelow >= menuMaxHeight || availableBelow >= availableAbove ? "bottom" : "top";
|
|
748
|
+
if (placement === "bottom") {
|
|
749
|
+
return {
|
|
750
|
+
placement,
|
|
751
|
+
left,
|
|
752
|
+
top: input.anchor.bottom + anchorGap,
|
|
753
|
+
bottom: null,
|
|
754
|
+
maxHeight: Math.min(menuMaxHeight, availableBelow)
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
placement,
|
|
759
|
+
left,
|
|
760
|
+
top: null,
|
|
761
|
+
bottom: input.viewport.height - input.anchor.top + anchorGap,
|
|
762
|
+
maxHeight: Math.min(menuMaxHeight, availableAbove)
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/editor/slash/extension.ts
|
|
767
|
+
function createSlashRuntimeConfig(config = {}) {
|
|
768
|
+
const locale = resolveMardoraLocale(config.locale ?? config.inheritedLocale);
|
|
769
|
+
return {
|
|
770
|
+
...config,
|
|
771
|
+
locale,
|
|
772
|
+
commands: config.commands ?? getDefaultSlashCommands(locale),
|
|
773
|
+
messages: getSlashMessages(locale)
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function requestFile(kind) {
|
|
777
|
+
return new Promise((resolve) => {
|
|
778
|
+
const input = document.createElement("input");
|
|
779
|
+
input.type = "file";
|
|
780
|
+
input.accept = kind === "image" ? "image/*" : kind === "video" ? "video/*" : kind === "audio" ? "audio/*" : "*/*";
|
|
781
|
+
input.addEventListener("change", () => resolve(input.files?.[0] ?? null), { once: true });
|
|
782
|
+
input.click();
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
var SlashCommandViewPlugin = class {
|
|
786
|
+
constructor(view, config) {
|
|
787
|
+
this.view = view;
|
|
788
|
+
this.config = config;
|
|
789
|
+
this.view.dom.ownerDocument.addEventListener("keydown", this.handleDocumentKeydown, true);
|
|
790
|
+
this.updateState();
|
|
791
|
+
}
|
|
792
|
+
view;
|
|
793
|
+
config;
|
|
794
|
+
query = null;
|
|
795
|
+
commands = [];
|
|
796
|
+
activeIndex = 0;
|
|
797
|
+
menu = null;
|
|
798
|
+
renderVersion = 0;
|
|
799
|
+
previousCaretColor = null;
|
|
800
|
+
update(update) {
|
|
801
|
+
if (update.docChanged || update.selectionSet || update.viewportChanged) {
|
|
802
|
+
this.updateState();
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
destroy() {
|
|
806
|
+
this.view.dom.ownerDocument.removeEventListener("keydown", this.handleDocumentKeydown, true);
|
|
807
|
+
this.renderVersion += 1;
|
|
808
|
+
this.removeMenu();
|
|
809
|
+
}
|
|
810
|
+
move(delta) {
|
|
811
|
+
if (!this.query || this.commands.length === 0) return false;
|
|
812
|
+
this.activeIndex = (this.activeIndex + delta + this.commands.length) % this.commands.length;
|
|
813
|
+
this.syncActiveItem("center");
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
close() {
|
|
817
|
+
if (!this.query) return false;
|
|
818
|
+
this.query = null;
|
|
819
|
+
this.commands = [];
|
|
820
|
+
this.renderVersion += 1;
|
|
821
|
+
this.removeMenu();
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
selectActive() {
|
|
825
|
+
if (!this.query || this.commands.length === 0) return false;
|
|
826
|
+
return this.select(this.activeIndex);
|
|
827
|
+
}
|
|
828
|
+
handleKeydown(event) {
|
|
829
|
+
if (event.key === "ArrowDown") {
|
|
830
|
+
return this.move(1);
|
|
831
|
+
}
|
|
832
|
+
if (event.key === "ArrowUp") {
|
|
833
|
+
return this.move(-1);
|
|
834
|
+
}
|
|
835
|
+
if (event.key === "Enter") {
|
|
836
|
+
return this.selectActive();
|
|
837
|
+
}
|
|
838
|
+
if (event.key === "Escape") {
|
|
839
|
+
return this.close();
|
|
840
|
+
}
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
select(index) {
|
|
844
|
+
const command = this.commands[index];
|
|
845
|
+
if (!this.query || !command) return false;
|
|
846
|
+
const queryRange = { from: this.query.from, to: this.query.to };
|
|
847
|
+
this.close();
|
|
848
|
+
return command.run({
|
|
849
|
+
view: this.view,
|
|
850
|
+
queryRange,
|
|
851
|
+
requestAttachment: (kind, context) => this.requestAttachment(kind, context.queryRange)
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
requestAttachment(kind, queryRange) {
|
|
855
|
+
if (!this.config.attachmentUploader) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
void requestFile(kind).then((file) => {
|
|
859
|
+
if (!file || !this.config.attachmentUploader) return;
|
|
860
|
+
void uploadAttachmentFile(this.view, file, {
|
|
861
|
+
kind,
|
|
862
|
+
source: "slash",
|
|
863
|
+
range: queryRange,
|
|
864
|
+
uploader: this.config.attachmentUploader
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
handleDocumentKeydown = (event) => {
|
|
870
|
+
if (!this.query || event.defaultPrevented) return;
|
|
871
|
+
const handled = this.handleKeydown(event);
|
|
872
|
+
if (!handled) return;
|
|
873
|
+
event.preventDefault();
|
|
874
|
+
event.stopPropagation();
|
|
875
|
+
};
|
|
876
|
+
updateState() {
|
|
877
|
+
const cursor = this.view.state.selection.main.head;
|
|
878
|
+
const query = detectSlashQuery(this.view.state.doc.toString(), cursor);
|
|
879
|
+
if (!query) {
|
|
880
|
+
this.query = null;
|
|
881
|
+
this.commands = [];
|
|
882
|
+
this.renderVersion += 1;
|
|
883
|
+
this.removeMenu();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (!this.query || this.query.query !== query.query || this.query.from !== query.from) {
|
|
887
|
+
this.activeIndex = 0;
|
|
888
|
+
}
|
|
889
|
+
this.query = query;
|
|
890
|
+
this.commands = filterSlashCommands(this.config.commands, query.query);
|
|
891
|
+
this.activeIndex = Math.min(this.activeIndex, Math.max(0, this.commands.length - 1));
|
|
892
|
+
this.renderMenu();
|
|
893
|
+
}
|
|
894
|
+
renderMenu() {
|
|
895
|
+
if (!this.query) return;
|
|
896
|
+
const renderVersion = ++this.renderVersion;
|
|
897
|
+
const queryFrom = this.query.from;
|
|
898
|
+
this.removeMenu();
|
|
899
|
+
this.view.requestMeasure({
|
|
900
|
+
read: (view) => view.coordsAtPos(queryFrom),
|
|
901
|
+
write: (coords) => {
|
|
902
|
+
if (renderVersion !== this.renderVersion || !this.query || !coords) return;
|
|
903
|
+
this.removeMenu();
|
|
904
|
+
this.menu = createSlashMenuElement(
|
|
905
|
+
{ commands: this.commands, activeIndex: this.activeIndex, messages: this.config.messages },
|
|
906
|
+
{
|
|
907
|
+
onHover: (index) => {
|
|
908
|
+
this.activeIndex = index;
|
|
909
|
+
this.syncActiveItem("nearest");
|
|
910
|
+
},
|
|
911
|
+
onSelect: (index) => {
|
|
912
|
+
this.select(index);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
);
|
|
916
|
+
const layout = computeSlashMenuLayout({
|
|
917
|
+
anchor: { left: coords.left, top: coords.top, bottom: coords.bottom },
|
|
918
|
+
viewport: {
|
|
919
|
+
width: this.view.dom.ownerDocument.defaultView?.innerWidth ?? window.innerWidth,
|
|
920
|
+
height: this.view.dom.ownerDocument.defaultView?.innerHeight ?? window.innerHeight
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
this.menu.dataset.mardoraSlashPlacement = layout.placement;
|
|
924
|
+
this.menu.style.left = `${layout.left}px`;
|
|
925
|
+
this.menu.style.maxHeight = `${layout.maxHeight}px`;
|
|
926
|
+
this.menu.style.top = layout.top === null ? "" : `${layout.top}px`;
|
|
927
|
+
this.menu.style.bottom = layout.bottom === null ? "" : `${layout.bottom}px`;
|
|
928
|
+
this.view.dom.classList.add("cm-mardora-slash-open");
|
|
929
|
+
this.hideEditorCaret();
|
|
930
|
+
this.view.dom.appendChild(this.menu);
|
|
931
|
+
this.syncActiveItem("nearest");
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
syncActiveItem(scrollMode) {
|
|
936
|
+
if (!this.menu) return;
|
|
937
|
+
const items = this.menu.querySelectorAll(".cm-mardora-slash-item");
|
|
938
|
+
let activeItem = null;
|
|
939
|
+
items.forEach((item) => {
|
|
940
|
+
const itemIndex = Number(item.dataset.mardoraSlashIndex);
|
|
941
|
+
const isActive = itemIndex === this.activeIndex;
|
|
942
|
+
item.classList.toggle("cm-mardora-slash-item-active", isActive);
|
|
943
|
+
item.setAttribute("aria-selected", String(isActive));
|
|
944
|
+
if (isActive) {
|
|
945
|
+
activeItem = item;
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
if (activeItem) {
|
|
949
|
+
this.scrollActiveItemIntoView(activeItem, scrollMode);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
scrollActiveItemIntoView(activeItem, mode) {
|
|
953
|
+
const list = this.menu?.querySelector(".cm-mardora-slash-list");
|
|
954
|
+
if (!list) return;
|
|
955
|
+
const itemTop = activeItem.offsetTop;
|
|
956
|
+
const itemBottom = itemTop + activeItem.offsetHeight;
|
|
957
|
+
const visibleTop = list.scrollTop;
|
|
958
|
+
const visibleBottom = visibleTop + list.clientHeight;
|
|
959
|
+
if (mode === "center") {
|
|
960
|
+
list.scrollTop = itemTop - (list.clientHeight - activeItem.offsetHeight) / 2;
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (itemTop < visibleTop) {
|
|
964
|
+
list.scrollTop = itemTop;
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (itemBottom > visibleBottom) {
|
|
968
|
+
list.scrollTop = itemBottom - list.clientHeight;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
hideEditorCaret() {
|
|
972
|
+
if (this.previousCaretColor === null) {
|
|
973
|
+
this.previousCaretColor = this.view.contentDOM.style.caretColor;
|
|
974
|
+
}
|
|
975
|
+
this.view.contentDOM.style.caretColor = "transparent";
|
|
976
|
+
}
|
|
977
|
+
restoreEditorCaret() {
|
|
978
|
+
if (this.previousCaretColor === null) return;
|
|
979
|
+
this.view.contentDOM.style.caretColor = this.previousCaretColor;
|
|
980
|
+
this.previousCaretColor = null;
|
|
981
|
+
}
|
|
982
|
+
removeMenu() {
|
|
983
|
+
this.menu?.remove();
|
|
984
|
+
this.menu = null;
|
|
985
|
+
this.view.dom.classList.remove("cm-mardora-slash-open");
|
|
986
|
+
this.restoreEditorCaret();
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
function slashCommands(config = {}) {
|
|
990
|
+
if (config.enabled === false) {
|
|
991
|
+
return [];
|
|
992
|
+
}
|
|
993
|
+
const normalizedConfig = createSlashRuntimeConfig(config);
|
|
994
|
+
const plugin = view.ViewPlugin.define((view) => new SlashCommandViewPlugin(view, normalizedConfig));
|
|
995
|
+
return [
|
|
996
|
+
slashMenuTheme,
|
|
997
|
+
plugin,
|
|
998
|
+
state.Prec.highest(
|
|
999
|
+
view.EditorView.domEventHandlers({
|
|
1000
|
+
keydown(event, view) {
|
|
1001
|
+
const value = view.plugin(plugin);
|
|
1002
|
+
if (!value) return false;
|
|
1003
|
+
const handled = value.handleKeydown(event);
|
|
1004
|
+
if (handled) event.preventDefault();
|
|
1005
|
+
return handled;
|
|
1006
|
+
}
|
|
1007
|
+
})
|
|
1008
|
+
)
|
|
1009
|
+
];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/editor/selection-toolbar/position.ts
|
|
1013
|
+
var viewportPadding2 = 8;
|
|
1014
|
+
var anchorGap2 = 8;
|
|
1015
|
+
function clamp2(value, min, max) {
|
|
1016
|
+
return Math.min(Math.max(value, min), max);
|
|
1017
|
+
}
|
|
1018
|
+
function computeSelectionToolbarLayout(input) {
|
|
1019
|
+
const selectionCenter = (input.anchor.left + input.anchor.right) / 2;
|
|
1020
|
+
const boundary = input.boundary ?? {
|
|
1021
|
+
left: viewportPadding2,
|
|
1022
|
+
right: input.viewport.width - viewportPadding2,
|
|
1023
|
+
top: viewportPadding2,
|
|
1024
|
+
bottom: input.viewport.height - viewportPadding2
|
|
1025
|
+
};
|
|
1026
|
+
const minLeft = Math.max(viewportPadding2, boundary.left);
|
|
1027
|
+
const maxLeft = Math.max(minLeft, Math.min(input.viewport.width - input.floating.width - viewportPadding2, boundary.right - input.floating.width));
|
|
1028
|
+
const left = clamp2(Math.round(selectionCenter - input.floating.width / 2), minLeft, maxLeft);
|
|
1029
|
+
const topLimit = Math.max(viewportPadding2, boundary.top);
|
|
1030
|
+
const bottomLimit = Math.min(input.viewport.height - viewportPadding2, boundary.bottom);
|
|
1031
|
+
const availableAbove = Math.max(1, input.anchor.top - anchorGap2 - topLimit);
|
|
1032
|
+
const availableBelow = Math.max(1, bottomLimit - input.anchor.bottom - anchorGap2);
|
|
1033
|
+
const placement = availableAbove >= input.floating.height || availableAbove >= availableBelow ? "top" : "bottom";
|
|
1034
|
+
if (placement === "top") {
|
|
1035
|
+
return {
|
|
1036
|
+
placement,
|
|
1037
|
+
left,
|
|
1038
|
+
top: Math.max(topLimit, Math.round(input.anchor.top - anchorGap2 - input.floating.height)),
|
|
1039
|
+
maxHeight: availableAbove
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
return {
|
|
1043
|
+
placement,
|
|
1044
|
+
left,
|
|
1045
|
+
top: Math.round(input.anchor.bottom + anchorGap2),
|
|
1046
|
+
maxHeight: availableBelow
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/editor/selection-toolbar/commands.ts
|
|
1051
|
+
var markdownLinkPattern = /^\[([^\]]*)\]\(([^)]*)\)$/;
|
|
1052
|
+
var urlPattern = /^(https?:\/\/|www\.)[^\s]+$/i;
|
|
1053
|
+
var listMarkerPattern = /^(\s*)([-*+]|\d+\.)\s(\[[ xX]\]\s)?/;
|
|
1054
|
+
var headingMarkerPattern = /^(\s*)(#{1,6})(?:[ \t]+|$)/;
|
|
1055
|
+
function selectedText(input) {
|
|
1056
|
+
return input.doc.slice(input.from, input.to);
|
|
1057
|
+
}
|
|
1058
|
+
function escapeRegExp(value) {
|
|
1059
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1060
|
+
}
|
|
1061
|
+
function buildHtmlWrapper(input) {
|
|
1062
|
+
if (input.htmlTag) {
|
|
1063
|
+
return { open: `<${input.htmlTag}>`, close: `</${input.htmlTag}>` };
|
|
1064
|
+
}
|
|
1065
|
+
if (input.spanStyle) {
|
|
1066
|
+
return { open: `<span style="${input.spanStyle.property}: ${input.spanStyle.value}">`, close: "</span>" };
|
|
1067
|
+
}
|
|
1068
|
+
const marker = input.marker ?? "";
|
|
1069
|
+
return { open: marker, close: marker };
|
|
1070
|
+
}
|
|
1071
|
+
function buildInlineFormatChange(input) {
|
|
1072
|
+
const text = selectedText(input);
|
|
1073
|
+
const { open, close } = buildHtmlWrapper(input);
|
|
1074
|
+
if (input.clear) {
|
|
1075
|
+
const tagPattern = input.spanStyle ? new RegExp(
|
|
1076
|
+
`^<span style="${escapeRegExp(input.spanStyle.property)}: ${input.spanStyle.value ? escapeRegExp(input.spanStyle.value) : "#[0-9a-fA-F]{6}"}">([\\s\\S]*)<\\/span>$`
|
|
1077
|
+
) : input.htmlTag ? new RegExp(`^<${input.htmlTag}>([\\s\\S]*)<\\/${input.htmlTag}>$`) : null;
|
|
1078
|
+
const match = tagPattern ? text.match(tagPattern) : null;
|
|
1079
|
+
const insert2 = match?.[1] ?? text;
|
|
1080
|
+
return {
|
|
1081
|
+
changes: { from: input.from, to: input.to, insert: insert2 },
|
|
1082
|
+
selection: { anchor: input.from, head: input.from + insert2.length }
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
const beforeFrom = Math.max(0, input.from - open.length);
|
|
1086
|
+
const afterTo = Math.min(input.doc.length, input.to + close.length);
|
|
1087
|
+
const before = input.doc.slice(beforeFrom, input.from);
|
|
1088
|
+
const after = input.doc.slice(input.to, afterTo);
|
|
1089
|
+
if (before === open && after === close) {
|
|
1090
|
+
return {
|
|
1091
|
+
changes: [
|
|
1092
|
+
{ from: beforeFrom, to: input.from, insert: "" },
|
|
1093
|
+
{ from: input.to, to: afterTo, insert: "" }
|
|
1094
|
+
],
|
|
1095
|
+
selection: { anchor: beforeFrom, head: beforeFrom + text.length }
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
const insert = `${open}${text}${close}`;
|
|
1099
|
+
return {
|
|
1100
|
+
changes: { from: input.from, to: input.to, insert },
|
|
1101
|
+
selection: { anchor: input.from + open.length, head: input.from + open.length + text.length }
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
function parseSelectedLink(text) {
|
|
1105
|
+
const linkMatch = text.match(markdownLinkPattern);
|
|
1106
|
+
if (linkMatch) {
|
|
1107
|
+
return { kind: "markdown-link", title: linkMatch[1] ?? "", url: linkMatch[2] ?? "" };
|
|
1108
|
+
}
|
|
1109
|
+
if (urlPattern.test(text)) {
|
|
1110
|
+
return { kind: "url", title: "", url: text };
|
|
1111
|
+
}
|
|
1112
|
+
return { kind: "text", title: text, url: "" };
|
|
1113
|
+
}
|
|
1114
|
+
function buildLinkChange(input) {
|
|
1115
|
+
if (input.remove) {
|
|
1116
|
+
return {
|
|
1117
|
+
changes: { from: input.from, to: input.to, insert: input.title },
|
|
1118
|
+
selection: { anchor: input.from, head: input.from + input.title.length }
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
if (!input.url.trim()) {
|
|
1122
|
+
throw new Error("Link URL is required");
|
|
1123
|
+
}
|
|
1124
|
+
const insert = `[${input.title}](${input.url})`;
|
|
1125
|
+
return {
|
|
1126
|
+
changes: { from: input.from, to: input.to, insert },
|
|
1127
|
+
selection: { anchor: input.from, head: input.from + insert.length }
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
function lineRanges(doc, from, to) {
|
|
1131
|
+
const ranges = [];
|
|
1132
|
+
let position = 0;
|
|
1133
|
+
for (const text of doc.split("\n")) {
|
|
1134
|
+
const lineFrom = position;
|
|
1135
|
+
const lineTo = position + text.length;
|
|
1136
|
+
if (lineTo >= from && lineFrom <= to) {
|
|
1137
|
+
ranges.push({ from: lineFrom, text });
|
|
1138
|
+
}
|
|
1139
|
+
position = lineTo + 1;
|
|
1140
|
+
}
|
|
1141
|
+
return ranges;
|
|
1142
|
+
}
|
|
1143
|
+
function markerFor(kind, order) {
|
|
1144
|
+
if (kind === "ordered") return `${order}. `;
|
|
1145
|
+
if (kind === "task") return "- [ ] ";
|
|
1146
|
+
return "- ";
|
|
1147
|
+
}
|
|
1148
|
+
function buildListChange(input) {
|
|
1149
|
+
const changes = [];
|
|
1150
|
+
let order = 1;
|
|
1151
|
+
for (const line of lineRanges(input.doc, input.from, input.to)) {
|
|
1152
|
+
const match = line.text.match(listMarkerPattern);
|
|
1153
|
+
const actualMarker = markerFor(input.kind, order);
|
|
1154
|
+
if (match) {
|
|
1155
|
+
const indent = match[1] ?? "";
|
|
1156
|
+
const isOrdered = /^\d+\.$/.test(match[2] ?? "");
|
|
1157
|
+
const isUnordered = /^[-*+]$/.test(match[2] ?? "");
|
|
1158
|
+
const hasTask = !!match[3];
|
|
1159
|
+
const same = input.kind === "ordered" && isOrdered && !hasTask || input.kind === "unordered" && isUnordered && !hasTask || input.kind === "task" && hasTask;
|
|
1160
|
+
changes.push({
|
|
1161
|
+
from: line.from,
|
|
1162
|
+
to: line.from + match[0].length,
|
|
1163
|
+
insert: same ? indent : indent + actualMarker
|
|
1164
|
+
});
|
|
1165
|
+
} else {
|
|
1166
|
+
const indentLength = line.text.match(/^(\s*)/)?.[1]?.length ?? 0;
|
|
1167
|
+
changes.push({ from: line.from + indentLength, to: line.from + indentLength, insert: actualMarker });
|
|
1168
|
+
}
|
|
1169
|
+
if (input.kind === "ordered") order += 1;
|
|
1170
|
+
}
|
|
1171
|
+
return { changes };
|
|
1172
|
+
}
|
|
1173
|
+
function headingLevelForLine(text) {
|
|
1174
|
+
return text.match(headingMarkerPattern)?.[2]?.length ?? 0;
|
|
1175
|
+
}
|
|
1176
|
+
function escapePlainTextBlockStart(text) {
|
|
1177
|
+
if (/^\d+[.)]\s/.test(text)) {
|
|
1178
|
+
return text.replace(/^(\d+)([.)])/, "$1\\$2");
|
|
1179
|
+
}
|
|
1180
|
+
if (/^[-*+]\s/.test(text) || /^#{1,6}(\s|$)/.test(text) || /^>\s?/.test(text)) {
|
|
1181
|
+
return `\\${text}`;
|
|
1182
|
+
}
|
|
1183
|
+
return text;
|
|
1184
|
+
}
|
|
1185
|
+
function unescapePlainTextBlockStart(text) {
|
|
1186
|
+
if (/^\d+\\[.)]\s/.test(text)) {
|
|
1187
|
+
return text.replace(/^(\d+)\\([.)])/, "$1$2");
|
|
1188
|
+
}
|
|
1189
|
+
if (/^\\([-*+#>])/.test(text)) {
|
|
1190
|
+
return text.slice(1);
|
|
1191
|
+
}
|
|
1192
|
+
return text;
|
|
1193
|
+
}
|
|
1194
|
+
function detectSelectionBlockType(input) {
|
|
1195
|
+
const line = lineRanges(input.doc, input.from, input.to)[0];
|
|
1196
|
+
const level = line ? headingLevelForLine(line.text) : 0;
|
|
1197
|
+
return level > 0 ? `heading-${level}` : "text";
|
|
1198
|
+
}
|
|
1199
|
+
function buildBlockTypeChange(input) {
|
|
1200
|
+
const changes = [];
|
|
1201
|
+
const lines = lineRanges(input.doc, input.from, input.to);
|
|
1202
|
+
let accumulatedDelta = 0;
|
|
1203
|
+
let selectionAnchor = null;
|
|
1204
|
+
let selectionHead = null;
|
|
1205
|
+
for (const line of lines) {
|
|
1206
|
+
const lineTo = line.from + line.text.length;
|
|
1207
|
+
const headingMatch = line.text.match(headingMarkerPattern);
|
|
1208
|
+
const indent = headingMatch?.[1] ?? line.text.match(/^(\s*)/)?.[1] ?? "";
|
|
1209
|
+
const oldPrefixLength = headingMatch ? headingMatch[0].length : indent.length;
|
|
1210
|
+
const content = line.text.slice(oldPrefixLength);
|
|
1211
|
+
if (input.level === 0 && headingMatch) {
|
|
1212
|
+
const escapedContent = escapePlainTextBlockStart(content);
|
|
1213
|
+
if (escapedContent !== content) {
|
|
1214
|
+
const insert = `${indent}${escapedContent}`;
|
|
1215
|
+
const delta2 = insert.length - line.text.length;
|
|
1216
|
+
changes.push({
|
|
1217
|
+
from: line.from,
|
|
1218
|
+
to: lineTo,
|
|
1219
|
+
insert
|
|
1220
|
+
});
|
|
1221
|
+
const newContentFrom2 = line.from + accumulatedDelta + indent.length;
|
|
1222
|
+
const newContentTo2 = lineTo + accumulatedDelta + delta2;
|
|
1223
|
+
selectionAnchor ??= newContentFrom2;
|
|
1224
|
+
selectionHead = newContentTo2;
|
|
1225
|
+
accumulatedDelta += delta2;
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const newPrefix = input.level === 0 ? indent : `${indent}${"#".repeat(input.level)} `;
|
|
1230
|
+
const delta = newPrefix.length - oldPrefixLength;
|
|
1231
|
+
const headingContent = input.level > 0 ? unescapePlainTextBlockStart(content) : content;
|
|
1232
|
+
if (input.level > 0 && headingContent !== content) {
|
|
1233
|
+
const insert = `${newPrefix}${headingContent}`;
|
|
1234
|
+
const lineDelta = insert.length - line.text.length;
|
|
1235
|
+
changes.push({
|
|
1236
|
+
from: line.from,
|
|
1237
|
+
to: lineTo,
|
|
1238
|
+
insert
|
|
1239
|
+
});
|
|
1240
|
+
const newContentFrom2 = line.from + accumulatedDelta + newPrefix.length;
|
|
1241
|
+
const newContentTo2 = lineTo + accumulatedDelta + lineDelta;
|
|
1242
|
+
selectionAnchor ??= newContentFrom2;
|
|
1243
|
+
selectionHead = newContentTo2;
|
|
1244
|
+
accumulatedDelta += lineDelta;
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (newPrefix !== line.text.slice(0, oldPrefixLength)) {
|
|
1248
|
+
changes.push({
|
|
1249
|
+
from: line.from,
|
|
1250
|
+
to: line.from + oldPrefixLength,
|
|
1251
|
+
insert: newPrefix
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
const newContentFrom = line.from + accumulatedDelta + newPrefix.length;
|
|
1255
|
+
const newContentTo = lineTo + accumulatedDelta + delta;
|
|
1256
|
+
selectionAnchor ??= newContentFrom;
|
|
1257
|
+
selectionHead = newContentTo;
|
|
1258
|
+
accumulatedDelta += delta;
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
changes,
|
|
1262
|
+
selection: {
|
|
1263
|
+
anchor: selectionAnchor ?? input.from,
|
|
1264
|
+
head: selectionHead ?? input.to
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/editor/selection-toolbar/menu.ts
|
|
1270
|
+
function iconButton(button, callbacks) {
|
|
1271
|
+
const element = document.createElement("button");
|
|
1272
|
+
element.type = "button";
|
|
1273
|
+
element.className = button.active ? "cm-mardora-selection-toolbar-button cm-mardora-selection-toolbar-button-active" : "cm-mardora-selection-toolbar-button";
|
|
1274
|
+
element.setAttribute("aria-label", button.label);
|
|
1275
|
+
element.setAttribute("aria-pressed", String(!!button.active));
|
|
1276
|
+
element.dataset.mardoraSelectionAction = button.id;
|
|
1277
|
+
element.addEventListener("mousedown", (event) => {
|
|
1278
|
+
event.preventDefault();
|
|
1279
|
+
callbacks.onAction(button.id);
|
|
1280
|
+
});
|
|
1281
|
+
if (button.text) {
|
|
1282
|
+
element.classList.add("cm-mardora-selection-toolbar-block-button");
|
|
1283
|
+
const label = document.createElement("span");
|
|
1284
|
+
label.className = "cm-mardora-selection-toolbar-button-text";
|
|
1285
|
+
label.textContent = button.text;
|
|
1286
|
+
element.appendChild(label);
|
|
1287
|
+
} else if (button.id === "block-type") {
|
|
1288
|
+
element.classList.add("cm-mardora-selection-toolbar-block-button");
|
|
1289
|
+
const icon = chunkWFVCG4LD_cjs.createMardoraIcon(button.icon);
|
|
1290
|
+
if (icon) element.appendChild(icon);
|
|
1291
|
+
} else {
|
|
1292
|
+
const icon = chunkWFVCG4LD_cjs.createMardoraIcon(button.icon);
|
|
1293
|
+
if (icon) {
|
|
1294
|
+
element.appendChild(icon);
|
|
1295
|
+
} else {
|
|
1296
|
+
element.textContent = button.label;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return element;
|
|
1300
|
+
}
|
|
1301
|
+
function blockTypeIcon(type) {
|
|
1302
|
+
const icon = document.createElement("span");
|
|
1303
|
+
icon.className = "cm-mardora-selection-toolbar-block-menu-icon";
|
|
1304
|
+
if (type === "text") {
|
|
1305
|
+
const svg = chunkWFVCG4LD_cjs.createMardoraIcon("text-align-start");
|
|
1306
|
+
if (svg) icon.appendChild(svg);
|
|
1307
|
+
} else {
|
|
1308
|
+
icon.textContent = `H${type.slice("heading-".length)}`;
|
|
1309
|
+
}
|
|
1310
|
+
return icon;
|
|
1311
|
+
}
|
|
1312
|
+
function appendBlockTypePanel(root, state, callbacks) {
|
|
1313
|
+
const list = document.createElement("div");
|
|
1314
|
+
list.className = "cm-mardora-selection-toolbar-block-menu";
|
|
1315
|
+
list.setAttribute("role", "menu");
|
|
1316
|
+
for (const item of state.blockTypes) {
|
|
1317
|
+
const button = document.createElement("button");
|
|
1318
|
+
button.type = "button";
|
|
1319
|
+
button.className = item.type === state.blockType ? "cm-mardora-selection-toolbar-block-item cm-mardora-selection-toolbar-block-item-active" : "cm-mardora-selection-toolbar-block-item";
|
|
1320
|
+
button.setAttribute("aria-label", item.label);
|
|
1321
|
+
button.setAttribute("aria-pressed", String(item.type === state.blockType));
|
|
1322
|
+
button.setAttribute("role", "menuitemradio");
|
|
1323
|
+
button.dataset.mardoraBlockType = item.type;
|
|
1324
|
+
button.addEventListener("mousedown", (event) => {
|
|
1325
|
+
event.preventDefault();
|
|
1326
|
+
callbacks.onBlockType(item.type);
|
|
1327
|
+
});
|
|
1328
|
+
const label = document.createElement("span");
|
|
1329
|
+
label.className = "cm-mardora-selection-toolbar-block-menu-label";
|
|
1330
|
+
label.textContent = item.label;
|
|
1331
|
+
button.append(blockTypeIcon(item.type), label);
|
|
1332
|
+
list.appendChild(button);
|
|
1333
|
+
}
|
|
1334
|
+
root.appendChild(list);
|
|
1335
|
+
}
|
|
1336
|
+
function divider() {
|
|
1337
|
+
const element = document.createElement("span");
|
|
1338
|
+
element.className = "cm-mardora-selection-toolbar-divider";
|
|
1339
|
+
element.setAttribute("aria-hidden", "true");
|
|
1340
|
+
return element;
|
|
1341
|
+
}
|
|
1342
|
+
function appendToolbarButtons(root, buttons, callbacks) {
|
|
1343
|
+
const groups = [buttons.slice(0, 1), buttons.slice(1, 8), buttons.slice(8, 9), buttons.slice(9)];
|
|
1344
|
+
groups.forEach((group, index) => {
|
|
1345
|
+
if (group.length === 0) return;
|
|
1346
|
+
if (index > 0) root.appendChild(divider());
|
|
1347
|
+
for (const button of group) root.appendChild(iconButton(button, callbacks));
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
function paletteButton(item, className, callback) {
|
|
1351
|
+
const element = document.createElement("button");
|
|
1352
|
+
element.type = "button";
|
|
1353
|
+
element.className = className;
|
|
1354
|
+
element.setAttribute("aria-label", item.label);
|
|
1355
|
+
element.title = item.label;
|
|
1356
|
+
element.dataset.mardoraSwatch = item.id;
|
|
1357
|
+
if (item.value) element.style.setProperty("--mardora-swatch-color", item.value);
|
|
1358
|
+
element.addEventListener("mousedown", (event) => {
|
|
1359
|
+
event.preventDefault();
|
|
1360
|
+
callback(item.value);
|
|
1361
|
+
});
|
|
1362
|
+
return element;
|
|
1363
|
+
}
|
|
1364
|
+
function appendPalette(root, title, items, callback) {
|
|
1365
|
+
const group = document.createElement("div");
|
|
1366
|
+
group.className = "cm-mardora-selection-toolbar-palette-group";
|
|
1367
|
+
const label = document.createElement("div");
|
|
1368
|
+
label.className = "cm-mardora-selection-toolbar-palette-label";
|
|
1369
|
+
label.textContent = title;
|
|
1370
|
+
group.appendChild(label);
|
|
1371
|
+
const grid = document.createElement("div");
|
|
1372
|
+
grid.className = "cm-mardora-selection-toolbar-swatch-grid";
|
|
1373
|
+
for (const item of items) {
|
|
1374
|
+
grid.appendChild(paletteButton(item, "cm-mardora-selection-toolbar-swatch", callback));
|
|
1375
|
+
}
|
|
1376
|
+
group.appendChild(grid);
|
|
1377
|
+
root.appendChild(group);
|
|
1378
|
+
}
|
|
1379
|
+
function appendLinkAction(actions, label, iconName, callback, danger = false) {
|
|
1380
|
+
const button = document.createElement("button");
|
|
1381
|
+
button.type = "button";
|
|
1382
|
+
button.className = danger ? "cm-mardora-selection-toolbar-link-button cm-mardora-selection-toolbar-link-button-danger" : "cm-mardora-selection-toolbar-link-button";
|
|
1383
|
+
button.setAttribute("aria-label", label);
|
|
1384
|
+
const svg = chunkWFVCG4LD_cjs.createMardoraIcon(iconName);
|
|
1385
|
+
if (svg) button.appendChild(svg);
|
|
1386
|
+
button.addEventListener("mousedown", (event) => {
|
|
1387
|
+
event.preventDefault();
|
|
1388
|
+
callback();
|
|
1389
|
+
});
|
|
1390
|
+
actions.appendChild(button);
|
|
1391
|
+
}
|
|
1392
|
+
function appendLinkPanel(root, state, callbacks) {
|
|
1393
|
+
const title = document.createElement("input");
|
|
1394
|
+
title.className = "cm-mardora-selection-toolbar-link-input";
|
|
1395
|
+
title.setAttribute("aria-label", state.messages.link.title);
|
|
1396
|
+
title.value = state.link.title;
|
|
1397
|
+
title.addEventListener("input", () => callbacks.onLinkInput("title", title.value));
|
|
1398
|
+
title.addEventListener("keydown", (event) => {
|
|
1399
|
+
if (event.key === "Enter") callbacks.onLinkSubmit();
|
|
1400
|
+
if (event.key === "Escape") callbacks.onCancelPanel();
|
|
1401
|
+
});
|
|
1402
|
+
const url = document.createElement("input");
|
|
1403
|
+
url.className = "cm-mardora-selection-toolbar-link-input";
|
|
1404
|
+
url.setAttribute("aria-label", state.messages.link.url);
|
|
1405
|
+
url.value = state.link.url;
|
|
1406
|
+
url.addEventListener("input", () => callbacks.onLinkInput("url", url.value));
|
|
1407
|
+
url.addEventListener("keydown", (event) => {
|
|
1408
|
+
if (event.key === "Enter") callbacks.onLinkSubmit();
|
|
1409
|
+
if (event.key === "Escape") callbacks.onCancelPanel();
|
|
1410
|
+
});
|
|
1411
|
+
const actions = document.createElement("div");
|
|
1412
|
+
actions.className = "cm-mardora-selection-toolbar-link-actions";
|
|
1413
|
+
appendLinkAction(actions, state.link.copied ? state.messages.link.copied : state.messages.link.copy, "copy", callbacks.onLinkCopy);
|
|
1414
|
+
appendLinkAction(actions, state.messages.link.open, "external-link", callbacks.onLinkOpen);
|
|
1415
|
+
if (state.link.canRemove) appendLinkAction(actions, state.messages.link.remove, "trash-2", callbacks.onLinkRemove, true);
|
|
1416
|
+
root.append(title, url);
|
|
1417
|
+
if (state.link.error) {
|
|
1418
|
+
const error = document.createElement("div");
|
|
1419
|
+
error.className = "cm-mardora-selection-toolbar-error";
|
|
1420
|
+
error.textContent = state.link.error;
|
|
1421
|
+
root.appendChild(error);
|
|
1422
|
+
}
|
|
1423
|
+
root.appendChild(actions);
|
|
1424
|
+
queueMicrotask(() => {
|
|
1425
|
+
title.focus();
|
|
1426
|
+
title.select();
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
function createSelectionToolbarElement(state, callbacks) {
|
|
1430
|
+
const root = document.createElement("div");
|
|
1431
|
+
root.className = state.panel === "toolbar" || state.panel === "block-type" ? "cm-mardora-selection-toolbar" : "cm-mardora-selection-toolbar cm-mardora-selection-toolbar-panel";
|
|
1432
|
+
root.setAttribute("role", "toolbar");
|
|
1433
|
+
root.addEventListener("mousedown", (event) => {
|
|
1434
|
+
const target = event.target;
|
|
1435
|
+
if (target instanceof HTMLElement && target.closest(".cm-mardora-selection-toolbar-link-input")) return;
|
|
1436
|
+
event.preventDefault();
|
|
1437
|
+
});
|
|
1438
|
+
if (state.panel === "toolbar" || state.panel === "block-type") {
|
|
1439
|
+
appendToolbarButtons(root, state.buttons, callbacks);
|
|
1440
|
+
if (state.panel === "block-type") appendBlockTypePanel(root, state, callbacks);
|
|
1441
|
+
} else if (state.panel === "link") {
|
|
1442
|
+
appendLinkPanel(root, state, callbacks);
|
|
1443
|
+
} else if (state.panel === "color") {
|
|
1444
|
+
appendPalette(root, state.messages.panels.textColor, state.textColors, callbacks.onColor);
|
|
1445
|
+
} else {
|
|
1446
|
+
appendPalette(root, state.messages.panels.highlightColor, state.highlightColors, callbacks.onHighlight);
|
|
1447
|
+
}
|
|
1448
|
+
return root;
|
|
1449
|
+
}
|
|
1450
|
+
var selectionToolbarTheme = view.EditorView.baseTheme({
|
|
1451
|
+
".cm-mardora-selection-toolbar": {
|
|
1452
|
+
position: "fixed",
|
|
1453
|
+
zIndex: "1001",
|
|
1454
|
+
display: "inline-flex",
|
|
1455
|
+
alignItems: "center",
|
|
1456
|
+
gap: "2px",
|
|
1457
|
+
border: "1px solid rgba(24, 24, 27, 0.14)",
|
|
1458
|
+
borderRadius: "10px",
|
|
1459
|
+
background: "var(--mardora-selection-toolbar-bg, #ffffff)",
|
|
1460
|
+
boxShadow: "0 14px 38px rgba(15, 23, 42, 0.16)",
|
|
1461
|
+
padding: "4px",
|
|
1462
|
+
color: "var(--mardora-selection-toolbar-fg, #18181b)",
|
|
1463
|
+
fontFamily: "var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif)",
|
|
1464
|
+
userSelect: "none"
|
|
1465
|
+
},
|
|
1466
|
+
".cm-mardora-selection-toolbar-panel": {
|
|
1467
|
+
display: "flex",
|
|
1468
|
+
flexDirection: "column",
|
|
1469
|
+
alignItems: "stretch",
|
|
1470
|
+
width: "336px",
|
|
1471
|
+
gap: "6px",
|
|
1472
|
+
padding: "8px"
|
|
1473
|
+
},
|
|
1474
|
+
".cm-mardora-selection-toolbar-block-button": {
|
|
1475
|
+
minWidth: "44px",
|
|
1476
|
+
width: "44px",
|
|
1477
|
+
fontWeight: "400"
|
|
1478
|
+
},
|
|
1479
|
+
".cm-mardora-selection-toolbar-button-text": {
|
|
1480
|
+
fontSize: "13px",
|
|
1481
|
+
lineHeight: "1"
|
|
1482
|
+
},
|
|
1483
|
+
".cm-mardora-selection-toolbar-button, .cm-mardora-selection-toolbar-link-button": {
|
|
1484
|
+
display: "inline-flex",
|
|
1485
|
+
alignItems: "center",
|
|
1486
|
+
justifyContent: "center",
|
|
1487
|
+
width: "30px",
|
|
1488
|
+
height: "30px",
|
|
1489
|
+
border: "0",
|
|
1490
|
+
borderRadius: "7px",
|
|
1491
|
+
background: "transparent",
|
|
1492
|
+
color: "inherit",
|
|
1493
|
+
cursor: "default",
|
|
1494
|
+
padding: "0"
|
|
1495
|
+
},
|
|
1496
|
+
".cm-mardora-selection-toolbar-button:hover, .cm-mardora-selection-toolbar-button-active": {
|
|
1497
|
+
background: "var(--mardora-selection-toolbar-active, #f4f4f5)"
|
|
1498
|
+
},
|
|
1499
|
+
".cm-mardora-selection-toolbar-button svg, .cm-mardora-selection-toolbar-link-button svg": {
|
|
1500
|
+
width: "16px",
|
|
1501
|
+
height: "16px",
|
|
1502
|
+
strokeWidth: "2"
|
|
1503
|
+
},
|
|
1504
|
+
".cm-mardora-selection-toolbar-divider": {
|
|
1505
|
+
width: "1px",
|
|
1506
|
+
height: "22px",
|
|
1507
|
+
margin: "0 4px",
|
|
1508
|
+
background: "var(--mardora-selection-toolbar-border, #e4e4e7)"
|
|
1509
|
+
},
|
|
1510
|
+
".cm-mardora-selection-toolbar-block-menu": {
|
|
1511
|
+
position: "absolute",
|
|
1512
|
+
left: "0",
|
|
1513
|
+
zIndex: "1002",
|
|
1514
|
+
display: "flex",
|
|
1515
|
+
flexDirection: "column",
|
|
1516
|
+
gap: "1px",
|
|
1517
|
+
boxSizing: "border-box",
|
|
1518
|
+
width: "168px",
|
|
1519
|
+
maxHeight: "var(--mardora-selection-toolbar-popover-max-height, 240px)",
|
|
1520
|
+
overflowY: "auto",
|
|
1521
|
+
border: "1px solid rgba(24, 24, 27, 0.14)",
|
|
1522
|
+
borderRadius: "8px",
|
|
1523
|
+
background: "var(--mardora-selection-toolbar-bg, #ffffff)",
|
|
1524
|
+
boxShadow: "0 12px 28px rgba(15, 23, 42, 0.14)",
|
|
1525
|
+
padding: "5px"
|
|
1526
|
+
},
|
|
1527
|
+
'.cm-mardora-selection-toolbar[data-mardora-selection-placement="bottom"] .cm-mardora-selection-toolbar-block-menu': {
|
|
1528
|
+
top: "calc(100% + 6px)"
|
|
1529
|
+
},
|
|
1530
|
+
'.cm-mardora-selection-toolbar[data-mardora-selection-placement="top"] .cm-mardora-selection-toolbar-block-menu': {
|
|
1531
|
+
bottom: "calc(100% + 6px)"
|
|
1532
|
+
},
|
|
1533
|
+
".cm-mardora-selection-toolbar-block-item": {
|
|
1534
|
+
display: "flex",
|
|
1535
|
+
alignItems: "center",
|
|
1536
|
+
gap: "8px",
|
|
1537
|
+
border: "0",
|
|
1538
|
+
borderRadius: "6px",
|
|
1539
|
+
background: "transparent",
|
|
1540
|
+
color: "inherit",
|
|
1541
|
+
cursor: "default",
|
|
1542
|
+
font: "inherit",
|
|
1543
|
+
minHeight: "28px",
|
|
1544
|
+
padding: "4px 8px",
|
|
1545
|
+
textAlign: "left"
|
|
1546
|
+
},
|
|
1547
|
+
".cm-mardora-selection-toolbar-block-item:hover, .cm-mardora-selection-toolbar-block-item-active": {
|
|
1548
|
+
background: "var(--mardora-selection-toolbar-active, #f4f4f5)"
|
|
1549
|
+
},
|
|
1550
|
+
".cm-mardora-selection-toolbar-block-menu-icon": {
|
|
1551
|
+
display: "inline-flex",
|
|
1552
|
+
alignItems: "center",
|
|
1553
|
+
justifyContent: "center",
|
|
1554
|
+
width: "22px",
|
|
1555
|
+
color: "inherit",
|
|
1556
|
+
fontSize: "12px",
|
|
1557
|
+
fontWeight: "400",
|
|
1558
|
+
lineHeight: "1"
|
|
1559
|
+
},
|
|
1560
|
+
".cm-mardora-selection-toolbar-block-menu-label": {
|
|
1561
|
+
fontSize: "13px",
|
|
1562
|
+
lineHeight: "1.1"
|
|
1563
|
+
},
|
|
1564
|
+
".cm-mardora-selection-toolbar-link-input": {
|
|
1565
|
+
boxSizing: "border-box",
|
|
1566
|
+
width: "100%",
|
|
1567
|
+
border: "0",
|
|
1568
|
+
borderRadius: "7px",
|
|
1569
|
+
background: "var(--mardora-selection-toolbar-input, #f4f4f5)",
|
|
1570
|
+
color: "inherit",
|
|
1571
|
+
font: "inherit",
|
|
1572
|
+
fontSize: "14px",
|
|
1573
|
+
outline: "none",
|
|
1574
|
+
padding: "8px 10px"
|
|
1575
|
+
},
|
|
1576
|
+
".cm-mardora-selection-toolbar-link-actions": {
|
|
1577
|
+
display: "flex",
|
|
1578
|
+
alignItems: "center",
|
|
1579
|
+
justifyContent: "flex-end",
|
|
1580
|
+
gap: "6px"
|
|
1581
|
+
},
|
|
1582
|
+
".cm-mardora-selection-toolbar-link-button:hover": {
|
|
1583
|
+
background: "var(--mardora-selection-toolbar-active, #f4f4f5)"
|
|
1584
|
+
},
|
|
1585
|
+
".cm-mardora-selection-toolbar-link-button-danger": {
|
|
1586
|
+
color: "var(--mardora-selection-toolbar-danger, #dc2626)"
|
|
1587
|
+
},
|
|
1588
|
+
".cm-mardora-selection-toolbar-link-button-danger:hover": {
|
|
1589
|
+
background: "var(--mardora-selection-toolbar-danger-bg, #fee2e2)"
|
|
1590
|
+
},
|
|
1591
|
+
".cm-mardora-selection-toolbar-error": {
|
|
1592
|
+
color: "var(--mardora-selection-toolbar-danger, #dc2626)",
|
|
1593
|
+
fontSize: "12px",
|
|
1594
|
+
padding: "0 2px"
|
|
1595
|
+
},
|
|
1596
|
+
".cm-mardora-selection-toolbar-palette-label": {
|
|
1597
|
+
color: "var(--mardora-selection-toolbar-muted, #71717a)",
|
|
1598
|
+
fontSize: "12px",
|
|
1599
|
+
padding: "0 2px 4px"
|
|
1600
|
+
},
|
|
1601
|
+
".cm-mardora-selection-toolbar-swatch-grid": {
|
|
1602
|
+
display: "grid",
|
|
1603
|
+
gridTemplateColumns: "repeat(8, 24px)",
|
|
1604
|
+
gap: "6px"
|
|
1605
|
+
},
|
|
1606
|
+
".cm-mardora-selection-toolbar-swatch": {
|
|
1607
|
+
width: "24px",
|
|
1608
|
+
height: "24px",
|
|
1609
|
+
border: "1px solid var(--mardora-selection-toolbar-border, #e4e4e7)",
|
|
1610
|
+
borderRadius: "6px",
|
|
1611
|
+
background: "var(--mardora-swatch-color, transparent)",
|
|
1612
|
+
boxShadow: "inset 0 0 0 1px rgba(255, 255, 255, 0.72)",
|
|
1613
|
+
cursor: "default",
|
|
1614
|
+
outline: "none",
|
|
1615
|
+
padding: "0",
|
|
1616
|
+
transition: "border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease"
|
|
1617
|
+
},
|
|
1618
|
+
".cm-mardora-selection-toolbar-swatch:hover": {
|
|
1619
|
+
borderColor: "var(--mardora-selection-toolbar-swatch-hover-border, #a1a1aa)",
|
|
1620
|
+
boxShadow: "0 0 0 3px var(--mardora-selection-toolbar-swatch-hover-ring, rgba(24, 24, 27, 0.08)), inset 0 0 0 1px rgba(255, 255, 255, 0.72)",
|
|
1621
|
+
transform: "translateY(-1px)"
|
|
1622
|
+
},
|
|
1623
|
+
".cm-mardora-selection-toolbar-swatch:focus-visible": {
|
|
1624
|
+
borderColor: "var(--mardora-selection-toolbar-swatch-hover-border, #71717a)",
|
|
1625
|
+
boxShadow: "0 0 0 3px var(--mardora-selection-toolbar-swatch-hover-ring, rgba(24, 24, 27, 0.12)), inset 0 0 0 1px rgba(255, 255, 255, 0.72)"
|
|
1626
|
+
},
|
|
1627
|
+
"&dark .cm-mardora-selection-toolbar": {
|
|
1628
|
+
"--mardora-selection-toolbar-bg": "#18181b",
|
|
1629
|
+
"--mardora-selection-toolbar-fg": "#f4f4f5",
|
|
1630
|
+
"--mardora-selection-toolbar-active": "#27272a",
|
|
1631
|
+
"--mardora-selection-toolbar-border": "#3f3f46",
|
|
1632
|
+
"--mardora-selection-toolbar-input": "#27272a",
|
|
1633
|
+
"--mardora-selection-toolbar-muted": "#a1a1aa",
|
|
1634
|
+
"--mardora-selection-toolbar-link": "#60a5fa",
|
|
1635
|
+
"--mardora-selection-toolbar-danger": "#f87171",
|
|
1636
|
+
"--mardora-selection-toolbar-danger-bg": "rgba(248, 113, 113, 0.18)",
|
|
1637
|
+
"--mardora-selection-toolbar-swatch-hover-border": "#71717a",
|
|
1638
|
+
"--mardora-selection-toolbar-swatch-hover-ring": "rgba(244, 244, 245, 0.12)"
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
// src/editor/selection-toolbar/i18n.ts
|
|
1643
|
+
var selectionToolbarMessages = {
|
|
1644
|
+
"zh-CN": {
|
|
1645
|
+
buttons: {
|
|
1646
|
+
blockType: "\u5757\u7C7B\u578B",
|
|
1647
|
+
bold: "\u52A0\u7C97",
|
|
1648
|
+
italic: "\u659C\u4F53",
|
|
1649
|
+
strike: "\u5220\u9664\u7EBF",
|
|
1650
|
+
underline: "\u4E0B\u5212\u7EBF",
|
|
1651
|
+
code: "\u884C\u5185\u4EE3\u7801",
|
|
1652
|
+
highlight: "\u9AD8\u4EAE",
|
|
1653
|
+
color: "\u6587\u5B57\u989C\u8272",
|
|
1654
|
+
link: "\u94FE\u63A5",
|
|
1655
|
+
orderedList: "\u6709\u5E8F\u5217\u8868",
|
|
1656
|
+
unorderedList: "\u65E0\u5E8F\u5217\u8868",
|
|
1657
|
+
taskList: "\u4EFB\u52A1\u5217\u8868"
|
|
1658
|
+
},
|
|
1659
|
+
panels: {
|
|
1660
|
+
textColor: "\u6587\u5B57\u989C\u8272",
|
|
1661
|
+
highlightColor: "\u9AD8\u4EAE\u989C\u8272"
|
|
1662
|
+
},
|
|
1663
|
+
link: {
|
|
1664
|
+
title: "\u94FE\u63A5\u6807\u9898",
|
|
1665
|
+
url: "\u94FE\u63A5 URL",
|
|
1666
|
+
copy: "\u590D\u5236\u94FE\u63A5",
|
|
1667
|
+
copied: "\u5DF2\u590D\u5236",
|
|
1668
|
+
open: "\u6253\u5F00\u94FE\u63A5",
|
|
1669
|
+
remove: "\u79FB\u9664\u94FE\u63A5",
|
|
1670
|
+
invalid: "\u8BF7\u8F93\u5165\u6709\u6548\u94FE\u63A5"
|
|
1671
|
+
},
|
|
1672
|
+
blockTypes: {
|
|
1673
|
+
text: "\u6587\u672C",
|
|
1674
|
+
"heading-1": "\u6807\u9898 1",
|
|
1675
|
+
"heading-2": "\u6807\u9898 2",
|
|
1676
|
+
"heading-3": "\u6807\u9898 3",
|
|
1677
|
+
"heading-4": "\u6807\u9898 4",
|
|
1678
|
+
"heading-5": "\u6807\u9898 5",
|
|
1679
|
+
"heading-6": "\u6807\u9898 6"
|
|
1680
|
+
},
|
|
1681
|
+
colors: {
|
|
1682
|
+
defaultText: "\u9ED8\u8BA4\u6587\u5B57\u989C\u8272",
|
|
1683
|
+
gray: "\u7070\u8272",
|
|
1684
|
+
red: "\u7EA2\u8272",
|
|
1685
|
+
orange: "\u6A59\u8272",
|
|
1686
|
+
yellow: "\u9EC4\u8272",
|
|
1687
|
+
green: "\u7EFF\u8272",
|
|
1688
|
+
blue: "\u84DD\u8272",
|
|
1689
|
+
purple: "\u7D2B\u8272",
|
|
1690
|
+
defaultHighlight: "\u9ED8\u8BA4\u9AD8\u4EAE",
|
|
1691
|
+
yellowHighlight: "\u9EC4\u8272\u9AD8\u4EAE",
|
|
1692
|
+
greenHighlight: "\u7EFF\u8272\u9AD8\u4EAE",
|
|
1693
|
+
blueHighlight: "\u84DD\u8272\u9AD8\u4EAE",
|
|
1694
|
+
pinkHighlight: "\u7C89\u8272\u9AD8\u4EAE",
|
|
1695
|
+
purpleHighlight: "\u7D2B\u8272\u9AD8\u4EAE"
|
|
1696
|
+
}
|
|
1697
|
+
},
|
|
1698
|
+
"en-US": {
|
|
1699
|
+
buttons: {
|
|
1700
|
+
blockType: "Block type",
|
|
1701
|
+
bold: "Bold",
|
|
1702
|
+
italic: "Italic",
|
|
1703
|
+
strike: "Strikethrough",
|
|
1704
|
+
underline: "Underline",
|
|
1705
|
+
code: "Inline code",
|
|
1706
|
+
highlight: "Highlight",
|
|
1707
|
+
color: "Text color",
|
|
1708
|
+
link: "Link",
|
|
1709
|
+
orderedList: "Numbered list",
|
|
1710
|
+
unorderedList: "Bulleted list",
|
|
1711
|
+
taskList: "To-do list"
|
|
1712
|
+
},
|
|
1713
|
+
panels: {
|
|
1714
|
+
textColor: "Text color",
|
|
1715
|
+
highlightColor: "Highlight color"
|
|
1716
|
+
},
|
|
1717
|
+
link: {
|
|
1718
|
+
title: "Link title",
|
|
1719
|
+
url: "Link URL",
|
|
1720
|
+
copy: "Copy link",
|
|
1721
|
+
copied: "Copied",
|
|
1722
|
+
open: "Open link",
|
|
1723
|
+
remove: "Remove link",
|
|
1724
|
+
invalid: "Enter a valid link"
|
|
1725
|
+
},
|
|
1726
|
+
blockTypes: {
|
|
1727
|
+
text: "Text",
|
|
1728
|
+
"heading-1": "Heading 1",
|
|
1729
|
+
"heading-2": "Heading 2",
|
|
1730
|
+
"heading-3": "Heading 3",
|
|
1731
|
+
"heading-4": "Heading 4",
|
|
1732
|
+
"heading-5": "Heading 5",
|
|
1733
|
+
"heading-6": "Heading 6"
|
|
1734
|
+
},
|
|
1735
|
+
colors: {
|
|
1736
|
+
defaultText: "Default text color",
|
|
1737
|
+
gray: "Gray",
|
|
1738
|
+
red: "Red",
|
|
1739
|
+
orange: "Orange",
|
|
1740
|
+
yellow: "Yellow",
|
|
1741
|
+
green: "Green",
|
|
1742
|
+
blue: "Blue",
|
|
1743
|
+
purple: "Purple",
|
|
1744
|
+
defaultHighlight: "Default highlight",
|
|
1745
|
+
yellowHighlight: "Yellow highlight",
|
|
1746
|
+
greenHighlight: "Green highlight",
|
|
1747
|
+
blueHighlight: "Blue highlight",
|
|
1748
|
+
pinkHighlight: "Pink highlight",
|
|
1749
|
+
purpleHighlight: "Purple highlight"
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
function getSelectionToolbarMessages(locale) {
|
|
1754
|
+
return selectionToolbarMessages[locale];
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// src/editor/selection-toolbar/activation.ts
|
|
1758
|
+
var excludedClassPrefixes = [
|
|
1759
|
+
"cm-mardora-code-block",
|
|
1760
|
+
"cm-mardora-code-caption",
|
|
1761
|
+
"cm-mardora-code-copy",
|
|
1762
|
+
"cm-mardora-code-diff",
|
|
1763
|
+
"cm-mardora-code-fence",
|
|
1764
|
+
"cm-mardora-code-header",
|
|
1765
|
+
"cm-mardora-code-line",
|
|
1766
|
+
"cm-mardora-image-",
|
|
1767
|
+
"cm-mardora-math-",
|
|
1768
|
+
"cm-mardora-mermaid-"
|
|
1769
|
+
];
|
|
1770
|
+
var excludedSyntaxNodeNames = /* @__PURE__ */ new Set(["FencedCode", "MermaidBlock", "MathBlock", "InlineMath", "Image"]);
|
|
1771
|
+
function canActivateFromNativeSelection(input) {
|
|
1772
|
+
return !input.nativeSelectionCollapsed && input.anchorInEditor && input.focusInEditor && input.rangeCount > 0 && !input.anchorExcluded && !input.focusExcluded;
|
|
1773
|
+
}
|
|
1774
|
+
function hasSelectionToolbarExcludedAncestor(target, root) {
|
|
1775
|
+
let current = toElementLike(target);
|
|
1776
|
+
while (current) {
|
|
1777
|
+
if (hasExcludedClass(current)) {
|
|
1778
|
+
return true;
|
|
1779
|
+
}
|
|
1780
|
+
if (current === root) {
|
|
1781
|
+
return false;
|
|
1782
|
+
}
|
|
1783
|
+
current = toElementLike(current.parentElement);
|
|
1784
|
+
}
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
function selectionOverlapsExcludedSyntaxNode(input) {
|
|
1788
|
+
return excludedSyntaxNodeNames.has(input.nodeName) && input.selectionFrom < input.nodeTo && input.selectionTo > input.nodeFrom;
|
|
1789
|
+
}
|
|
1790
|
+
function toElementLike(value) {
|
|
1791
|
+
if (!value || typeof value !== "object") {
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
const element = value;
|
|
1795
|
+
if (hasClassData(element)) {
|
|
1796
|
+
return element;
|
|
1797
|
+
}
|
|
1798
|
+
return toElementLike(element.parentElement);
|
|
1799
|
+
}
|
|
1800
|
+
function hasClassData(value) {
|
|
1801
|
+
return value.className !== void 0 || value.classList !== void 0 || value.parentElement !== void 0;
|
|
1802
|
+
}
|
|
1803
|
+
function hasExcludedClass(element) {
|
|
1804
|
+
for (const className of classNamesFrom(element)) {
|
|
1805
|
+
if (excludedClassPrefixes.some((prefix) => className.startsWith(prefix))) {
|
|
1806
|
+
return true;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return false;
|
|
1810
|
+
}
|
|
1811
|
+
function classNamesFrom(element) {
|
|
1812
|
+
const classList = element.classList;
|
|
1813
|
+
if (isClassListLike(classList)) {
|
|
1814
|
+
const classNames = [];
|
|
1815
|
+
for (let index = 0; index < classList.length; index += 1) {
|
|
1816
|
+
const item = classList.item(index);
|
|
1817
|
+
if (item) classNames.push(item);
|
|
1818
|
+
}
|
|
1819
|
+
return classNames;
|
|
1820
|
+
}
|
|
1821
|
+
return typeof element.className === "string" ? element.className.split(/\s+/).filter(Boolean) : [];
|
|
1822
|
+
}
|
|
1823
|
+
function isClassListLike(value) {
|
|
1824
|
+
return !!value && typeof value === "object" && typeof value.length === "number" && typeof value.item === "function";
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// src/editor/selection-toolbar/extension.ts
|
|
1828
|
+
var toolbarWidth = 448;
|
|
1829
|
+
var toolbarHeight = 40;
|
|
1830
|
+
var panelWidth = 336;
|
|
1831
|
+
var blockTypePanelHeight = 300;
|
|
1832
|
+
var linkPanelHeight = 138;
|
|
1833
|
+
var palettePanelHeight = 72;
|
|
1834
|
+
var popoverGap = 6;
|
|
1835
|
+
function textColors(messages) {
|
|
1836
|
+
return [
|
|
1837
|
+
{ id: "default", label: messages.colors.defaultText, value: null },
|
|
1838
|
+
{ id: "gray", label: messages.colors.gray, value: "#71717a" },
|
|
1839
|
+
{ id: "red", label: messages.colors.red, value: "#dc2626" },
|
|
1840
|
+
{ id: "orange", label: messages.colors.orange, value: "#ea580c" },
|
|
1841
|
+
{ id: "yellow", label: messages.colors.yellow, value: "#ca8a04" },
|
|
1842
|
+
{ id: "green", label: messages.colors.green, value: "#16a34a" },
|
|
1843
|
+
{ id: "blue", label: messages.colors.blue, value: "#2563eb" },
|
|
1844
|
+
{ id: "purple", label: messages.colors.purple, value: "#7c3aed" }
|
|
1845
|
+
];
|
|
1846
|
+
}
|
|
1847
|
+
function highlightColors(messages) {
|
|
1848
|
+
return [
|
|
1849
|
+
{ id: "default", label: messages.colors.defaultHighlight, value: null },
|
|
1850
|
+
{ id: "yellow", label: messages.colors.yellowHighlight, value: "#fef08a" },
|
|
1851
|
+
{ id: "green", label: messages.colors.greenHighlight, value: "#bbf7d0" },
|
|
1852
|
+
{ id: "blue", label: messages.colors.blueHighlight, value: "#bfdbfe" },
|
|
1853
|
+
{ id: "pink", label: messages.colors.pinkHighlight, value: "#fbcfe8" },
|
|
1854
|
+
{ id: "purple", label: messages.colors.purpleHighlight, value: "#ddd6fe" }
|
|
1855
|
+
];
|
|
1856
|
+
}
|
|
1857
|
+
function blockTypeOptions(messages) {
|
|
1858
|
+
return [
|
|
1859
|
+
{ type: "text", label: messages.blockTypes.text, icon: "text-align-start" },
|
|
1860
|
+
{ type: "heading-1", label: messages.blockTypes["heading-1"], icon: "heading-1" },
|
|
1861
|
+
{ type: "heading-2", label: messages.blockTypes["heading-2"], icon: "heading-2" },
|
|
1862
|
+
{ type: "heading-3", label: messages.blockTypes["heading-3"], icon: "heading-3" },
|
|
1863
|
+
{ type: "heading-4", label: messages.blockTypes["heading-4"], icon: "heading-4" },
|
|
1864
|
+
{ type: "heading-5", label: messages.blockTypes["heading-5"], icon: "heading-5" },
|
|
1865
|
+
{ type: "heading-6", label: messages.blockTypes["heading-6"], icon: "heading-6" }
|
|
1866
|
+
];
|
|
1867
|
+
}
|
|
1868
|
+
function blockButton(blockType, messages) {
|
|
1869
|
+
return blockType === "text" ? { id: "block-type", label: messages.buttons.blockType, icon: "text-align-start" } : {
|
|
1870
|
+
id: "block-type",
|
|
1871
|
+
label: messages.buttons.blockType,
|
|
1872
|
+
icon: blockType,
|
|
1873
|
+
text: `H${blockType.slice("heading-".length)}`
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
function defaultButtons(messages, blockType) {
|
|
1877
|
+
return [
|
|
1878
|
+
blockButton(blockType, messages),
|
|
1879
|
+
{ id: "bold", label: messages.buttons.bold, icon: "bold" },
|
|
1880
|
+
{ id: "italic", label: messages.buttons.italic, icon: "italic" },
|
|
1881
|
+
{ id: "strike", label: messages.buttons.strike, icon: "strikethrough" },
|
|
1882
|
+
{ id: "underline", label: messages.buttons.underline, icon: "underline" },
|
|
1883
|
+
{ id: "code", label: messages.buttons.code, icon: "code" },
|
|
1884
|
+
{ id: "highlight", label: messages.buttons.highlight, icon: "highlighter" },
|
|
1885
|
+
{ id: "color", label: messages.buttons.color, icon: "baseline" },
|
|
1886
|
+
{ id: "link", label: messages.buttons.link, icon: "link" },
|
|
1887
|
+
{ id: "ordered-list", label: messages.buttons.orderedList, icon: "list-ordered" },
|
|
1888
|
+
{ id: "unordered-list", label: messages.buttons.unorderedList, icon: "list" },
|
|
1889
|
+
{ id: "task-list", label: messages.buttons.taskList, icon: "list-todo" }
|
|
1890
|
+
];
|
|
1891
|
+
}
|
|
1892
|
+
function isValidUrl(value) {
|
|
1893
|
+
return /^(https?:\/\/|www\.)[^\s]+$/i.test(value);
|
|
1894
|
+
}
|
|
1895
|
+
function normalizedUrl(value) {
|
|
1896
|
+
return value.startsWith("http") ? value : `https://${value}`;
|
|
1897
|
+
}
|
|
1898
|
+
function floatingSizeForPanel(panel) {
|
|
1899
|
+
if (panel === "toolbar" || panel === "block-type") return { width: toolbarWidth, height: toolbarHeight };
|
|
1900
|
+
if (panel === "link") return { width: panelWidth, height: linkPanelHeight };
|
|
1901
|
+
return { width: panelWidth, height: palettePanelHeight };
|
|
1902
|
+
}
|
|
1903
|
+
function boundaryFromRect(rect) {
|
|
1904
|
+
return {
|
|
1905
|
+
left: rect.left,
|
|
1906
|
+
right: rect.right,
|
|
1907
|
+
top: rect.top,
|
|
1908
|
+
bottom: rect.bottom
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
var SelectionToolbarViewPlugin = class {
|
|
1912
|
+
constructor(view, config) {
|
|
1913
|
+
this.view = view;
|
|
1914
|
+
this.config = config;
|
|
1915
|
+
const locale = resolveMardoraLocale(config.locale ?? config.inheritedLocale);
|
|
1916
|
+
this.messages = getSelectionToolbarMessages(locale);
|
|
1917
|
+
this.view.dom.ownerDocument.addEventListener("mousedown", this.handleDocumentMouseDown, true);
|
|
1918
|
+
this.view.dom.ownerDocument.addEventListener("selectionchange", this.handleDocumentSelectionChange);
|
|
1919
|
+
this.updateState();
|
|
1920
|
+
}
|
|
1921
|
+
view;
|
|
1922
|
+
config;
|
|
1923
|
+
menu = null;
|
|
1924
|
+
panel = "toolbar";
|
|
1925
|
+
savedRange = null;
|
|
1926
|
+
selectionAnchor = null;
|
|
1927
|
+
link = { title: "", url: "", canRemove: false };
|
|
1928
|
+
renderVersion = 0;
|
|
1929
|
+
messages;
|
|
1930
|
+
update(update) {
|
|
1931
|
+
if (update.docChanged || update.selectionSet || update.viewportChanged || update.focusChanged) {
|
|
1932
|
+
this.updateState();
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
destroy() {
|
|
1936
|
+
this.view.dom.ownerDocument.removeEventListener("mousedown", this.handleDocumentMouseDown, true);
|
|
1937
|
+
this.view.dom.ownerDocument.removeEventListener("selectionchange", this.handleDocumentSelectionChange);
|
|
1938
|
+
this.removeMenu();
|
|
1939
|
+
}
|
|
1940
|
+
closeFromKeyboard() {
|
|
1941
|
+
if (!this.menu) return false;
|
|
1942
|
+
if (this.panel !== "toolbar") {
|
|
1943
|
+
this.panel = "toolbar";
|
|
1944
|
+
this.renderMenu();
|
|
1945
|
+
return true;
|
|
1946
|
+
}
|
|
1947
|
+
this.close();
|
|
1948
|
+
return true;
|
|
1949
|
+
}
|
|
1950
|
+
handleDocumentMouseDown = (event) => {
|
|
1951
|
+
if (!this.menu) return;
|
|
1952
|
+
const target = event.target;
|
|
1953
|
+
if (target instanceof Node && (this.menu.contains(target) || this.view.dom.contains(target))) return;
|
|
1954
|
+
this.close();
|
|
1955
|
+
};
|
|
1956
|
+
handleDocumentSelectionChange = () => {
|
|
1957
|
+
const doc = this.view.dom.ownerDocument;
|
|
1958
|
+
const activeElement = doc.activeElement;
|
|
1959
|
+
if (this.menu && activeElement instanceof Node && this.menu.contains(activeElement)) return;
|
|
1960
|
+
if (!this.view.hasFocus || this.view.dom.classList.contains("cm-mardora-slash-open")) return;
|
|
1961
|
+
const selection = doc.getSelection();
|
|
1962
|
+
if (!selection || !selection.anchorNode || !selection.focusNode) return;
|
|
1963
|
+
if (!canActivateFromNativeSelection({
|
|
1964
|
+
editorSelectionEmpty: this.view.state.selection.main.empty,
|
|
1965
|
+
nativeSelectionCollapsed: selection.isCollapsed,
|
|
1966
|
+
anchorInEditor: this.view.contentDOM.contains(selection.anchorNode),
|
|
1967
|
+
focusInEditor: this.view.contentDOM.contains(selection.focusNode),
|
|
1968
|
+
rangeCount: selection.rangeCount,
|
|
1969
|
+
anchorExcluded: hasSelectionToolbarExcludedAncestor(selection.anchorNode, this.view.contentDOM),
|
|
1970
|
+
focusExcluded: hasSelectionToolbarExcludedAncestor(selection.focusNode, this.view.contentDOM)
|
|
1971
|
+
})) {
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
let anchor;
|
|
1975
|
+
let head;
|
|
1976
|
+
try {
|
|
1977
|
+
anchor = this.view.posAtDOM(selection.anchorNode, selection.anchorOffset);
|
|
1978
|
+
head = this.view.posAtDOM(selection.focusNode, selection.focusOffset);
|
|
1979
|
+
} catch {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const from = Math.min(anchor, head);
|
|
1983
|
+
const to = Math.max(anchor, head);
|
|
1984
|
+
if (from === to) return;
|
|
1985
|
+
if (this.selectionTouchesExcludedSyntax(from, to)) return;
|
|
1986
|
+
const rect = selection.getRangeAt(0).getBoundingClientRect();
|
|
1987
|
+
this.savedRange = {
|
|
1988
|
+
from,
|
|
1989
|
+
to,
|
|
1990
|
+
text: this.view.state.sliceDoc(from, to)
|
|
1991
|
+
};
|
|
1992
|
+
this.selectionAnchor = {
|
|
1993
|
+
left: rect.left,
|
|
1994
|
+
right: rect.right,
|
|
1995
|
+
top: rect.top,
|
|
1996
|
+
bottom: rect.bottom
|
|
1997
|
+
};
|
|
1998
|
+
this.renderMenu();
|
|
1999
|
+
};
|
|
2000
|
+
updateState() {
|
|
2001
|
+
const selection = this.view.state.selection.main;
|
|
2002
|
+
if (this.view.dom.classList.contains("cm-mardora-slash-open")) {
|
|
2003
|
+
this.close();
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
if (!this.view.hasFocus) {
|
|
2007
|
+
if (this.isMenuActive() && this.savedRange) return;
|
|
2008
|
+
this.close();
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
if (selection.empty) {
|
|
2012
|
+
this.close();
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (this.selectionTouchesExcludedSyntax(selection.from, selection.to)) {
|
|
2016
|
+
this.close();
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
this.savedRange = {
|
|
2020
|
+
from: selection.from,
|
|
2021
|
+
to: selection.to,
|
|
2022
|
+
text: this.view.state.sliceDoc(selection.from, selection.to)
|
|
2023
|
+
};
|
|
2024
|
+
this.selectionAnchor = null;
|
|
2025
|
+
this.renderMenu();
|
|
2026
|
+
}
|
|
2027
|
+
isMenuActive() {
|
|
2028
|
+
const activeElement = this.view.dom.ownerDocument.activeElement;
|
|
2029
|
+
return !!this.menu && activeElement instanceof Node && this.menu.contains(activeElement);
|
|
2030
|
+
}
|
|
2031
|
+
selectionTouchesExcludedSyntax(from, to) {
|
|
2032
|
+
let excluded = false;
|
|
2033
|
+
language.syntaxTree(this.view.state).iterate({
|
|
2034
|
+
from,
|
|
2035
|
+
to,
|
|
2036
|
+
enter: (node) => {
|
|
2037
|
+
if (selectionOverlapsExcludedSyntaxNode({
|
|
2038
|
+
selectionFrom: from,
|
|
2039
|
+
selectionTo: to,
|
|
2040
|
+
nodeFrom: node.from,
|
|
2041
|
+
nodeTo: node.to,
|
|
2042
|
+
nodeName: node.name
|
|
2043
|
+
})) {
|
|
2044
|
+
excluded = true;
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
return void 0;
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
return excluded;
|
|
2051
|
+
}
|
|
2052
|
+
close() {
|
|
2053
|
+
this.panel = "toolbar";
|
|
2054
|
+
this.savedRange = null;
|
|
2055
|
+
this.selectionAnchor = null;
|
|
2056
|
+
this.removeMenu();
|
|
2057
|
+
}
|
|
2058
|
+
removeMenu() {
|
|
2059
|
+
this.renderVersion += 1;
|
|
2060
|
+
this.detachMenu();
|
|
2061
|
+
}
|
|
2062
|
+
detachMenu() {
|
|
2063
|
+
this.menu?.remove();
|
|
2064
|
+
this.menu = null;
|
|
2065
|
+
}
|
|
2066
|
+
menuState() {
|
|
2067
|
+
const range = this.savedRange;
|
|
2068
|
+
const blockType = range ? detectSelectionBlockType({ doc: this.view.state.doc.toString(), from: range.from, to: range.to }) : "text";
|
|
2069
|
+
return {
|
|
2070
|
+
panel: this.panel,
|
|
2071
|
+
buttons: defaultButtons(this.messages, blockType),
|
|
2072
|
+
blockType,
|
|
2073
|
+
blockTypes: blockTypeOptions(this.messages),
|
|
2074
|
+
textColors: textColors(this.messages),
|
|
2075
|
+
highlightColors: highlightColors(this.messages),
|
|
2076
|
+
link: this.link,
|
|
2077
|
+
messages: this.messages
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
renderMenu() {
|
|
2081
|
+
const range = this.savedRange;
|
|
2082
|
+
if (!range) return;
|
|
2083
|
+
const renderVersion = ++this.renderVersion;
|
|
2084
|
+
const floating = floatingSizeForPanel(this.panel);
|
|
2085
|
+
this.detachMenu();
|
|
2086
|
+
const anchorFromSelection = this.selectionAnchor;
|
|
2087
|
+
this.view.requestMeasure({
|
|
2088
|
+
read: (view) => {
|
|
2089
|
+
const boundary = boundaryFromRect(view.dom.getBoundingClientRect());
|
|
2090
|
+
if (anchorFromSelection) return { anchor: anchorFromSelection, boundary };
|
|
2091
|
+
const from = view.coordsAtPos(range.from);
|
|
2092
|
+
const to = view.coordsAtPos(range.to);
|
|
2093
|
+
if (!from || !to) return null;
|
|
2094
|
+
const anchor = {
|
|
2095
|
+
left: Math.min(from.left, to.left),
|
|
2096
|
+
right: Math.max(from.right, to.right),
|
|
2097
|
+
top: Math.min(from.top, to.top),
|
|
2098
|
+
bottom: Math.max(from.bottom, to.bottom)
|
|
2099
|
+
};
|
|
2100
|
+
return { anchor, boundary };
|
|
2101
|
+
},
|
|
2102
|
+
write: (measure) => {
|
|
2103
|
+
if (renderVersion !== this.renderVersion || !measure) return;
|
|
2104
|
+
const win = this.view.dom.ownerDocument.defaultView ?? window;
|
|
2105
|
+
const layout = computeSelectionToolbarLayout({
|
|
2106
|
+
anchor: measure.anchor,
|
|
2107
|
+
viewport: { width: win.innerWidth, height: win.innerHeight },
|
|
2108
|
+
boundary: measure.boundary,
|
|
2109
|
+
floating
|
|
2110
|
+
});
|
|
2111
|
+
this.menu = createSelectionToolbarElement(this.menuState(), {
|
|
2112
|
+
onAction: (id) => this.handleAction(id),
|
|
2113
|
+
onBlockType: (type) => this.applyBlockType(type),
|
|
2114
|
+
onColor: (value) => this.applyColor(value),
|
|
2115
|
+
onHighlight: (value) => this.applyHighlight(value),
|
|
2116
|
+
onLinkInput: (field, value) => {
|
|
2117
|
+
const next = { ...this.link, [field]: value };
|
|
2118
|
+
delete next.error;
|
|
2119
|
+
this.link = next;
|
|
2120
|
+
},
|
|
2121
|
+
onLinkSubmit: () => this.submitLink(),
|
|
2122
|
+
onLinkCopy: () => void this.copyLink(),
|
|
2123
|
+
onLinkOpen: () => this.openLink(),
|
|
2124
|
+
onLinkRemove: () => this.removeLink(),
|
|
2125
|
+
onCancelPanel: () => {
|
|
2126
|
+
this.panel = "toolbar";
|
|
2127
|
+
this.renderMenu();
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
this.menu.style.left = `${layout.left}px`;
|
|
2131
|
+
this.menu.style.top = `${layout.top}px`;
|
|
2132
|
+
this.menu.style.maxHeight = `${layout.maxHeight}px`;
|
|
2133
|
+
this.menu.dataset.mardoraSelectionPlacement = layout.placement;
|
|
2134
|
+
if (this.panel === "block-type") {
|
|
2135
|
+
const opensDown = layout.placement === "bottom";
|
|
2136
|
+
const available = opensDown ? measure.boundary.bottom - (layout.top + toolbarHeight) - popoverGap : layout.top - measure.boundary.top - popoverGap;
|
|
2137
|
+
this.menu.style.setProperty(
|
|
2138
|
+
"--mardora-selection-toolbar-popover-max-height",
|
|
2139
|
+
`${Math.max(96, Math.min(blockTypePanelHeight, Math.floor(available)))}px`
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
this.view.dom.appendChild(this.menu);
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
dispatchResult(result) {
|
|
2147
|
+
if (!this.isSavedRangeCurrent()) {
|
|
2148
|
+
this.close();
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
this.view.dispatch({
|
|
2152
|
+
changes: result.changes,
|
|
2153
|
+
selection: result.selection,
|
|
2154
|
+
scrollIntoView: true
|
|
2155
|
+
});
|
|
2156
|
+
this.view.focus();
|
|
2157
|
+
this.close();
|
|
2158
|
+
}
|
|
2159
|
+
isSavedRangeCurrent() {
|
|
2160
|
+
if (!this.savedRange) return false;
|
|
2161
|
+
return this.view.state.sliceDoc(this.savedRange.from, this.savedRange.to) === this.savedRange.text;
|
|
2162
|
+
}
|
|
2163
|
+
handleAction(id) {
|
|
2164
|
+
const range = this.savedRange;
|
|
2165
|
+
if (!range) return;
|
|
2166
|
+
const doc = this.view.state.doc.toString();
|
|
2167
|
+
if (id === "link") {
|
|
2168
|
+
const parsed = parseSelectedLink(range.text);
|
|
2169
|
+
this.link = { title: parsed.title || range.text, url: parsed.url, canRemove: parsed.kind === "markdown-link" };
|
|
2170
|
+
this.panel = "link";
|
|
2171
|
+
this.renderMenu();
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
if (id === "block-type") {
|
|
2175
|
+
this.panel = "block-type";
|
|
2176
|
+
this.renderMenu();
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
if (id === "color") {
|
|
2180
|
+
this.panel = "color";
|
|
2181
|
+
this.renderMenu();
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
if (id === "highlight") {
|
|
2185
|
+
this.panel = "highlight";
|
|
2186
|
+
this.renderMenu();
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
if (id === "ordered-list" || id === "unordered-list" || id === "task-list") {
|
|
2190
|
+
const kind = id === "ordered-list" ? "ordered" : id === "task-list" ? "task" : "unordered";
|
|
2191
|
+
this.dispatchResult(buildListChange({ doc, from: range.from, to: range.to, kind }));
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
if (id === "underline") {
|
|
2195
|
+
this.dispatchResult(buildInlineFormatChange({ doc, from: range.from, to: range.to, htmlTag: "u" }));
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
const marker = id === "bold" ? "**" : id === "italic" ? "*" : id === "strike" ? "~~" : id === "code" ? "`" : null;
|
|
2199
|
+
if (!marker) return;
|
|
2200
|
+
const result = buildInlineFormatChange({ doc, from: range.from, to: range.to, marker });
|
|
2201
|
+
this.dispatchResult(result);
|
|
2202
|
+
}
|
|
2203
|
+
applyBlockType(type) {
|
|
2204
|
+
const range = this.savedRange;
|
|
2205
|
+
if (!range) return;
|
|
2206
|
+
const level = type === "text" ? 0 : Number(type.slice("heading-".length));
|
|
2207
|
+
if (level < 0 || level > 6) return;
|
|
2208
|
+
this.dispatchResult(
|
|
2209
|
+
buildBlockTypeChange({
|
|
2210
|
+
doc: this.view.state.doc.toString(),
|
|
2211
|
+
from: range.from,
|
|
2212
|
+
to: range.to,
|
|
2213
|
+
level
|
|
2214
|
+
})
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
applyColor(value) {
|
|
2218
|
+
const range = this.savedRange;
|
|
2219
|
+
if (!range) return;
|
|
2220
|
+
this.dispatchResult(
|
|
2221
|
+
buildInlineFormatChange({
|
|
2222
|
+
doc: this.view.state.doc.toString(),
|
|
2223
|
+
from: range.from,
|
|
2224
|
+
to: range.to,
|
|
2225
|
+
spanStyle: { property: "color", value: value ?? "" },
|
|
2226
|
+
clear: value === null
|
|
2227
|
+
})
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
applyHighlight(value) {
|
|
2231
|
+
const range = this.savedRange;
|
|
2232
|
+
if (!range) return;
|
|
2233
|
+
const result = value ? buildInlineFormatChange({
|
|
2234
|
+
doc: this.view.state.doc.toString(),
|
|
2235
|
+
from: range.from,
|
|
2236
|
+
to: range.to,
|
|
2237
|
+
spanStyle: { property: "background-color", value }
|
|
2238
|
+
}) : buildInlineFormatChange({ doc: this.view.state.doc.toString(), from: range.from, to: range.to, marker: "==" });
|
|
2239
|
+
this.dispatchResult(result);
|
|
2240
|
+
}
|
|
2241
|
+
submitLink() {
|
|
2242
|
+
const range = this.savedRange;
|
|
2243
|
+
if (!range) return;
|
|
2244
|
+
if (!this.link.url || !isValidUrl(this.link.url)) {
|
|
2245
|
+
this.link = { ...this.link, error: this.messages.link.invalid };
|
|
2246
|
+
this.renderMenu();
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
const title = this.link.title || range.text || this.link.url;
|
|
2250
|
+
this.dispatchResult(buildLinkChange({ from: range.from, to: range.to, title, url: this.link.url }));
|
|
2251
|
+
}
|
|
2252
|
+
removeLink() {
|
|
2253
|
+
const range = this.savedRange;
|
|
2254
|
+
if (!range) return;
|
|
2255
|
+
this.dispatchResult(
|
|
2256
|
+
buildLinkChange({ from: range.from, to: range.to, title: this.link.title || range.text, url: "", remove: true })
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
async copyLink() {
|
|
2260
|
+
if (!this.link.url) return;
|
|
2261
|
+
await this.view.dom.ownerDocument.defaultView?.navigator.clipboard?.writeText(this.link.url);
|
|
2262
|
+
this.link = { ...this.link, copied: true };
|
|
2263
|
+
this.renderMenu();
|
|
2264
|
+
}
|
|
2265
|
+
openLink() {
|
|
2266
|
+
if (!this.link.url || !isValidUrl(this.link.url)) return;
|
|
2267
|
+
this.view.dom.ownerDocument.defaultView?.open(normalizedUrl(this.link.url), "_blank", "noopener,noreferrer");
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
2270
|
+
function selectionToolbar(config = {}) {
|
|
2271
|
+
if (config.enabled === false) return [];
|
|
2272
|
+
const plugin = view.ViewPlugin.define((view) => new SelectionToolbarViewPlugin(view, config));
|
|
2273
|
+
return [
|
|
2274
|
+
selectionToolbarTheme,
|
|
2275
|
+
plugin,
|
|
2276
|
+
state.Prec.highest(
|
|
2277
|
+
view.EditorView.domEventHandlers({
|
|
2278
|
+
keydown(event, view) {
|
|
2279
|
+
const value = view.plugin(plugin);
|
|
2280
|
+
if (!value || event.key !== "Escape") return false;
|
|
2281
|
+
const handled = value.closeFromKeyboard();
|
|
2282
|
+
if (handled) event.preventDefault();
|
|
2283
|
+
return handled;
|
|
2284
|
+
}
|
|
2285
|
+
})
|
|
2286
|
+
)
|
|
2287
|
+
];
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
// src/editor/heading-fold/config.ts
|
|
2291
|
+
var minSupportedLevel = 2;
|
|
2292
|
+
var maxSupportedLevel = 5;
|
|
2293
|
+
function clampLevel(level) {
|
|
2294
|
+
return Math.min(Math.max(level, minSupportedLevel), maxSupportedLevel);
|
|
2295
|
+
}
|
|
2296
|
+
function resolveHeadingFoldConfig(config = {}) {
|
|
2297
|
+
const requestedMinLevel = clampLevel(config.minLevel ?? minSupportedLevel);
|
|
2298
|
+
const requestedMaxLevel = clampLevel(config.maxLevel ?? maxSupportedLevel);
|
|
2299
|
+
const minLevel = Math.min(requestedMinLevel, requestedMaxLevel);
|
|
2300
|
+
const maxLevel = Math.max(requestedMinLevel, requestedMaxLevel);
|
|
2301
|
+
return {
|
|
2302
|
+
enabled: config.enabled !== false,
|
|
2303
|
+
minLevel,
|
|
2304
|
+
maxLevel
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
var headingPattern = /^ATXHeading([1-6])$/;
|
|
2308
|
+
var headingFoldParseTimeout = 100;
|
|
2309
|
+
function headingLevel(node) {
|
|
2310
|
+
const match = headingPattern.exec(node.name);
|
|
2311
|
+
return match ? Number(match[1]) : null;
|
|
2312
|
+
}
|
|
2313
|
+
function canFoldLevel(level, minLevel, maxLevel) {
|
|
2314
|
+
return level >= minLevel && level <= maxLevel;
|
|
2315
|
+
}
|
|
2316
|
+
function collectHeadings(state) {
|
|
2317
|
+
const headings = [];
|
|
2318
|
+
const tree = language.ensureSyntaxTree(state, state.doc.length, headingFoldParseTimeout) ?? language.syntaxTree(state);
|
|
2319
|
+
tree.iterate({
|
|
2320
|
+
enter: (node) => {
|
|
2321
|
+
const level = headingLevel(node);
|
|
2322
|
+
if (!level) return;
|
|
2323
|
+
const text = chunkWFVCG4LD_cjs.stripMarkdownHeadingText(state.sliceDoc(node.from, node.to));
|
|
2324
|
+
if (!text) return;
|
|
2325
|
+
headings.push({ level, text, from: node.from, to: node.to });
|
|
2326
|
+
}
|
|
2327
|
+
});
|
|
2328
|
+
return headings;
|
|
2329
|
+
}
|
|
2330
|
+
function findFoldEnd(state, headings, index) {
|
|
2331
|
+
const heading = headings[index];
|
|
2332
|
+
const nextBoundary = headings.slice(index + 1).find((candidate) => candidate.level <= heading.level);
|
|
2333
|
+
if (!nextBoundary) return state.doc.length;
|
|
2334
|
+
return state.doc.lineAt(nextBoundary.from).from;
|
|
2335
|
+
}
|
|
2336
|
+
function findFoldStart(state, heading) {
|
|
2337
|
+
const headingLine = state.doc.lineAt(heading.from);
|
|
2338
|
+
if (headingLine.number >= state.doc.lines) return headingLine.to;
|
|
2339
|
+
return state.doc.line(headingLine.number + 1).from;
|
|
2340
|
+
}
|
|
2341
|
+
function extractHeadingFoldRangesFromState(state, config = {}) {
|
|
2342
|
+
const resolved = resolveHeadingFoldConfig(config);
|
|
2343
|
+
if (!resolved.enabled) return [];
|
|
2344
|
+
const headings = collectHeadings(state);
|
|
2345
|
+
const ranges = [];
|
|
2346
|
+
headings.forEach((heading, index) => {
|
|
2347
|
+
if (!canFoldLevel(heading.level, resolved.minLevel, resolved.maxLevel)) return;
|
|
2348
|
+
const line = state.doc.lineAt(heading.from);
|
|
2349
|
+
const foldFrom = findFoldStart(state, heading);
|
|
2350
|
+
const foldTo = findFoldEnd(state, headings, index);
|
|
2351
|
+
if (foldTo <= foldFrom) return;
|
|
2352
|
+
ranges.push({
|
|
2353
|
+
level: heading.level,
|
|
2354
|
+
text: heading.text,
|
|
2355
|
+
headingFrom: heading.from,
|
|
2356
|
+
headingTo: heading.to,
|
|
2357
|
+
headingLineFrom: line.from,
|
|
2358
|
+
headingLineTo: line.to,
|
|
2359
|
+
foldFrom,
|
|
2360
|
+
foldTo
|
|
2361
|
+
});
|
|
2362
|
+
});
|
|
2363
|
+
return ranges;
|
|
2364
|
+
}
|
|
2365
|
+
var headingFoldTheme = view.EditorView.baseTheme({
|
|
2366
|
+
".cm-mardora-heading-fold-line": {
|
|
2367
|
+
position: "relative"
|
|
2368
|
+
},
|
|
2369
|
+
".cm-mardora-heading-fold-toggle": {
|
|
2370
|
+
alignItems: "center",
|
|
2371
|
+
background: "transparent",
|
|
2372
|
+
border: "0",
|
|
2373
|
+
borderRadius: "4px",
|
|
2374
|
+
color: "var(--mardora-heading-fold-muted, #a1a1aa)",
|
|
2375
|
+
cursor: "pointer",
|
|
2376
|
+
display: "inline-flex",
|
|
2377
|
+
font: "600 0.7rem/1 var(--font-sans, sans-serif)",
|
|
2378
|
+
height: "1rem",
|
|
2379
|
+
justifyContent: "center",
|
|
2380
|
+
marginLeft: "-2.55rem",
|
|
2381
|
+
marginRight: "0.45rem",
|
|
2382
|
+
padding: "0",
|
|
2383
|
+
transform: "translateY(-0.52em)",
|
|
2384
|
+
verticalAlign: "middle",
|
|
2385
|
+
width: "2rem"
|
|
2386
|
+
},
|
|
2387
|
+
".cm-mardora-heading-fold-toggle:hover, .cm-mardora-heading-fold-toggle:focus-visible": {
|
|
2388
|
+
color: "var(--mardora-heading-fold-active, #52525b)"
|
|
2389
|
+
},
|
|
2390
|
+
".cm-mardora-heading-fold-toggle:focus-visible": {
|
|
2391
|
+
outline: "2px solid var(--mardora-heading-fold-focus, #a1a1aa)",
|
|
2392
|
+
outlineOffset: "2px"
|
|
2393
|
+
},
|
|
2394
|
+
".cm-mardora-heading-fold-level": {
|
|
2395
|
+
display: "inline-flex"
|
|
2396
|
+
},
|
|
2397
|
+
".cm-mardora-heading-fold-arrow": {
|
|
2398
|
+
display: "none",
|
|
2399
|
+
height: "0.55rem",
|
|
2400
|
+
position: "relative",
|
|
2401
|
+
width: "0.55rem"
|
|
2402
|
+
},
|
|
2403
|
+
".cm-mardora-heading-fold-arrow::before": {
|
|
2404
|
+
borderBottom: "1.5px solid currentColor",
|
|
2405
|
+
borderRight: "1.5px solid currentColor",
|
|
2406
|
+
content: '""',
|
|
2407
|
+
display: "block",
|
|
2408
|
+
height: "0.28rem",
|
|
2409
|
+
left: "0.08rem",
|
|
2410
|
+
position: "absolute",
|
|
2411
|
+
top: "0.03rem",
|
|
2412
|
+
transform: "rotate(45deg)",
|
|
2413
|
+
width: "0.28rem"
|
|
2414
|
+
},
|
|
2415
|
+
".cm-mardora-heading-fold-toggle[data-mardora-heading-fold-folded='true'] .cm-mardora-heading-fold-arrow::before": {
|
|
2416
|
+
left: "0.03rem",
|
|
2417
|
+
top: "0.11rem",
|
|
2418
|
+
transform: "rotate(-45deg)"
|
|
2419
|
+
},
|
|
2420
|
+
".cm-mardora-heading-fold-line:hover .cm-mardora-heading-fold-level, .cm-mardora-heading-fold-line-active .cm-mardora-heading-fold-level, .cm-mardora-heading-fold-toggle:hover .cm-mardora-heading-fold-level, .cm-mardora-heading-fold-toggle:focus-visible .cm-mardora-heading-fold-level, .cm-mardora-heading-fold-toggle[data-mardora-heading-fold-folded='true'] .cm-mardora-heading-fold-level": {
|
|
2421
|
+
display: "none"
|
|
2422
|
+
},
|
|
2423
|
+
".cm-mardora-heading-fold-line:hover .cm-mardora-heading-fold-arrow, .cm-mardora-heading-fold-line-active .cm-mardora-heading-fold-arrow, .cm-mardora-heading-fold-toggle:hover .cm-mardora-heading-fold-arrow, .cm-mardora-heading-fold-toggle:focus-visible .cm-mardora-heading-fold-arrow, .cm-mardora-heading-fold-toggle[data-mardora-heading-fold-folded='true'] .cm-mardora-heading-fold-arrow": {
|
|
2424
|
+
display: "inline-block"
|
|
2425
|
+
},
|
|
2426
|
+
".cm-mardora-heading-fold-placeholder": {
|
|
2427
|
+
color: "var(--mardora-heading-fold-muted, #a1a1aa)",
|
|
2428
|
+
font: "600 0.9rem/1.4 var(--font-sans, sans-serif)",
|
|
2429
|
+
padding: "0.1rem 0 0.35rem 0.25rem"
|
|
2430
|
+
},
|
|
2431
|
+
"&dark .cm-mardora-heading-fold-toggle, &dark .cm-mardora-heading-fold-placeholder": {
|
|
2432
|
+
"--mardora-heading-fold-muted": "#71717a",
|
|
2433
|
+
"--mardora-heading-fold-active": "#d4d4d8",
|
|
2434
|
+
"--mardora-heading-fold-focus": "#71717a"
|
|
2435
|
+
}
|
|
2436
|
+
});
|
|
2437
|
+
var toggleHeadingFoldEffect = state.StateEffect.define();
|
|
2438
|
+
var headingFoldConfigFacet = state.Facet.define({
|
|
2439
|
+
combine: (values) => values[values.length - 1] ?? {}
|
|
2440
|
+
});
|
|
2441
|
+
function nextFoldedHeadings(value, transaction) {
|
|
2442
|
+
const next = /* @__PURE__ */ new Set();
|
|
2443
|
+
if (transaction.docChanged) {
|
|
2444
|
+
for (const position of value) {
|
|
2445
|
+
next.add(transaction.changes.mapPos(position, 1));
|
|
2446
|
+
}
|
|
2447
|
+
} else {
|
|
2448
|
+
for (const position of value) next.add(position);
|
|
2449
|
+
}
|
|
2450
|
+
for (const effect of transaction.effects) {
|
|
2451
|
+
if (!effect.is(toggleHeadingFoldEffect)) continue;
|
|
2452
|
+
if (next.has(effect.value)) {
|
|
2453
|
+
next.delete(effect.value);
|
|
2454
|
+
} else {
|
|
2455
|
+
next.add(effect.value);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
return Array.from(next).sort((a, b) => a - b);
|
|
2459
|
+
}
|
|
2460
|
+
var HeadingFoldWidget = class extends view.WidgetType {
|
|
2461
|
+
constructor(range, folded) {
|
|
2462
|
+
super();
|
|
2463
|
+
this.range = range;
|
|
2464
|
+
this.folded = folded;
|
|
2465
|
+
}
|
|
2466
|
+
range;
|
|
2467
|
+
folded;
|
|
2468
|
+
eq(other) {
|
|
2469
|
+
return other.range.headingFrom === this.range.headingFrom && other.folded === this.folded;
|
|
2470
|
+
}
|
|
2471
|
+
toDOM() {
|
|
2472
|
+
const button = document.createElement("button");
|
|
2473
|
+
button.type = "button";
|
|
2474
|
+
button.className = "cm-mardora-heading-fold-toggle";
|
|
2475
|
+
button.setAttribute("aria-label", `${this.folded ? "Expand" : "Collapse"} H${this.range.level} section`);
|
|
2476
|
+
button.dataset.mardoraHeadingFoldFolded = String(this.folded);
|
|
2477
|
+
button.dataset.mardoraHeadingFoldFrom = String(this.range.headingFrom);
|
|
2478
|
+
const level = document.createElement("span");
|
|
2479
|
+
level.className = "cm-mardora-heading-fold-level";
|
|
2480
|
+
level.textContent = `H${this.range.level}`;
|
|
2481
|
+
const arrow = document.createElement("span");
|
|
2482
|
+
arrow.className = "cm-mardora-heading-fold-arrow";
|
|
2483
|
+
arrow.setAttribute("aria-hidden", "true");
|
|
2484
|
+
button.append(level, arrow);
|
|
2485
|
+
return button;
|
|
2486
|
+
}
|
|
2487
|
+
ignoreEvent(event) {
|
|
2488
|
+
return event.type === "mousedown" || event.type === "click";
|
|
2489
|
+
}
|
|
2490
|
+
};
|
|
2491
|
+
var FoldPlaceholderWidget = class _FoldPlaceholderWidget extends view.WidgetType {
|
|
2492
|
+
eq(other) {
|
|
2493
|
+
return other instanceof _FoldPlaceholderWidget;
|
|
2494
|
+
}
|
|
2495
|
+
toDOM() {
|
|
2496
|
+
const placeholder = document.createElement("span");
|
|
2497
|
+
placeholder.className = "cm-mardora-heading-fold-placeholder";
|
|
2498
|
+
placeholder.setAttribute("aria-hidden", "true");
|
|
2499
|
+
placeholder.textContent = "...";
|
|
2500
|
+
return placeholder;
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
function selectionTouchesHeading(state, range) {
|
|
2504
|
+
return state.selection.ranges.some((selection) => {
|
|
2505
|
+
const line = state.doc.lineAt(selection.head);
|
|
2506
|
+
return line.from === range.headingLineFrom;
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
function readHeadingFoldButtonPosition(target, view) {
|
|
2510
|
+
if (!(target instanceof Element)) return null;
|
|
2511
|
+
const button = target.closest(".cm-mardora-heading-fold-toggle");
|
|
2512
|
+
if (!(button instanceof HTMLElement) || !view.dom.contains(button)) return null;
|
|
2513
|
+
const position = Number(button.dataset.mardoraHeadingFoldFrom);
|
|
2514
|
+
return Number.isFinite(position) ? position : null;
|
|
2515
|
+
}
|
|
2516
|
+
function toggleHeadingFoldAt(view, position) {
|
|
2517
|
+
view.dispatch({ effects: toggleHeadingFoldEffect.of(position) });
|
|
2518
|
+
view.focus();
|
|
2519
|
+
}
|
|
2520
|
+
var headingFoldDomHandlers = view.EditorView.domEventHandlers({
|
|
2521
|
+
mousedown: (event, view) => {
|
|
2522
|
+
if (event.button !== 0) return false;
|
|
2523
|
+
const position = readHeadingFoldButtonPosition(event.target, view);
|
|
2524
|
+
if (position === null) return false;
|
|
2525
|
+
event.preventDefault();
|
|
2526
|
+
event.stopPropagation();
|
|
2527
|
+
toggleHeadingFoldAt(view, position);
|
|
2528
|
+
return true;
|
|
2529
|
+
},
|
|
2530
|
+
click: (event, view) => {
|
|
2531
|
+
if (event.detail !== 0) return false;
|
|
2532
|
+
const position = readHeadingFoldButtonPosition(event.target, view);
|
|
2533
|
+
if (position === null) return false;
|
|
2534
|
+
event.preventDefault();
|
|
2535
|
+
event.stopPropagation();
|
|
2536
|
+
toggleHeadingFoldAt(view, position);
|
|
2537
|
+
return true;
|
|
2538
|
+
}
|
|
2539
|
+
});
|
|
2540
|
+
function __buildHeadingFoldDecorations(state, config, foldedHeadings) {
|
|
2541
|
+
const folded = new Set(foldedHeadings);
|
|
2542
|
+
const decorations = [];
|
|
2543
|
+
for (const range of extractHeadingFoldRangesFromState(state, config)) {
|
|
2544
|
+
const isFolded = folded.has(range.headingFrom);
|
|
2545
|
+
const active = selectionTouchesHeading(state, range);
|
|
2546
|
+
const lineClass = [
|
|
2547
|
+
"cm-mardora-heading-fold-line",
|
|
2548
|
+
active ? "cm-mardora-heading-fold-line-active" : "",
|
|
2549
|
+
isFolded ? "cm-mardora-heading-fold-line-folded" : ""
|
|
2550
|
+
].filter(Boolean).join(" ");
|
|
2551
|
+
decorations.push(view.Decoration.line({ class: lineClass }).range(range.headingLineFrom));
|
|
2552
|
+
decorations.push(
|
|
2553
|
+
view.Decoration.widget({
|
|
2554
|
+
widget: new HeadingFoldWidget(range, isFolded),
|
|
2555
|
+
side: -1
|
|
2556
|
+
}).range(range.headingFrom)
|
|
2557
|
+
);
|
|
2558
|
+
if (isFolded) {
|
|
2559
|
+
decorations.push(
|
|
2560
|
+
view.Decoration.widget({
|
|
2561
|
+
widget: new FoldPlaceholderWidget(),
|
|
2562
|
+
side: 1,
|
|
2563
|
+
mardoraHeadingFoldRole: "placeholder"
|
|
2564
|
+
}).range(range.headingTo)
|
|
2565
|
+
);
|
|
2566
|
+
decorations.push(
|
|
2567
|
+
view.Decoration.replace({
|
|
2568
|
+
mardoraHeadingFoldRole: "hidden-content"
|
|
2569
|
+
}).range(range.foldFrom, range.foldTo)
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
return view.Decoration.set(decorations, true);
|
|
2574
|
+
}
|
|
2575
|
+
var headingFoldStateField = state.StateField.define({
|
|
2576
|
+
create: (state) => {
|
|
2577
|
+
const foldedHeadings = [];
|
|
2578
|
+
return {
|
|
2579
|
+
foldedHeadings,
|
|
2580
|
+
decorations: __buildHeadingFoldDecorations(state, state.facet(headingFoldConfigFacet), foldedHeadings)
|
|
2581
|
+
};
|
|
2582
|
+
},
|
|
2583
|
+
update: (value, transaction) => {
|
|
2584
|
+
const foldedHeadings = nextFoldedHeadings(value.foldedHeadings, transaction);
|
|
2585
|
+
return {
|
|
2586
|
+
foldedHeadings,
|
|
2587
|
+
decorations: __buildHeadingFoldDecorations(transaction.state, transaction.state.facet(headingFoldConfigFacet), foldedHeadings)
|
|
2588
|
+
};
|
|
2589
|
+
},
|
|
2590
|
+
provide: (field) => view.EditorView.decorations.from(field, (value) => value.decorations)
|
|
2591
|
+
});
|
|
2592
|
+
var HeadingFoldViewPlugin = class {
|
|
2593
|
+
constructor(view, _config) {
|
|
2594
|
+
this.view = view;
|
|
2595
|
+
this.view.dom.addEventListener("pointerdown", this.handlePointerDown, true);
|
|
2596
|
+
this.view.dom.addEventListener("mousedown", this.handleMouseDown, true);
|
|
2597
|
+
this.view.dom.addEventListener("click", this.handleClick, true);
|
|
2598
|
+
}
|
|
2599
|
+
view;
|
|
2600
|
+
suppressNextMouseDown = false;
|
|
2601
|
+
update(_update) {
|
|
2602
|
+
}
|
|
2603
|
+
destroy() {
|
|
2604
|
+
this.view.dom.removeEventListener("pointerdown", this.handlePointerDown, true);
|
|
2605
|
+
this.view.dom.removeEventListener("mousedown", this.handleMouseDown, true);
|
|
2606
|
+
this.view.dom.removeEventListener("click", this.handleClick, true);
|
|
2607
|
+
}
|
|
2608
|
+
handlePointerDown = (event) => {
|
|
2609
|
+
if (event.button !== 0) return;
|
|
2610
|
+
if (readHeadingFoldButtonPosition(event.target, this.view) !== null) this.suppressNextMouseDown = true;
|
|
2611
|
+
this.handleToggleEvent(event);
|
|
2612
|
+
};
|
|
2613
|
+
handleMouseDown = (event) => {
|
|
2614
|
+
if (event.button !== 0) return;
|
|
2615
|
+
if (this.suppressNextMouseDown) {
|
|
2616
|
+
this.suppressNextMouseDown = false;
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
this.handleToggleEvent(event);
|
|
2620
|
+
};
|
|
2621
|
+
handleClick = (event) => {
|
|
2622
|
+
if (event.detail !== 0) return;
|
|
2623
|
+
this.handleToggleEvent(event);
|
|
2624
|
+
};
|
|
2625
|
+
handleToggleEvent(event) {
|
|
2626
|
+
const position = readHeadingFoldButtonPosition(event.target, this.view);
|
|
2627
|
+
if (position === null) return;
|
|
2628
|
+
event.preventDefault();
|
|
2629
|
+
event.stopPropagation();
|
|
2630
|
+
toggleHeadingFoldAt(this.view, position);
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
function headingFold(config = {}) {
|
|
2634
|
+
if (config.enabled === false) return [];
|
|
2635
|
+
return [
|
|
2636
|
+
headingFoldConfigFacet.of(config),
|
|
2637
|
+
headingFoldStateField,
|
|
2638
|
+
headingFoldTheme,
|
|
2639
|
+
headingFoldDomHandlers,
|
|
2640
|
+
view.ViewPlugin.define((view) => new HeadingFoldViewPlugin(view, config))
|
|
2641
|
+
];
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// src/editor/mardora.ts
|
|
2645
|
+
function mardora(config = {}) {
|
|
2646
|
+
const {
|
|
2647
|
+
locale: configLocale,
|
|
2648
|
+
i18n: configI18n = {},
|
|
2649
|
+
theme: configTheme = "auto" /* AUTO */,
|
|
2650
|
+
baseStyles = true,
|
|
2651
|
+
plugins = [],
|
|
2652
|
+
extensions = [],
|
|
2653
|
+
keymap: configKeymap = [],
|
|
2654
|
+
disableViewPlugin = false,
|
|
2655
|
+
defaultKeybindings = true,
|
|
2656
|
+
history: configHistory = true,
|
|
2657
|
+
indentWithTab: configIndentWithTab = true,
|
|
2658
|
+
highlightActiveLine: configHighlightActiveLine = true,
|
|
2659
|
+
lineWrapping: configLineWrapping = true,
|
|
2660
|
+
onNodesChange: configOnNodesChange = void 0,
|
|
2661
|
+
slashCommands: configSlashCommands = { enabled: true },
|
|
2662
|
+
attachments: configAttachments = { enabled: false },
|
|
2663
|
+
selectionToolbar: configSelectionToolbar = { enabled: true },
|
|
2664
|
+
toc: configToc = { enabled: true },
|
|
2665
|
+
headingFold: configHeadingFold = { enabled: true }
|
|
2666
|
+
} = config;
|
|
2667
|
+
const resolvedLocale = resolveMardoraLocale(configSlashCommands.locale ?? configI18n.locale ?? configLocale);
|
|
2668
|
+
const allPlugins = [...plugins];
|
|
2669
|
+
const pluginExtensions = [];
|
|
2670
|
+
const pluginKeymaps = [];
|
|
2671
|
+
const markdownExtensions = [];
|
|
2672
|
+
const pluginContext = { config };
|
|
2673
|
+
if (!disableViewPlugin) {
|
|
2674
|
+
for (const plugin of allPlugins) {
|
|
2675
|
+
plugin.onRegister(pluginContext);
|
|
2676
|
+
const exts = plugin.getExtensions();
|
|
2677
|
+
if (exts.length > 0) {
|
|
2678
|
+
pluginExtensions.push(...exts);
|
|
2679
|
+
}
|
|
2680
|
+
const keys = plugin.getKeymap();
|
|
2681
|
+
if (keys.length > 0) {
|
|
2682
|
+
pluginKeymaps.push(...keys);
|
|
2683
|
+
}
|
|
2684
|
+
const theme = plugin.theme;
|
|
2685
|
+
if (baseStyles && theme && typeof theme === "function") {
|
|
2686
|
+
pluginExtensions.push(view.EditorView.theme(theme(configTheme)));
|
|
2687
|
+
}
|
|
2688
|
+
const md = plugin.getMarkdownConfig();
|
|
2689
|
+
if (md) {
|
|
2690
|
+
markdownExtensions.push(md);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
if (config.markdown) {
|
|
2695
|
+
markdownExtensions.push(...config.markdown);
|
|
2696
|
+
}
|
|
2697
|
+
const markdownSupport = langMarkdown.markdown({
|
|
2698
|
+
base: langMarkdown.markdownLanguage,
|
|
2699
|
+
codeLanguages: languageData.languages,
|
|
2700
|
+
extensions: markdownExtensions,
|
|
2701
|
+
addKeymap: true,
|
|
2702
|
+
completeHTMLTags: true,
|
|
2703
|
+
pasteURLAsLink: true
|
|
2704
|
+
});
|
|
2705
|
+
const baseExtensions = [
|
|
2706
|
+
...defaultKeybindings ? [view.keymap.of(commands.defaultKeymap)] : [],
|
|
2707
|
+
...configHistory ? [commands.history(), view.keymap.of(commands.historyKeymap)] : [],
|
|
2708
|
+
...configIndentWithTab ? [language.indentOnInput(), view.keymap.of([commands.indentWithTab])] : [],
|
|
2709
|
+
...configHighlightActiveLine && disableViewPlugin ? [view.highlightActiveLine()] : []
|
|
2710
|
+
];
|
|
2711
|
+
const mardoraExtensions = [];
|
|
2712
|
+
if (!disableViewPlugin) {
|
|
2713
|
+
mardoraExtensions.push(createMardoraViewExtension(configTheme, baseStyles, allPlugins, configOnNodesChange));
|
|
2714
|
+
mardoraExtensions.push(state.Prec.highest(markdownResetExtension));
|
|
2715
|
+
}
|
|
2716
|
+
if (!disableViewPlugin || configLineWrapping) mardoraExtensions.push(view.EditorView.lineWrapping);
|
|
2717
|
+
const composedExtensions = [
|
|
2718
|
+
// Core markdown support (highest priority)
|
|
2719
|
+
state.Prec.high(markdownSupport),
|
|
2720
|
+
state.Prec.high(view.keymap.of(langMarkdown.markdownKeymap)),
|
|
2721
|
+
// mardora view plugin for rich rendering
|
|
2722
|
+
mardoraExtensions,
|
|
2723
|
+
// Core CodeMirror extensions
|
|
2724
|
+
baseExtensions,
|
|
2725
|
+
// Mardora editor commands and browser attachments
|
|
2726
|
+
slashCommands({
|
|
2727
|
+
...configSlashCommands,
|
|
2728
|
+
inheritedLocale: resolvedLocale,
|
|
2729
|
+
attachmentUploader: configAttachments.uploader
|
|
2730
|
+
}),
|
|
2731
|
+
attachments(configAttachments),
|
|
2732
|
+
selectionToolbar({
|
|
2733
|
+
...configSelectionToolbar,
|
|
2734
|
+
inheritedLocale: resolvedLocale
|
|
2735
|
+
}),
|
|
2736
|
+
chunkWFVCG4LD_cjs.tableOfContents(configToc),
|
|
2737
|
+
...!disableViewPlugin ? headingFold(configHeadingFold) : [],
|
|
2738
|
+
// Plugin extensions & keymaps
|
|
2739
|
+
pluginExtensions,
|
|
2740
|
+
pluginKeymaps.length > 0 ? view.keymap.of(pluginKeymaps) : [],
|
|
2741
|
+
// Config keymaps & extensions
|
|
2742
|
+
configKeymap.length > 0 ? view.keymap.of(configKeymap) : [],
|
|
2743
|
+
extensions
|
|
2744
|
+
];
|
|
2745
|
+
return composedExtensions;
|
|
2746
|
+
}
|
|
2747
|
+
var MardoraPlugin = class {
|
|
2748
|
+
/** Decoration priority (higher = applied later) */
|
|
2749
|
+
decorationPriority = 100;
|
|
2750
|
+
/** Plugin dependencies - names of required plugins */
|
|
2751
|
+
dependencies = [];
|
|
2752
|
+
/** Node types this plugin handles for decorations and preview rendering */
|
|
2753
|
+
requiredNodes = [];
|
|
2754
|
+
/** Private configuration storage */
|
|
2755
|
+
_config = {};
|
|
2756
|
+
/** Protected context - accessible to subclasses */
|
|
2757
|
+
_context = null;
|
|
2758
|
+
/** Get plugin configuration */
|
|
2759
|
+
get config() {
|
|
2760
|
+
return this._config;
|
|
2761
|
+
}
|
|
2762
|
+
/** Set plugin configuration */
|
|
2763
|
+
set config(value) {
|
|
2764
|
+
this._config = value;
|
|
2765
|
+
}
|
|
2766
|
+
/** Get plugin context */
|
|
2767
|
+
get context() {
|
|
2768
|
+
return this._context;
|
|
2769
|
+
}
|
|
2770
|
+
/** Plugin theme */
|
|
2771
|
+
get theme() {
|
|
2772
|
+
return chunkWFVCG4LD_cjs.createTheme({
|
|
2773
|
+
default: {},
|
|
2774
|
+
dark: {},
|
|
2775
|
+
light: {}
|
|
2776
|
+
});
|
|
2777
|
+
}
|
|
2778
|
+
// ============================================
|
|
2779
|
+
// EXTENSION METHODS (overridable by subclasses)
|
|
2780
|
+
// ============================================
|
|
2781
|
+
/**
|
|
2782
|
+
* Return CodeMirror extensions for this plugin
|
|
2783
|
+
* Override to provide custom extensions
|
|
2784
|
+
*/
|
|
2785
|
+
getExtensions() {
|
|
2786
|
+
return [];
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Return markdown parser extensions
|
|
2790
|
+
* Override to extend markdown parsing
|
|
2791
|
+
*/
|
|
2792
|
+
getMarkdownConfig() {
|
|
2793
|
+
return null;
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Return keybindings for this plugin
|
|
2797
|
+
* Override to add custom keyboard shortcuts
|
|
2798
|
+
*/
|
|
2799
|
+
getKeymap() {
|
|
2800
|
+
return [];
|
|
2801
|
+
}
|
|
2802
|
+
// ============================================
|
|
2803
|
+
// DECORATION METHODS (overridable by subclasses)
|
|
2804
|
+
// ============================================
|
|
2805
|
+
/**
|
|
2806
|
+
* Build decorations for the current view state
|
|
2807
|
+
* Override to contribute decorations to the editor
|
|
2808
|
+
*
|
|
2809
|
+
* @param ctx - Decoration context with view and decoration array
|
|
2810
|
+
*/
|
|
2811
|
+
buildDecorations(_ctx) {
|
|
2812
|
+
}
|
|
2813
|
+
// ============================================
|
|
2814
|
+
// LIFECYCLE HOOKS (overridable by subclasses)
|
|
2815
|
+
// ============================================
|
|
2816
|
+
/**
|
|
2817
|
+
* Called when plugin is registered with mardora
|
|
2818
|
+
* Override to perform initialization
|
|
2819
|
+
*
|
|
2820
|
+
* @param context - Plugin context with configuration
|
|
2821
|
+
*/
|
|
2822
|
+
onRegister(context) {
|
|
2823
|
+
this._context = context;
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* Called when plugin is unregistered
|
|
2827
|
+
* Override to perform cleanup
|
|
2828
|
+
*/
|
|
2829
|
+
onUnregister() {
|
|
2830
|
+
this._context = null;
|
|
2831
|
+
}
|
|
2832
|
+
/**
|
|
2833
|
+
* Called when EditorView is created and ready
|
|
2834
|
+
* Override to perform view-specific initialization
|
|
2835
|
+
*
|
|
2836
|
+
* @param view - The EditorView instance
|
|
2837
|
+
*/
|
|
2838
|
+
onViewReady(_view) {
|
|
2839
|
+
}
|
|
2840
|
+
/**
|
|
2841
|
+
* Called on view updates (document changes, selection changes, etc.)
|
|
2842
|
+
* Override to react to editor changes
|
|
2843
|
+
*
|
|
2844
|
+
* @param update - The ViewUpdate with change information
|
|
2845
|
+
*/
|
|
2846
|
+
onViewUpdate(_update) {
|
|
2847
|
+
}
|
|
2848
|
+
// ============================================
|
|
2849
|
+
// PROTECTED UTILITIES (for subclasses)
|
|
2850
|
+
// ============================================
|
|
2851
|
+
/**
|
|
2852
|
+
* Helper to get current editor state
|
|
2853
|
+
* @param view - The EditorView instance
|
|
2854
|
+
*/
|
|
2855
|
+
getState(view) {
|
|
2856
|
+
return view.state;
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Helper to get current document
|
|
2860
|
+
* @param view - The EditorView instance
|
|
2861
|
+
*/
|
|
2862
|
+
getDocument(view) {
|
|
2863
|
+
return view.state.doc;
|
|
2864
|
+
}
|
|
2865
|
+
/**
|
|
2866
|
+
* Get CSS styles for preview mode
|
|
2867
|
+
* Override to provide custom CSS for preview rendering
|
|
2868
|
+
*
|
|
2869
|
+
* @param theme - Current theme enum
|
|
2870
|
+
* @returns CSS string for preview styles
|
|
2871
|
+
*/
|
|
2872
|
+
getPreviewStyles(theme, wrapperClass) {
|
|
2873
|
+
const themeStyles = this.theme(theme);
|
|
2874
|
+
return this.transformToCss(themeStyles, wrapperClass);
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Transform ThemeStyle object to CSS string for preview
|
|
2878
|
+
* Uses cssClassMap to convert CM selectors to semantic selectors
|
|
2879
|
+
*/
|
|
2880
|
+
transformToCss(themeStyles, wrapperClass) {
|
|
2881
|
+
const styleMod$1 = new styleMod.StyleModule(themeStyles, {
|
|
2882
|
+
finish: (sel) => {
|
|
2883
|
+
return `.${wrapperClass} ${sel}`;
|
|
2884
|
+
}
|
|
2885
|
+
});
|
|
2886
|
+
return styleMod$1.getRules();
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
var DecorationPlugin = class extends MardoraPlugin {
|
|
2890
|
+
/**
|
|
2891
|
+
* Decoration priority - lower than default for decoration plugins
|
|
2892
|
+
* Override to customize
|
|
2893
|
+
*/
|
|
2894
|
+
decorationPriority = 50;
|
|
2895
|
+
};
|
|
2896
|
+
var SyntaxPlugin = class extends MardoraPlugin {
|
|
2897
|
+
};
|
|
2898
|
+
|
|
2899
|
+
Object.defineProperty(exports, "Compartment", {
|
|
2900
|
+
enumerable: true,
|
|
2901
|
+
get: function () { return state.Compartment; }
|
|
2902
|
+
});
|
|
2903
|
+
Object.defineProperty(exports, "EditorSelection", {
|
|
2904
|
+
enumerable: true,
|
|
2905
|
+
get: function () { return state.EditorSelection; }
|
|
2906
|
+
});
|
|
2907
|
+
Object.defineProperty(exports, "EditorState", {
|
|
2908
|
+
enumerable: true,
|
|
2909
|
+
get: function () { return state.EditorState; }
|
|
2910
|
+
});
|
|
2911
|
+
Object.defineProperty(exports, "StateEffect", {
|
|
2912
|
+
enumerable: true,
|
|
2913
|
+
get: function () { return state.StateEffect; }
|
|
2914
|
+
});
|
|
2915
|
+
Object.defineProperty(exports, "StateField", {
|
|
2916
|
+
enumerable: true,
|
|
2917
|
+
get: function () { return state.StateField; }
|
|
2918
|
+
});
|
|
2919
|
+
Object.defineProperty(exports, "EditorView", {
|
|
2920
|
+
enumerable: true,
|
|
2921
|
+
get: function () { return view.EditorView; }
|
|
2922
|
+
});
|
|
2923
|
+
Object.defineProperty(exports, "highlightActiveLine", {
|
|
2924
|
+
enumerable: true,
|
|
2925
|
+
get: function () { return view.highlightActiveLine; }
|
|
2926
|
+
});
|
|
2927
|
+
Object.defineProperty(exports, "keymap", {
|
|
2928
|
+
enumerable: true,
|
|
2929
|
+
get: function () { return view.keymap; }
|
|
2930
|
+
});
|
|
2931
|
+
exports.DecorationPlugin = DecorationPlugin;
|
|
2932
|
+
exports.MardoraPlugin = MardoraPlugin;
|
|
2933
|
+
exports.SyntaxPlugin = SyntaxPlugin;
|
|
2934
|
+
exports.attachments = attachments;
|
|
2935
|
+
exports.buildBlockTypeChange = buildBlockTypeChange;
|
|
2936
|
+
exports.buildInlineFormatChange = buildInlineFormatChange;
|
|
2937
|
+
exports.buildLinkChange = buildLinkChange;
|
|
2938
|
+
exports.buildListChange = buildListChange;
|
|
2939
|
+
exports.buildSlashReplacement = buildSlashReplacement;
|
|
2940
|
+
exports.computeSelectionToolbarLayout = computeSelectionToolbarLayout;
|
|
2941
|
+
exports.createSelectionToolbarElement = createSelectionToolbarElement;
|
|
2942
|
+
exports.createSlashMenuElement = createSlashMenuElement;
|
|
2943
|
+
exports.createSlashRuntimeConfig = createSlashRuntimeConfig;
|
|
2944
|
+
exports.createUploadMarker = createUploadMarker;
|
|
2945
|
+
exports.defaultMardoraLocale = defaultMardoraLocale;
|
|
2946
|
+
exports.defaultSlashCommands = defaultSlashCommands;
|
|
2947
|
+
exports.detectAttachmentKind = detectAttachmentKind;
|
|
2948
|
+
exports.detectSelectionBlockType = detectSelectionBlockType;
|
|
2949
|
+
exports.detectSlashQuery = detectSlashQuery;
|
|
2950
|
+
exports.extractHeadingFoldRangesFromState = extractHeadingFoldRangesFromState;
|
|
2951
|
+
exports.filterSlashCommands = filterSlashCommands;
|
|
2952
|
+
exports.formatAttachmentMarkdown = formatAttachmentMarkdown;
|
|
2953
|
+
exports.getDefaultSlashCommands = getDefaultSlashCommands;
|
|
2954
|
+
exports.getSelectionToolbarMessages = getSelectionToolbarMessages;
|
|
2955
|
+
exports.getSlashMessages = getSlashMessages;
|
|
2956
|
+
exports.headingFold = headingFold;
|
|
2957
|
+
exports.headingFoldTheme = headingFoldTheme;
|
|
2958
|
+
exports.isAcceptedAttachment = isAcceptedAttachment;
|
|
2959
|
+
exports.mardora = mardora;
|
|
2960
|
+
exports.mardoraBaseTheme = mardoraBaseTheme;
|
|
2961
|
+
exports.markdownResetExtension = markdownResetExtension;
|
|
2962
|
+
exports.parseSelectedLink = parseSelectedLink;
|
|
2963
|
+
exports.resolveHeadingFoldConfig = resolveHeadingFoldConfig;
|
|
2964
|
+
exports.resolveMardoraLocale = resolveMardoraLocale;
|
|
2965
|
+
exports.selectionToolbar = selectionToolbar;
|
|
2966
|
+
exports.selectionToolbarTheme = selectionToolbarTheme;
|
|
2967
|
+
exports.slashCommands = slashCommands;
|
|
2968
|
+
exports.slashMenuTheme = slashMenuTheme;
|
|
2969
|
+
exports.uploadAttachmentFile = uploadAttachmentFile;
|
|
2970
|
+
//# sourceMappingURL=chunk-MLBEBFHB.cjs.map
|
|
2971
|
+
//# sourceMappingURL=chunk-MLBEBFHB.cjs.map
|