mardora 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/chunk-3OCUX4OO.js +7690 -0
- package/dist/chunk-3OCUX4OO.js.map +1 -0
- package/dist/chunk-3ZOCCFDL.cjs +74 -0
- package/dist/chunk-3ZOCCFDL.cjs.map +1 -0
- package/dist/chunk-7JOEPNEV.cjs +7740 -0
- package/dist/chunk-7JOEPNEV.cjs.map +1 -0
- package/dist/chunk-BIKZQZ6W.js +33 -0
- package/dist/chunk-BIKZQZ6W.js.map +1 -0
- package/dist/chunk-EQJESPP2.js +234 -0
- package/dist/chunk-EQJESPP2.js.map +1 -0
- package/dist/chunk-G4SE26YY.js +70 -0
- package/dist/chunk-G4SE26YY.js.map +1 -0
- package/dist/chunk-KNDWF2DP.cjs +35 -0
- package/dist/chunk-KNDWF2DP.cjs.map +1 -0
- package/dist/chunk-MLBEBFHB.cjs +2971 -0
- package/dist/chunk-MLBEBFHB.cjs.map +1 -0
- package/dist/chunk-P7JFCYU3.js +905 -0
- package/dist/chunk-P7JFCYU3.js.map +1 -0
- package/dist/chunk-SWFUKJDO.cjs +243 -0
- package/dist/chunk-SWFUKJDO.cjs.map +1 -0
- package/dist/chunk-WFVCG4LD.cjs +926 -0
- package/dist/chunk-WFVCG4LD.cjs.map +1 -0
- package/dist/chunk-XL6WFGJT.js +2901 -0
- package/dist/chunk-XL6WFGJT.js.map +1 -0
- package/dist/editor/index.cjs +277 -0
- package/dist/editor/index.cjs.map +1 -0
- package/dist/editor/index.d.cts +186 -0
- package/dist/editor/index.d.ts +186 -0
- package/dist/editor/index.js +4 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/index.cjs +405 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/index.cjs +12 -0
- package/dist/lib/index.cjs.map +1 -0
- package/dist/lib/index.d.cts +16 -0
- package/dist/lib/index.d.ts +16 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/mardora-DCwjomil.d.cts +640 -0
- package/dist/mardora-DCwjomil.d.ts +640 -0
- package/dist/plugins/index.cjs +104 -0
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.d.cts +740 -0
- package/dist/plugins/index.d.ts +740 -0
- package/dist/plugins/index.js +7 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/preview/index.cjs +38 -0
- package/dist/preview/index.cjs.map +1 -0
- package/dist/preview/index.d.cts +101 -0
- package/dist/preview/index.d.ts +101 -0
- package/dist/preview/index.js +5 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/types-NBsaxl4d.d.cts +71 -0
- package/dist/types-Pw2SWWAR.d.ts +71 -0
- package/package.json +92 -0
- package/src/editor/attachments/extension.ts +181 -0
- package/src/editor/attachments/format.ts +63 -0
- package/src/editor/attachments/index.ts +3 -0
- package/src/editor/attachments/types.ts +37 -0
- package/src/editor/heading-fold/config.ts +25 -0
- package/src/editor/heading-fold/extension.ts +268 -0
- package/src/editor/heading-fold/extract.ts +88 -0
- package/src/editor/heading-fold/index.ts +5 -0
- package/src/editor/heading-fold/theme.ts +85 -0
- package/src/editor/heading-fold/types.ts +24 -0
- package/src/editor/i18n.ts +13 -0
- package/src/editor/icons/index.ts +367 -0
- package/src/editor/index.ts +16 -0
- package/src/editor/mardora.ts +257 -0
- package/src/editor/media-lightbox-theme.ts +146 -0
- package/src/editor/media-lightbox.ts +125 -0
- package/src/editor/plugin.ts +294 -0
- package/src/editor/selection-toolbar/activation.ts +123 -0
- package/src/editor/selection-toolbar/commands.ts +279 -0
- package/src/editor/selection-toolbar/extension.ts +564 -0
- package/src/editor/selection-toolbar/i18n.ts +164 -0
- package/src/editor/selection-toolbar/index.ts +7 -0
- package/src/editor/selection-toolbar/menu.ts +252 -0
- package/src/editor/selection-toolbar/position.ts +43 -0
- package/src/editor/selection-toolbar/theme.ts +195 -0
- package/src/editor/selection-toolbar/types.ts +155 -0
- package/src/editor/slash/default-commands.ts +190 -0
- package/src/editor/slash/extension.ts +319 -0
- package/src/editor/slash/index.ts +7 -0
- package/src/editor/slash/insertions.ts +26 -0
- package/src/editor/slash/menu.ts +123 -0
- package/src/editor/slash/position.ts +61 -0
- package/src/editor/slash/query.ts +33 -0
- package/src/editor/slash/theme.ts +113 -0
- package/src/editor/slash/types.ts +40 -0
- package/src/editor/table-of-contents/extension.ts +202 -0
- package/src/editor/table-of-contents/extract.ts +53 -0
- package/src/editor/table-of-contents/index.ts +7 -0
- package/src/editor/table-of-contents/panel.ts +83 -0
- package/src/editor/table-of-contents/slug.ts +50 -0
- package/src/editor/table-of-contents/storage.ts +35 -0
- package/src/editor/table-of-contents/theme.ts +153 -0
- package/src/editor/table-of-contents/types.ts +44 -0
- package/src/editor/theme.ts +72 -0
- package/src/editor/utils.ts +176 -0
- package/src/editor/view-plugin.ts +189 -0
- package/src/index.ts +5 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/input-handler.ts +47 -0
- package/src/plugins/code-plugin.theme.ts +545 -0
- package/src/plugins/code-plugin.ts +1892 -0
- package/src/plugins/emoji-plugin.ts +140 -0
- package/src/plugins/heading-plugin.ts +194 -0
- package/src/plugins/hr-plugin.ts +102 -0
- package/src/plugins/html-plugin.ts +353 -0
- package/src/plugins/image-plugin.ts +806 -0
- package/src/plugins/index.ts +71 -0
- package/src/plugins/inline-plugin.ts +311 -0
- package/src/plugins/link-plugin.ts +509 -0
- package/src/plugins/list-plugin.ts +492 -0
- package/src/plugins/math-plugin.ts +526 -0
- package/src/plugins/mermaid-plugin.ts +513 -0
- package/src/plugins/paragraph-plugin.ts +38 -0
- package/src/plugins/quote-plugin.ts +733 -0
- package/src/plugins/table-controls-theme.ts +126 -0
- package/src/plugins/table-controls.ts +423 -0
- package/src/plugins/table-model.ts +661 -0
- package/src/plugins/table-plugin.ts +2111 -0
- package/src/preview/context.ts +45 -0
- package/src/preview/css-generator.ts +64 -0
- package/src/preview/default-renderers.ts +29 -0
- package/src/preview/index.ts +29 -0
- package/src/preview/preview.ts +41 -0
- package/src/preview/renderer.ts +184 -0
- package/src/preview/syntax-theme.ts +112 -0
- package/src/preview/toc.ts +23 -0
- package/src/preview/types.ts +89 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { Decoration, EditorView, WidgetType } from "@codemirror/view";
|
|
2
|
+
import { syntaxTree } from "@codemirror/language";
|
|
3
|
+
import { DecorationContext, DecorationPlugin } from "../editor/plugin";
|
|
4
|
+
import { createTheme, ThemeEnum } from "../editor";
|
|
5
|
+
import { createMediaPreviewButton } from "../editor/media-lightbox";
|
|
6
|
+
import { mediaLightboxTheme } from "../editor/media-lightbox-theme";
|
|
7
|
+
import { SyntaxNode } from "@lezer/common";
|
|
8
|
+
import { tags } from "@lezer/highlight";
|
|
9
|
+
import type { MarkdownConfig, BlockParser, Line, BlockContext } from "@lezer/markdown";
|
|
10
|
+
import mermaid from "mermaid";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initialize mermaid with default configuration
|
|
14
|
+
*/
|
|
15
|
+
mermaid.initialize({
|
|
16
|
+
startOnLoad: false,
|
|
17
|
+
theme: "default",
|
|
18
|
+
suppressErrorRendering: true,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render a mermaid diagram definition to SVG
|
|
23
|
+
*/
|
|
24
|
+
let mermaidCounter = 0;
|
|
25
|
+
async function renderMermaid(
|
|
26
|
+
definition: string,
|
|
27
|
+
options: Record<string, string> = {},
|
|
28
|
+
defaultTheme: string = "default"
|
|
29
|
+
): Promise<{ svg: string; error: string | null }> {
|
|
30
|
+
try {
|
|
31
|
+
const id = `mardora-mermaid-${mermaidCounter++}`;
|
|
32
|
+
let finalDefinition = definition;
|
|
33
|
+
|
|
34
|
+
// transform theme to mermaid config
|
|
35
|
+
const mermaidConfig: Record<string, string> = {};
|
|
36
|
+
if (options.theme) {
|
|
37
|
+
mermaidConfig.theme = options.theme;
|
|
38
|
+
} else {
|
|
39
|
+
mermaidConfig.theme = defaultTheme;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If we have config to apply, prepend the directive
|
|
43
|
+
if (Object.keys(mermaidConfig).length > 0) {
|
|
44
|
+
const jsonConfig = JSON.stringify(mermaidConfig);
|
|
45
|
+
// Mermaid directive format: %%{init: { ... }}%%
|
|
46
|
+
finalDefinition = `%%{init: ${jsonConfig} }%%\n${definition}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { svg } = await mermaid.render(id, finalDefinition);
|
|
50
|
+
return { svg, error: null };
|
|
51
|
+
} catch (e) {
|
|
52
|
+
const errorMsg = e instanceof Error ? e.message : "Unknown error";
|
|
53
|
+
return { svg: "", error: errorMsg };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Helper to parse attributes from fence line
|
|
59
|
+
* Example: ```mermaid theme="dark" scale="2"
|
|
60
|
+
*/
|
|
61
|
+
function parseAttributes(fenceLine: string): Record<string, string> {
|
|
62
|
+
const attributes: Record<string, string> = {};
|
|
63
|
+
// Match key="value" or key='value'
|
|
64
|
+
const regex = /(\w+)=["']([^"']*)["']/g;
|
|
65
|
+
let match;
|
|
66
|
+
while ((match = regex.exec(fenceLine)) !== null && match[1] && match[2]) {
|
|
67
|
+
attributes[match[1]] = match[2];
|
|
68
|
+
}
|
|
69
|
+
return attributes;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Mark decorations for mermaid syntax elements
|
|
74
|
+
*/
|
|
75
|
+
const mermaidMarkDecorations = {
|
|
76
|
+
"mermaid-block-start": Decoration.line({ class: "cm-mardora-mermaid-block-start" }),
|
|
77
|
+
"mermaid-block-end": Decoration.line({ class: "cm-mardora-mermaid-block-end" }),
|
|
78
|
+
"mermaid-block": Decoration.line({ class: "cm-mardora-mermaid-block" }),
|
|
79
|
+
"mermaid-block-rendered": Decoration.line({ class: "cm-mardora-mermaid-block-rendered" }),
|
|
80
|
+
"mermaid-marker": Decoration.mark({ class: "cm-mardora-mermaid-marker" }),
|
|
81
|
+
"mermaid-hidden": Decoration.mark({ class: "cm-mardora-mermaid-hidden" }),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Widget to render mermaid block diagrams
|
|
86
|
+
*/
|
|
87
|
+
class MermaidBlockWidget extends WidgetType {
|
|
88
|
+
constructor(
|
|
89
|
+
readonly definition: string,
|
|
90
|
+
readonly attributes: Record<string, string>,
|
|
91
|
+
readonly defaultTheme: string,
|
|
92
|
+
readonly from: number,
|
|
93
|
+
readonly to: number
|
|
94
|
+
) {
|
|
95
|
+
super();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override eq(other: MermaidBlockWidget): boolean {
|
|
99
|
+
return (
|
|
100
|
+
other.definition === this.definition &&
|
|
101
|
+
JSON.stringify(other.attributes) === JSON.stringify(this.attributes) &&
|
|
102
|
+
other.defaultTheme === this.defaultTheme &&
|
|
103
|
+
other.from === this.from &&
|
|
104
|
+
other.to === this.to
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
toDOM(view: EditorView) {
|
|
109
|
+
const div = document.createElement("div");
|
|
110
|
+
div.className = "cm-mardora-mermaid-rendered cm-mardora-media-preview";
|
|
111
|
+
div.style.cursor = "pointer";
|
|
112
|
+
|
|
113
|
+
// Show loading state initially
|
|
114
|
+
div.innerHTML = `<div class="cm-mardora-mermaid-loading">Rendering diagram…</div>`;
|
|
115
|
+
|
|
116
|
+
// Render mermaid asynchronously
|
|
117
|
+
// Render mermaid asynchronously
|
|
118
|
+
renderMermaid(this.definition, this.attributes, this.defaultTheme).then(({ svg, error }) => {
|
|
119
|
+
if (error) {
|
|
120
|
+
div.className += " cm-mardora-mermaid-error";
|
|
121
|
+
div.innerHTML = `<span>[Mermaid Error: ${error}]</span>`;
|
|
122
|
+
} else {
|
|
123
|
+
div.innerHTML = svg;
|
|
124
|
+
div.appendChild(
|
|
125
|
+
createMediaPreviewButton(div.ownerDocument, {
|
|
126
|
+
label: "放大查看 Mermaid 图",
|
|
127
|
+
content: () => ({ kind: "html", html: svg, title: "Mermaid 图" }),
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Click handler to select the raw mermaid text
|
|
134
|
+
div.addEventListener("click", (e) => {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
view.dispatch({
|
|
138
|
+
selection: { anchor: this.from, head: this.to },
|
|
139
|
+
scrollIntoView: true,
|
|
140
|
+
});
|
|
141
|
+
view.focus();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return div;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
override ignoreEvent(event: Event) {
|
|
148
|
+
return event.type !== "click";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Block parser for mermaid blocks:
|
|
154
|
+
* ```mermaid
|
|
155
|
+
* graph TD
|
|
156
|
+
* A --> B
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
const mermaidBlockParser: BlockParser = {
|
|
160
|
+
name: "MermaidBlock",
|
|
161
|
+
before: "FencedCode",
|
|
162
|
+
parse(cx: BlockContext, line: Line) {
|
|
163
|
+
const text = line.text;
|
|
164
|
+
const trimmed = text.slice(line.pos).trimStart();
|
|
165
|
+
|
|
166
|
+
// Must start with ```mermaid
|
|
167
|
+
if (!trimmed.startsWith("```mermaid")) return false;
|
|
168
|
+
|
|
169
|
+
// Ensure nothing meaningful after ```mermaid (allow trailing whitespace)
|
|
170
|
+
// We now allow attributes, so we don't strictly check for empty rest
|
|
171
|
+
// const rest = trimmed.slice(10);
|
|
172
|
+
// if (rest.trim().length > 0) return false;
|
|
173
|
+
|
|
174
|
+
const startLine = cx.lineStart;
|
|
175
|
+
let endPos = -1;
|
|
176
|
+
let closeBacktickStart = -1;
|
|
177
|
+
|
|
178
|
+
// Move past the opening line and find the closing ```
|
|
179
|
+
while (cx.nextLine()) {
|
|
180
|
+
const currentText = line.text;
|
|
181
|
+
const currentLineStart = cx.lineStart;
|
|
182
|
+
const lastLineEnd = currentLineStart + currentText.length;
|
|
183
|
+
|
|
184
|
+
// Check if this line is a closing ``` (only backticks, possibly with whitespace)
|
|
185
|
+
const trimmedLine = currentText.trim();
|
|
186
|
+
if (trimmedLine === "```") {
|
|
187
|
+
endPos = lastLineEnd;
|
|
188
|
+
closeBacktickStart = currentLineStart + currentText.indexOf("```");
|
|
189
|
+
// Move past the closing line so subsequent markdown gets parsed
|
|
190
|
+
cx.nextLine();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (endPos === -1) {
|
|
196
|
+
// No closing found, treat as regular text
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Create the mermaid block element with markers
|
|
201
|
+
const openMarkEnd = startLine + text.indexOf("```mermaid") + 10;
|
|
202
|
+
const openMark = cx.elt("MermaidBlockMark", startLine, openMarkEnd);
|
|
203
|
+
const closeMark = cx.elt("MermaidBlockMark", closeBacktickStart, closeBacktickStart + 3);
|
|
204
|
+
|
|
205
|
+
cx.addElement(cx.elt("MermaidBlock", startLine, endPos, [openMark, closeMark]));
|
|
206
|
+
|
|
207
|
+
return true;
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* MermaidPlugin - Renders mermaid diagrams in the editor
|
|
213
|
+
*
|
|
214
|
+
* Supports block mermaid syntax:
|
|
215
|
+
* ```mermaid
|
|
216
|
+
* graph TD
|
|
217
|
+
* A --> B
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* Behavior:
|
|
221
|
+
* - Always show rendered diagram below the block
|
|
222
|
+
* - Hide raw definition when cursor is outside the block
|
|
223
|
+
* - Show raw definition with styled markers when cursor is inside
|
|
224
|
+
*/
|
|
225
|
+
export class MermaidPlugin extends DecorationPlugin {
|
|
226
|
+
readonly name = "mermaid";
|
|
227
|
+
readonly version = "1.0.0";
|
|
228
|
+
override decorationPriority = 25;
|
|
229
|
+
override readonly requiredNodes = ["MermaidBlock", "MermaidBlockMark"] as const;
|
|
230
|
+
|
|
231
|
+
constructor() {
|
|
232
|
+
super();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Plugin theme
|
|
237
|
+
*/
|
|
238
|
+
override get theme() {
|
|
239
|
+
return theme;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Return markdown parser extensions for mermaid syntax
|
|
244
|
+
*/
|
|
245
|
+
override getMarkdownConfig(): MarkdownConfig {
|
|
246
|
+
return {
|
|
247
|
+
defineNodes: [
|
|
248
|
+
{ name: "MermaidBlock", block: true },
|
|
249
|
+
{ name: "MermaidBlockMark", style: tags.processingInstruction },
|
|
250
|
+
],
|
|
251
|
+
parseBlock: [mermaidBlockParser],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Build decorations for mermaid blocks
|
|
257
|
+
*/
|
|
258
|
+
buildDecorations(ctx: DecorationContext): void {
|
|
259
|
+
const { view, decorations } = ctx;
|
|
260
|
+
const tree = syntaxTree(view.state);
|
|
261
|
+
const config = this.context?.config;
|
|
262
|
+
const currentTheme = config?.theme === ThemeEnum.DARK ? "dark" : "default";
|
|
263
|
+
|
|
264
|
+
tree.iterate({
|
|
265
|
+
enter: (node) => {
|
|
266
|
+
const { from, to, name } = node;
|
|
267
|
+
|
|
268
|
+
if (name === "MermaidBlock") {
|
|
269
|
+
const content = view.state.sliceDoc(from, to);
|
|
270
|
+
|
|
271
|
+
// Extract mermaid definition (remove ```mermaid and ``` markers)
|
|
272
|
+
const lines = content.split("\n");
|
|
273
|
+
const definition = lines
|
|
274
|
+
.slice(1, -1) // Remove first and last lines (the markers)
|
|
275
|
+
.join("\n")
|
|
276
|
+
.trim();
|
|
277
|
+
|
|
278
|
+
const docLines = content.split("\n");
|
|
279
|
+
const fenceLine = docLines[0] || "";
|
|
280
|
+
const attributes = parseAttributes(fenceLine);
|
|
281
|
+
|
|
282
|
+
const nodeLineStart = view.state.doc.lineAt(from);
|
|
283
|
+
const nodeLineEnd = view.state.doc.lineAt(to);
|
|
284
|
+
const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
|
|
285
|
+
|
|
286
|
+
// Calculate line number width for mermaid block
|
|
287
|
+
const totalCodeLines = nodeLineEnd.number - nodeLineStart.number - 1;
|
|
288
|
+
const lineNumWidth = String(totalCodeLines).length;
|
|
289
|
+
let codeLineIndex = 1;
|
|
290
|
+
|
|
291
|
+
// Add line decorations for mermaid block
|
|
292
|
+
for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
|
|
293
|
+
const line = view.state.doc.line(i);
|
|
294
|
+
const isFenceLine = i === nodeLineStart.number || i === nodeLineEnd.number;
|
|
295
|
+
const relativeLineNum = codeLineIndex;
|
|
296
|
+
|
|
297
|
+
decorations.push(mermaidMarkDecorations["mermaid-block"].range(line.from));
|
|
298
|
+
if (!cursorInRange) decorations.push(mermaidMarkDecorations["mermaid-block-rendered"].range(line.from));
|
|
299
|
+
|
|
300
|
+
if (i === nodeLineStart.number)
|
|
301
|
+
decorations.push(mermaidMarkDecorations["mermaid-block-start"].range(line.from));
|
|
302
|
+
|
|
303
|
+
if (i === nodeLineEnd.number)
|
|
304
|
+
decorations.push(mermaidMarkDecorations["mermaid-block-end"].range(line.from));
|
|
305
|
+
|
|
306
|
+
if (!isFenceLine) {
|
|
307
|
+
decorations.push(
|
|
308
|
+
Decoration.line({
|
|
309
|
+
attributes: {
|
|
310
|
+
"data-line-num": String(relativeLineNum),
|
|
311
|
+
style: `--line-num-width: ${lineNumWidth}ch`,
|
|
312
|
+
},
|
|
313
|
+
}).range(line.from)
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Increment code line index (only for non-fence lines)
|
|
318
|
+
if (!isFenceLine) {
|
|
319
|
+
codeLineIndex++;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Always add the rendered widget below the block
|
|
324
|
+
decorations.push(
|
|
325
|
+
Decoration.widget({
|
|
326
|
+
widget: new MermaidBlockWidget(definition, attributes, currentTheme, from, to),
|
|
327
|
+
side: 1,
|
|
328
|
+
block: false,
|
|
329
|
+
}).range(to)
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
if (cursorInRange) {
|
|
333
|
+
// Cursor in range: show raw definition with styled markers
|
|
334
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
335
|
+
if (child.name === "MermaidBlockMark") {
|
|
336
|
+
decorations.push(mermaidMarkDecorations["mermaid-marker"].range(child.from, child.to));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
// Cursor out of range: hide the raw text
|
|
341
|
+
decorations.push(mermaidMarkDecorations["mermaid-hidden"].range(from, to));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Render mermaid to HTML for preview mode
|
|
350
|
+
*
|
|
351
|
+
* Renders the actual mermaid diagram to SVG HTML
|
|
352
|
+
*/
|
|
353
|
+
override async renderToHTML(
|
|
354
|
+
node: SyntaxNode,
|
|
355
|
+
_children: string,
|
|
356
|
+
ctx: { sliceDoc(from: number, to: number): string; sanitize(html: string): string }
|
|
357
|
+
): Promise<string | null> {
|
|
358
|
+
if (node.name === "MermaidBlock") {
|
|
359
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
360
|
+
const lines = content.split("\n");
|
|
361
|
+
const definition = lines.length > 1 ? lines.slice(1, -1).join("\n").trim() : "";
|
|
362
|
+
|
|
363
|
+
const fenceLine = lines[0] || "";
|
|
364
|
+
const attributes = parseAttributes(fenceLine);
|
|
365
|
+
|
|
366
|
+
const config = this.context?.config;
|
|
367
|
+
const currentTheme = config?.theme === ThemeEnum.DARK ? "dark" : "default";
|
|
368
|
+
|
|
369
|
+
const { svg, error } = await renderMermaid(definition, attributes, currentTheme);
|
|
370
|
+
|
|
371
|
+
if (error) {
|
|
372
|
+
return `<div class="cm-mardora-mermaid-error">${ctx.sanitize(`[Mermaid Error: ${error}]`)}</div>`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return `<div class="cm-mardora-mermaid-rendered">${svg}</div>`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Hide mermaid markers in preview
|
|
379
|
+
if (node.name === "MermaidBlockMark") {
|
|
380
|
+
return "";
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Theme for mermaid styling
|
|
389
|
+
*/
|
|
390
|
+
const mermaidTheme = createTheme({
|
|
391
|
+
default: {
|
|
392
|
+
// Raw mermaid block lines (monospace)
|
|
393
|
+
".cm-mardora-mermaid-block:not(.cm-mardora-mermaid-block-rendered)": {
|
|
394
|
+
"--radius": "0.375rem",
|
|
395
|
+
position: "relative",
|
|
396
|
+
|
|
397
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
398
|
+
fontSize: "0.9rem",
|
|
399
|
+
backgroundColor: "rgba(0, 0, 0, 0.03)",
|
|
400
|
+
padding: "0 1rem !important",
|
|
401
|
+
paddingLeft: "calc(var(--line-num-width, 2ch) + 1rem) !important",
|
|
402
|
+
lineHeight: "1.5",
|
|
403
|
+
borderLeft: "1px solid var(--color-border)",
|
|
404
|
+
borderRight: "1px solid var(--color-border)",
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
".cm-mardora-mermaid-block-start:not(.cm-mardora-mermaid-block-rendered)": {
|
|
408
|
+
overflow: "hidden",
|
|
409
|
+
borderTopLeftRadius: "var(--radius)",
|
|
410
|
+
borderTopRightRadius: "var(--radius)",
|
|
411
|
+
borderTop: "1px solid var(--color-border)",
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
".cm-mardora-mermaid-block-end:not(.cm-mardora-mermaid-block-rendered)": {
|
|
415
|
+
overflow: "hidden",
|
|
416
|
+
borderBottomLeftRadius: "var(--radius)",
|
|
417
|
+
borderBottomRightRadius: "var(--radius)",
|
|
418
|
+
borderBottom: "1px solid var(--color-border)",
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
".cm-mardora-mermaid-block:not(.cm-mardora-mermaid-block-rendered)::before": {
|
|
422
|
+
content: "attr(data-line-num)",
|
|
423
|
+
position: "absolute",
|
|
424
|
+
left: "0.5rem",
|
|
425
|
+
top: "0.2rem",
|
|
426
|
+
width: "var(--line-num-width, 2ch)",
|
|
427
|
+
textAlign: "right",
|
|
428
|
+
color: "#6a737d",
|
|
429
|
+
opacity: "0.6",
|
|
430
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
431
|
+
fontSize: "0.85rem",
|
|
432
|
+
userSelect: "none",
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
".cm-mardora-mermaid-block.cm-mardora-mermaid-block-rendered br": {
|
|
436
|
+
display: "none",
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
// Mermaid markers (```mermaid / ```)
|
|
440
|
+
".cm-mardora-mermaid-marker": {
|
|
441
|
+
color: "#6a737d",
|
|
442
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
// Hidden mermaid syntax (when cursor is not in range)
|
|
446
|
+
".cm-mardora-mermaid-hidden": {
|
|
447
|
+
display: "none",
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
// Rendered mermaid container
|
|
451
|
+
".cm-mardora-mermaid-rendered": {
|
|
452
|
+
display: "flex",
|
|
453
|
+
justifyContent: "center",
|
|
454
|
+
alignItems: "center",
|
|
455
|
+
padding: "1em 0",
|
|
456
|
+
borderRadius: "4px",
|
|
457
|
+
overflow: "auto",
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
// SVG inside rendered container
|
|
461
|
+
".cm-mardora-mermaid-rendered svg": {
|
|
462
|
+
maxWidth: "100%",
|
|
463
|
+
height: "auto",
|
|
464
|
+
aspectRatio: "auto",
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
// Loading state
|
|
468
|
+
".cm-mardora-mermaid-loading": {
|
|
469
|
+
display: "inline-block",
|
|
470
|
+
padding: "0.5em 1em",
|
|
471
|
+
color: "#6a737d",
|
|
472
|
+
fontSize: "0.875em",
|
|
473
|
+
fontStyle: "italic",
|
|
474
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
// Error styling
|
|
478
|
+
".cm-mardora-mermaid-error": {
|
|
479
|
+
display: "inline-block",
|
|
480
|
+
padding: "0.25em 0.5em",
|
|
481
|
+
backgroundColor: "rgba(255, 0, 0, 0.1)",
|
|
482
|
+
color: "#d73a49",
|
|
483
|
+
borderRadius: "4px",
|
|
484
|
+
fontSize: "0.875em",
|
|
485
|
+
fontStyle: "italic",
|
|
486
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
dark: {
|
|
491
|
+
".cm-mardora-mermaid-block:not(.cm-mardora-mermaid-block-rendered)": {
|
|
492
|
+
backgroundColor: "rgba(255, 255, 255, 0.03)",
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
".cm-mardora-mermaid-marker": {
|
|
496
|
+
color: "#8b949e",
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
".cm-mardora-mermaid-loading": {
|
|
500
|
+
color: "#8b949e",
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
".cm-mardora-mermaid-error": {
|
|
504
|
+
backgroundColor: "rgba(255, 0, 0, 0.15)",
|
|
505
|
+
color: "#f85149",
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const theme = (theme: Parameters<typeof mermaidTheme>[0]) => ({
|
|
511
|
+
...mermaidTheme(theme),
|
|
512
|
+
...mediaLightboxTheme(theme),
|
|
513
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SyntaxNode } from "@lezer/common";
|
|
2
|
+
import { MardoraPlugin } from "../editor/plugin";
|
|
3
|
+
import { createTheme } from "../editor";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ParagraphPlugin - Adds top and bottom padding to paragraphs in preview
|
|
7
|
+
*
|
|
8
|
+
* Applies visual spacing to markdown paragraphs for better readability
|
|
9
|
+
*/
|
|
10
|
+
export class ParagraphPlugin extends MardoraPlugin {
|
|
11
|
+
readonly name = "paragraph";
|
|
12
|
+
readonly version = "1.0.0";
|
|
13
|
+
override readonly requiredNodes = ["Paragraph"] as const;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Plugin theme for preview styling
|
|
17
|
+
*/
|
|
18
|
+
override get theme() {
|
|
19
|
+
return theme;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override renderToHTML(node: SyntaxNode, children: string): string | null {
|
|
23
|
+
if (node.name !== "Paragraph") {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return `<p class="cm-mardora-paragraph">${children}</p>`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const theme = createTheme({
|
|
32
|
+
default: {
|
|
33
|
+
".cm-mardora-paragraph": {
|
|
34
|
+
paddingTop: "0.5em",
|
|
35
|
+
paddingBottom: "0.5em",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|