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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/chunk-3OCUX4OO.js +7690 -0
  4. package/dist/chunk-3OCUX4OO.js.map +1 -0
  5. package/dist/chunk-3ZOCCFDL.cjs +74 -0
  6. package/dist/chunk-3ZOCCFDL.cjs.map +1 -0
  7. package/dist/chunk-7JOEPNEV.cjs +7740 -0
  8. package/dist/chunk-7JOEPNEV.cjs.map +1 -0
  9. package/dist/chunk-BIKZQZ6W.js +33 -0
  10. package/dist/chunk-BIKZQZ6W.js.map +1 -0
  11. package/dist/chunk-EQJESPP2.js +234 -0
  12. package/dist/chunk-EQJESPP2.js.map +1 -0
  13. package/dist/chunk-G4SE26YY.js +70 -0
  14. package/dist/chunk-G4SE26YY.js.map +1 -0
  15. package/dist/chunk-KNDWF2DP.cjs +35 -0
  16. package/dist/chunk-KNDWF2DP.cjs.map +1 -0
  17. package/dist/chunk-MLBEBFHB.cjs +2971 -0
  18. package/dist/chunk-MLBEBFHB.cjs.map +1 -0
  19. package/dist/chunk-P7JFCYU3.js +905 -0
  20. package/dist/chunk-P7JFCYU3.js.map +1 -0
  21. package/dist/chunk-SWFUKJDO.cjs +243 -0
  22. package/dist/chunk-SWFUKJDO.cjs.map +1 -0
  23. package/dist/chunk-WFVCG4LD.cjs +926 -0
  24. package/dist/chunk-WFVCG4LD.cjs.map +1 -0
  25. package/dist/chunk-XL6WFGJT.js +2901 -0
  26. package/dist/chunk-XL6WFGJT.js.map +1 -0
  27. package/dist/editor/index.cjs +277 -0
  28. package/dist/editor/index.cjs.map +1 -0
  29. package/dist/editor/index.d.cts +186 -0
  30. package/dist/editor/index.d.ts +186 -0
  31. package/dist/editor/index.js +4 -0
  32. package/dist/editor/index.js.map +1 -0
  33. package/dist/index.cjs +405 -0
  34. package/dist/index.cjs.map +1 -0
  35. package/dist/index.d.cts +13 -0
  36. package/dist/index.d.ts +13 -0
  37. package/dist/index.js +8 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/lib/index.cjs +12 -0
  40. package/dist/lib/index.cjs.map +1 -0
  41. package/dist/lib/index.d.cts +16 -0
  42. package/dist/lib/index.d.ts +16 -0
  43. package/dist/lib/index.js +3 -0
  44. package/dist/lib/index.js.map +1 -0
  45. package/dist/mardora-DCwjomil.d.cts +640 -0
  46. package/dist/mardora-DCwjomil.d.ts +640 -0
  47. package/dist/plugins/index.cjs +104 -0
  48. package/dist/plugins/index.cjs.map +1 -0
  49. package/dist/plugins/index.d.cts +740 -0
  50. package/dist/plugins/index.d.ts +740 -0
  51. package/dist/plugins/index.js +7 -0
  52. package/dist/plugins/index.js.map +1 -0
  53. package/dist/preview/index.cjs +38 -0
  54. package/dist/preview/index.cjs.map +1 -0
  55. package/dist/preview/index.d.cts +101 -0
  56. package/dist/preview/index.d.ts +101 -0
  57. package/dist/preview/index.js +5 -0
  58. package/dist/preview/index.js.map +1 -0
  59. package/dist/types-NBsaxl4d.d.cts +71 -0
  60. package/dist/types-Pw2SWWAR.d.ts +71 -0
  61. package/package.json +92 -0
  62. package/src/editor/attachments/extension.ts +181 -0
  63. package/src/editor/attachments/format.ts +63 -0
  64. package/src/editor/attachments/index.ts +3 -0
  65. package/src/editor/attachments/types.ts +37 -0
  66. package/src/editor/heading-fold/config.ts +25 -0
  67. package/src/editor/heading-fold/extension.ts +268 -0
  68. package/src/editor/heading-fold/extract.ts +88 -0
  69. package/src/editor/heading-fold/index.ts +5 -0
  70. package/src/editor/heading-fold/theme.ts +85 -0
  71. package/src/editor/heading-fold/types.ts +24 -0
  72. package/src/editor/i18n.ts +13 -0
  73. package/src/editor/icons/index.ts +367 -0
  74. package/src/editor/index.ts +16 -0
  75. package/src/editor/mardora.ts +257 -0
  76. package/src/editor/media-lightbox-theme.ts +146 -0
  77. package/src/editor/media-lightbox.ts +125 -0
  78. package/src/editor/plugin.ts +294 -0
  79. package/src/editor/selection-toolbar/activation.ts +123 -0
  80. package/src/editor/selection-toolbar/commands.ts +279 -0
  81. package/src/editor/selection-toolbar/extension.ts +564 -0
  82. package/src/editor/selection-toolbar/i18n.ts +164 -0
  83. package/src/editor/selection-toolbar/index.ts +7 -0
  84. package/src/editor/selection-toolbar/menu.ts +252 -0
  85. package/src/editor/selection-toolbar/position.ts +43 -0
  86. package/src/editor/selection-toolbar/theme.ts +195 -0
  87. package/src/editor/selection-toolbar/types.ts +155 -0
  88. package/src/editor/slash/default-commands.ts +190 -0
  89. package/src/editor/slash/extension.ts +319 -0
  90. package/src/editor/slash/index.ts +7 -0
  91. package/src/editor/slash/insertions.ts +26 -0
  92. package/src/editor/slash/menu.ts +123 -0
  93. package/src/editor/slash/position.ts +61 -0
  94. package/src/editor/slash/query.ts +33 -0
  95. package/src/editor/slash/theme.ts +113 -0
  96. package/src/editor/slash/types.ts +40 -0
  97. package/src/editor/table-of-contents/extension.ts +202 -0
  98. package/src/editor/table-of-contents/extract.ts +53 -0
  99. package/src/editor/table-of-contents/index.ts +7 -0
  100. package/src/editor/table-of-contents/panel.ts +83 -0
  101. package/src/editor/table-of-contents/slug.ts +50 -0
  102. package/src/editor/table-of-contents/storage.ts +35 -0
  103. package/src/editor/table-of-contents/theme.ts +153 -0
  104. package/src/editor/table-of-contents/types.ts +44 -0
  105. package/src/editor/theme.ts +72 -0
  106. package/src/editor/utils.ts +176 -0
  107. package/src/editor/view-plugin.ts +189 -0
  108. package/src/index.ts +5 -0
  109. package/src/lib/index.ts +2 -0
  110. package/src/lib/input-handler.ts +47 -0
  111. package/src/plugins/code-plugin.theme.ts +545 -0
  112. package/src/plugins/code-plugin.ts +1892 -0
  113. package/src/plugins/emoji-plugin.ts +140 -0
  114. package/src/plugins/heading-plugin.ts +194 -0
  115. package/src/plugins/hr-plugin.ts +102 -0
  116. package/src/plugins/html-plugin.ts +353 -0
  117. package/src/plugins/image-plugin.ts +806 -0
  118. package/src/plugins/index.ts +71 -0
  119. package/src/plugins/inline-plugin.ts +311 -0
  120. package/src/plugins/link-plugin.ts +509 -0
  121. package/src/plugins/list-plugin.ts +492 -0
  122. package/src/plugins/math-plugin.ts +526 -0
  123. package/src/plugins/mermaid-plugin.ts +513 -0
  124. package/src/plugins/paragraph-plugin.ts +38 -0
  125. package/src/plugins/quote-plugin.ts +733 -0
  126. package/src/plugins/table-controls-theme.ts +126 -0
  127. package/src/plugins/table-controls.ts +423 -0
  128. package/src/plugins/table-model.ts +661 -0
  129. package/src/plugins/table-plugin.ts +2111 -0
  130. package/src/preview/context.ts +45 -0
  131. package/src/preview/css-generator.ts +64 -0
  132. package/src/preview/default-renderers.ts +29 -0
  133. package/src/preview/index.ts +29 -0
  134. package/src/preview/preview.ts +41 -0
  135. package/src/preview/renderer.ts +184 -0
  136. package/src/preview/syntax-theme.ts +112 -0
  137. package/src/preview/toc.ts +23 -0
  138. package/src/preview/types.ts +89 -0
@@ -0,0 +1,2901 @@
1
+ import { selectionOverlapsRange, cursorInRange, stripMarkdownHeadingText, createMardoraIcon, tableOfContents, createTheme } from './chunk-P7JFCYU3.js';
2
+ import { Facet, StateEffect, StateField, RangeSetBuilder, Prec } from '@codemirror/state';
3
+ export { Compartment, EditorSelection, EditorState, StateEffect, StateField } from '@codemirror/state';
4
+ import { EditorView, ViewPlugin, Decoration, WidgetType, keymap, highlightActiveLine } from '@codemirror/view';
5
+ export { EditorView, highlightActiveLine, keymap } from '@codemirror/view';
6
+ import { markdown, markdownLanguage, markdownKeymap } from '@codemirror/lang-markdown';
7
+ import { HighlightStyle, syntaxHighlighting, syntaxTree, ensureSyntaxTree, indentOnInput } from '@codemirror/language';
8
+ import { tags } from '@lezer/highlight';
9
+ import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
10
+ import { languages } from '@codemirror/language-data';
11
+ import { StyleModule } from 'style-mod';
12
+
13
+ var mardoraBaseTheme = 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 = HighlightStyle.define([
44
+ {
45
+ tag: [
46
+ tags.heading,
47
+ tags.strong,
48
+ tags.emphasis,
49
+ tags.strikethrough,
50
+ tags.link,
51
+ tags.url,
52
+ tags.quote,
53
+ tags.list,
54
+ tags.meta,
55
+ tags.contentSeparator,
56
+ tags.labelName
57
+ ],
58
+ color: "inherit",
59
+ fontWeight: "inherit",
60
+ fontStyle: "inherit",
61
+ textDecoration: "none"
62
+ }
63
+ ]);
64
+ var markdownResetExtension = syntaxHighlighting(markdownResetStyle, { fallback: false });
65
+
66
+ // src/editor/view-plugin.ts
67
+ var MardoraPluginsFacet = Facet.define({
68
+ combine: (values) => values.flat()
69
+ });
70
+ var mardoraOnNodesChangeFacet = Facet.define({
71
+ combine: (values) => values.find((v) => v !== void 0)
72
+ });
73
+ var mardoraThemeFacet = Facet.define({
74
+ combine: (values) => values.find((v) => v !== void 0) || "auto" /* AUTO */
75
+ });
76
+ function buildDecorations(view, plugins = []) {
77
+ const builder = new RangeSetBuilder();
78
+ const decorations = [];
79
+ if (plugins.length > 0) {
80
+ const ctx = {
81
+ view,
82
+ decorations,
83
+ selectionOverlapsRange: (from, to) => selectionOverlapsRange(view, from, to),
84
+ cursorInRange: (from, to) => 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 = 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: 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 = ViewPlugin.fromClass(mardoraViewPluginClass, {
156
+ decorations: (v) => v.decorations,
157
+ provide: () => []
158
+ });
159
+ var mardoraEditorClass = 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: "![image](url)",
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 = 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 = 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 `![${input.name}](mardora-upload://${input.taskId})`;
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 ? `![${name}](${result.url} "${result.title}")` : `![${name}](${result.url})`;
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
+ 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 = ViewPlugin.define((view) => new SlashCommandViewPlugin(view, normalizedConfig));
995
+ return [
996
+ slashMenuTheme,
997
+ plugin,
998
+ Prec.highest(
999
+ 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 = createMardoraIcon(button.icon);
1290
+ if (icon) element.appendChild(icon);
1291
+ } else {
1292
+ const icon = 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 = 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 = 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 = 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
+ 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 = ViewPlugin.define((view) => new SelectionToolbarViewPlugin(view, config));
2273
+ return [
2274
+ selectionToolbarTheme,
2275
+ plugin,
2276
+ Prec.highest(
2277
+ 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 = ensureSyntaxTree(state, state.doc.length, headingFoldParseTimeout) ?? syntaxTree(state);
2319
+ tree.iterate({
2320
+ enter: (node) => {
2321
+ const level = headingLevel(node);
2322
+ if (!level) return;
2323
+ const text = 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 = 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 = StateEffect.define();
2438
+ var headingFoldConfigFacet = 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 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 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 = 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(Decoration.line({ class: lineClass }).range(range.headingLineFrom));
2552
+ decorations.push(
2553
+ Decoration.widget({
2554
+ widget: new HeadingFoldWidget(range, isFolded),
2555
+ side: -1
2556
+ }).range(range.headingFrom)
2557
+ );
2558
+ if (isFolded) {
2559
+ decorations.push(
2560
+ Decoration.widget({
2561
+ widget: new FoldPlaceholderWidget(),
2562
+ side: 1,
2563
+ mardoraHeadingFoldRole: "placeholder"
2564
+ }).range(range.headingTo)
2565
+ );
2566
+ decorations.push(
2567
+ Decoration.replace({
2568
+ mardoraHeadingFoldRole: "hidden-content"
2569
+ }).range(range.foldFrom, range.foldTo)
2570
+ );
2571
+ }
2572
+ }
2573
+ return Decoration.set(decorations, true);
2574
+ }
2575
+ var headingFoldStateField = 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) => 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
+ 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(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 = markdown({
2698
+ base: markdownLanguage,
2699
+ codeLanguages: languages,
2700
+ extensions: markdownExtensions,
2701
+ addKeymap: true,
2702
+ completeHTMLTags: true,
2703
+ pasteURLAsLink: true
2704
+ });
2705
+ const baseExtensions = [
2706
+ ...defaultKeybindings ? [keymap.of(defaultKeymap)] : [],
2707
+ ...configHistory ? [history(), keymap.of(historyKeymap)] : [],
2708
+ ...configIndentWithTab ? [indentOnInput(), keymap.of([indentWithTab])] : [],
2709
+ ...configHighlightActiveLine && disableViewPlugin ? [highlightActiveLine()] : []
2710
+ ];
2711
+ const mardoraExtensions = [];
2712
+ if (!disableViewPlugin) {
2713
+ mardoraExtensions.push(createMardoraViewExtension(configTheme, baseStyles, allPlugins, configOnNodesChange));
2714
+ mardoraExtensions.push(Prec.highest(markdownResetExtension));
2715
+ }
2716
+ if (!disableViewPlugin || configLineWrapping) mardoraExtensions.push(EditorView.lineWrapping);
2717
+ const composedExtensions = [
2718
+ // Core markdown support (highest priority)
2719
+ Prec.high(markdownSupport),
2720
+ Prec.high(keymap.of(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
+ tableOfContents(configToc),
2737
+ ...!disableViewPlugin ? headingFold(configHeadingFold) : [],
2738
+ // Plugin extensions & keymaps
2739
+ pluginExtensions,
2740
+ pluginKeymaps.length > 0 ? keymap.of(pluginKeymaps) : [],
2741
+ // Config keymaps & extensions
2742
+ configKeymap.length > 0 ? 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 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 = new StyleModule(themeStyles, {
2882
+ finish: (sel) => {
2883
+ return `.${wrapperClass} ${sel}`;
2884
+ }
2885
+ });
2886
+ return styleMod.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
+ export { DecorationPlugin, MardoraPlugin, SyntaxPlugin, attachments, buildBlockTypeChange, buildInlineFormatChange, buildLinkChange, buildListChange, buildSlashReplacement, computeSelectionToolbarLayout, createSelectionToolbarElement, createSlashMenuElement, createSlashRuntimeConfig, createUploadMarker, defaultMardoraLocale, defaultSlashCommands, detectAttachmentKind, detectSelectionBlockType, detectSlashQuery, extractHeadingFoldRangesFromState, filterSlashCommands, formatAttachmentMarkdown, getDefaultSlashCommands, getSelectionToolbarMessages, getSlashMessages, headingFold, headingFoldTheme, isAcceptedAttachment, mardora, mardoraBaseTheme, markdownResetExtension, parseSelectedLink, resolveHeadingFoldConfig, resolveMardoraLocale, selectionToolbar, selectionToolbarTheme, slashCommands, slashMenuTheme, uploadAttachmentFile };
2900
+ //# sourceMappingURL=chunk-XL6WFGJT.js.map
2901
+ //# sourceMappingURL=chunk-XL6WFGJT.js.map