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,526 @@
|
|
|
1
|
+
import { Decoration, EditorView, WidgetType } from "@codemirror/view";
|
|
2
|
+
import { Extension } from "@codemirror/state";
|
|
3
|
+
import { syntaxTree } from "@codemirror/language";
|
|
4
|
+
import { DecorationContext, DecorationPlugin } from "../editor/plugin";
|
|
5
|
+
import { createTheme } from "../editor";
|
|
6
|
+
import { SyntaxNode } from "@lezer/common";
|
|
7
|
+
import { tags } from "@lezer/highlight";
|
|
8
|
+
import type { MarkdownConfig, InlineParser, BlockParser, Line, BlockContext } from "@lezer/markdown";
|
|
9
|
+
import katex from "katex";
|
|
10
|
+
import { createWrapSelectionInputHandler } from "../lib";
|
|
11
|
+
// @ts-expect-error - raw import for CSS as string
|
|
12
|
+
import katexCss from "katex/dist/katex.min.css?raw";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Inject KaTeX CSS into the document head (only once)
|
|
16
|
+
*/
|
|
17
|
+
function injectKatexStyles(): void {
|
|
18
|
+
if (typeof document === "undefined") return;
|
|
19
|
+
if (document.getElementById("mardora-katex-styles")) return;
|
|
20
|
+
|
|
21
|
+
const style = document.createElement("style");
|
|
22
|
+
style.id = "mardora-katex-styles";
|
|
23
|
+
style.textContent = katexCss;
|
|
24
|
+
document.head.appendChild(style);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Inject styles when module loads
|
|
28
|
+
injectKatexStyles();
|
|
29
|
+
|
|
30
|
+
// Character codes
|
|
31
|
+
const DOLLAR = 36; // '$'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Mark decorations for math syntax elements
|
|
35
|
+
*/
|
|
36
|
+
const mathMarkDecorations = {
|
|
37
|
+
"math-block": Decoration.line({ class: "cm-mardora-math-block" }),
|
|
38
|
+
"math-inline": Decoration.mark({ class: "cm-mardora-math-inline" }),
|
|
39
|
+
"math-marker": Decoration.mark({ class: "cm-mardora-math-marker" }),
|
|
40
|
+
"math-hidden": Decoration.mark({ class: "cm-mardora-math-hidden" }),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Render LaTeX to HTML using KaTeX
|
|
45
|
+
*/
|
|
46
|
+
function renderMath(latex: string, displayMode: boolean): { html: string; error: string | null } {
|
|
47
|
+
try {
|
|
48
|
+
const html = katex.renderToString(latex, {
|
|
49
|
+
displayMode,
|
|
50
|
+
throwOnError: false,
|
|
51
|
+
errorColor: "#d73a49",
|
|
52
|
+
trust: false,
|
|
53
|
+
strict: false,
|
|
54
|
+
});
|
|
55
|
+
return { html, error: null };
|
|
56
|
+
} catch (e) {
|
|
57
|
+
const errorMsg = e instanceof Error ? e.message : "Unknown error";
|
|
58
|
+
return { html: "", error: errorMsg };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Widget to render inline math
|
|
64
|
+
*/
|
|
65
|
+
class InlineMathWidget extends WidgetType {
|
|
66
|
+
constructor(
|
|
67
|
+
readonly latex: string,
|
|
68
|
+
readonly from: number,
|
|
69
|
+
readonly to: number
|
|
70
|
+
) {
|
|
71
|
+
super();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override eq(other: InlineMathWidget): boolean {
|
|
75
|
+
return other.latex === this.latex && other.from === this.from && other.to === this.to;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
toDOM(view: EditorView) {
|
|
79
|
+
const span = document.createElement("span");
|
|
80
|
+
span.className = "cm-mardora-math-rendered cm-mardora-math-rendered-inline";
|
|
81
|
+
span.style.cursor = "pointer";
|
|
82
|
+
|
|
83
|
+
const { html, error } = renderMath(this.latex, false);
|
|
84
|
+
|
|
85
|
+
if (error) {
|
|
86
|
+
span.className += " cm-mardora-math-error";
|
|
87
|
+
span.textContent = `[Math Error: ${error}]`;
|
|
88
|
+
} else {
|
|
89
|
+
span.innerHTML = html;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Click handler to select the raw math text
|
|
93
|
+
span.addEventListener("click", (e) => {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
e.stopPropagation();
|
|
96
|
+
view.dispatch({
|
|
97
|
+
selection: { anchor: this.from, head: this.to },
|
|
98
|
+
scrollIntoView: true,
|
|
99
|
+
});
|
|
100
|
+
view.focus();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return span;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override ignoreEvent(event: Event) {
|
|
107
|
+
return event.type !== "click";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Widget to render block math (display mode)
|
|
113
|
+
*/
|
|
114
|
+
class MathBlockWidget extends WidgetType {
|
|
115
|
+
constructor(
|
|
116
|
+
readonly latex: string,
|
|
117
|
+
readonly from: number,
|
|
118
|
+
readonly to: number
|
|
119
|
+
) {
|
|
120
|
+
super();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
override eq(other: MathBlockWidget): boolean {
|
|
124
|
+
return other.latex === this.latex && other.from === this.from && other.to === this.to;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
toDOM(view: EditorView) {
|
|
128
|
+
const div = document.createElement("div");
|
|
129
|
+
div.className = "cm-mardora-math-rendered cm-mardora-math-rendered-block";
|
|
130
|
+
div.style.cursor = "pointer";
|
|
131
|
+
|
|
132
|
+
const { html, error } = renderMath(this.latex, true);
|
|
133
|
+
|
|
134
|
+
if (error) {
|
|
135
|
+
div.className += " cm-mardora-math-error";
|
|
136
|
+
div.textContent = `[Math Error: ${error}]`;
|
|
137
|
+
} else {
|
|
138
|
+
div.innerHTML = html;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Click handler to select the raw math text
|
|
142
|
+
div.addEventListener("click", (e) => {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
e.stopPropagation();
|
|
145
|
+
view.dispatch({
|
|
146
|
+
selection: { anchor: this.from, head: this.to },
|
|
147
|
+
scrollIntoView: true,
|
|
148
|
+
});
|
|
149
|
+
view.focus();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return div;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
override ignoreEvent(event: Event) {
|
|
156
|
+
return event.type !== "click";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Inline parser for inline math: $...$
|
|
162
|
+
* Does not match $$ (block math markers)
|
|
163
|
+
*/
|
|
164
|
+
const inlineMathParser: InlineParser = {
|
|
165
|
+
name: "InlineMath",
|
|
166
|
+
parse(cx, next, pos) {
|
|
167
|
+
// Check if we are at a $ character
|
|
168
|
+
if (next !== DOLLAR) return -1;
|
|
169
|
+
|
|
170
|
+
// Don't match $$ (that's block math)
|
|
171
|
+
if (cx.char(pos + 1) === DOLLAR) return -1;
|
|
172
|
+
|
|
173
|
+
// Find the closing $
|
|
174
|
+
let end = pos + 1;
|
|
175
|
+
while (end < cx.end) {
|
|
176
|
+
const char = cx.char(end);
|
|
177
|
+
if (char === DOLLAR) {
|
|
178
|
+
// Found closing $, but make sure it's not $$
|
|
179
|
+
if (cx.char(end + 1) !== DOLLAR) {
|
|
180
|
+
// Extract the math content (excluding the $ markers)
|
|
181
|
+
const content = cx.slice(pos + 1, end);
|
|
182
|
+
|
|
183
|
+
// Skip empty math
|
|
184
|
+
if (content.trim().length === 0) return -1;
|
|
185
|
+
|
|
186
|
+
// Create the element with markers
|
|
187
|
+
const openMark = cx.elt("InlineMathMark", pos, pos + 1);
|
|
188
|
+
const closeMark = cx.elt("InlineMathMark", end, end + 1);
|
|
189
|
+
const inlineMath = cx.elt("InlineMath", pos, end + 1, [openMark, closeMark]);
|
|
190
|
+
|
|
191
|
+
return cx.addElement(inlineMath);
|
|
192
|
+
}
|
|
193
|
+
// Skip $$ for block math
|
|
194
|
+
return -1;
|
|
195
|
+
}
|
|
196
|
+
// Skip escaped characters
|
|
197
|
+
if (char === 92 /* backslash */) {
|
|
198
|
+
end += 2;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
end++;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return -1;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Block parser for math blocks: $$...$$
|
|
210
|
+
*/
|
|
211
|
+
const mathBlockParser: BlockParser = {
|
|
212
|
+
name: "MathBlock",
|
|
213
|
+
parse(cx: BlockContext, line: Line) {
|
|
214
|
+
// Check if line starts with $$
|
|
215
|
+
const text = line.text;
|
|
216
|
+
const trimmed = text.slice(line.pos).trimStart();
|
|
217
|
+
|
|
218
|
+
if (!trimmed.startsWith("$$")) return false;
|
|
219
|
+
|
|
220
|
+
// Find the end of the math block
|
|
221
|
+
const startLine = cx.lineStart;
|
|
222
|
+
let endPos = -1;
|
|
223
|
+
let lastLineEnd = startLine + line.text.length;
|
|
224
|
+
|
|
225
|
+
// Move past the opening line
|
|
226
|
+
while (cx.nextLine()) {
|
|
227
|
+
const currentText = line.text;
|
|
228
|
+
lastLineEnd = cx.lineStart + currentText.length;
|
|
229
|
+
|
|
230
|
+
// Check if this line contains closing $$
|
|
231
|
+
if (currentText.trimEnd().endsWith("$$")) {
|
|
232
|
+
endPos = lastLineEnd;
|
|
233
|
+
// Move past the closing line so subsequent markdown gets parsed
|
|
234
|
+
cx.nextLine();
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (endPos === -1) {
|
|
240
|
+
// No closing found, treat as regular paragraph
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Create the math block element
|
|
245
|
+
const openMark = cx.elt("MathBlockMark", startLine, startLine + text.indexOf("$$") + 2);
|
|
246
|
+
const closeMark = cx.elt("MathBlockMark", endPos - 2, endPos);
|
|
247
|
+
cx.addElement(cx.elt("MathBlock", startLine, endPos, [openMark, closeMark]));
|
|
248
|
+
|
|
249
|
+
return true;
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* MathPlugin - Renders LaTeX math expressions using KaTeX
|
|
255
|
+
*
|
|
256
|
+
* Supports:
|
|
257
|
+
* - Inline math: $E = mc^2$
|
|
258
|
+
* - Block math (display mode):
|
|
259
|
+
* $$
|
|
260
|
+
* \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
|
|
261
|
+
* $$
|
|
262
|
+
*
|
|
263
|
+
* Behavior:
|
|
264
|
+
* - Inline math: Show rendered output when cursor outside, raw LaTeX when inside
|
|
265
|
+
* - Block math: Always show rendered output below, hide raw when cursor outside (like ImagePlugin)
|
|
266
|
+
*/
|
|
267
|
+
export class MathPlugin extends DecorationPlugin {
|
|
268
|
+
readonly name = "math";
|
|
269
|
+
readonly version = "1.0.0";
|
|
270
|
+
override decorationPriority = 25;
|
|
271
|
+
override readonly requiredNodes = ["InlineMath", "MathBlock", "InlineMathMark", "MathBlockMark"] as const;
|
|
272
|
+
|
|
273
|
+
constructor() {
|
|
274
|
+
super();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Plugin theme
|
|
279
|
+
*/
|
|
280
|
+
override get theme() {
|
|
281
|
+
return theme;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Intercepts dollar typing to wrap selected text as inline math.
|
|
286
|
+
*
|
|
287
|
+
* If user types '$' while text is selected, wraps each selected range
|
|
288
|
+
* with single dollars (selected -> $selected$).
|
|
289
|
+
*/
|
|
290
|
+
override getExtensions(): Extension[] {
|
|
291
|
+
return [createWrapSelectionInputHandler({ $: "$" })];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Return markdown parser extensions for math syntax
|
|
296
|
+
*/
|
|
297
|
+
override getMarkdownConfig(): MarkdownConfig {
|
|
298
|
+
return {
|
|
299
|
+
defineNodes: [
|
|
300
|
+
{ name: "InlineMath", style: tags.emphasis },
|
|
301
|
+
{ name: "InlineMathMark", style: tags.processingInstruction },
|
|
302
|
+
{ name: "MathBlock", block: true },
|
|
303
|
+
{ name: "MathBlockMark", style: tags.processingInstruction },
|
|
304
|
+
],
|
|
305
|
+
parseInline: [inlineMathParser],
|
|
306
|
+
parseBlock: [mathBlockParser],
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Build decorations for math expressions
|
|
312
|
+
*/
|
|
313
|
+
buildDecorations(ctx: DecorationContext): void {
|
|
314
|
+
const { view, decorations } = ctx;
|
|
315
|
+
const tree = syntaxTree(view.state);
|
|
316
|
+
|
|
317
|
+
tree.iterate({
|
|
318
|
+
enter: (node) => {
|
|
319
|
+
const { from, to, name } = node;
|
|
320
|
+
|
|
321
|
+
// Handle inline math
|
|
322
|
+
if (name === "InlineMath") {
|
|
323
|
+
const content = view.state.sliceDoc(from, to);
|
|
324
|
+
// Extract LaTeX content (remove $ markers)
|
|
325
|
+
const latex = content.slice(1, -1);
|
|
326
|
+
|
|
327
|
+
const cursorInRange = ctx.selectionOverlapsRange(from, to);
|
|
328
|
+
|
|
329
|
+
if (cursorInRange) {
|
|
330
|
+
// Show raw math with styled markers
|
|
331
|
+
decorations.push(mathMarkDecorations["math-inline"].range(from, to));
|
|
332
|
+
|
|
333
|
+
// Style the $ markers
|
|
334
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
335
|
+
if (child.name === "InlineMathMark") {
|
|
336
|
+
decorations.push(mathMarkDecorations["math-marker"].range(child.from, child.to));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
// Replace with rendered math widget
|
|
341
|
+
decorations.push(
|
|
342
|
+
Decoration.replace({
|
|
343
|
+
widget: new InlineMathWidget(latex, from, to),
|
|
344
|
+
}).range(from, to)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Handle math blocks
|
|
350
|
+
if (name === "MathBlock") {
|
|
351
|
+
const content = view.state.sliceDoc(from, to);
|
|
352
|
+
|
|
353
|
+
// Extract LaTeX content (remove $$ markers and trim)
|
|
354
|
+
const lines = content.split("\n");
|
|
355
|
+
const latex = lines
|
|
356
|
+
.slice(1, -1) // Remove first and last lines (the $$ markers)
|
|
357
|
+
.join("\n")
|
|
358
|
+
.trim();
|
|
359
|
+
|
|
360
|
+
// If the block is simple (everything on one line), handle differently
|
|
361
|
+
const singleLine = !content.includes("\n");
|
|
362
|
+
const latexContent = singleLine ? content.slice(2, -2).trim() : latex;
|
|
363
|
+
|
|
364
|
+
const nodeLineStart = view.state.doc.lineAt(from);
|
|
365
|
+
const nodeLineEnd = view.state.doc.lineAt(to);
|
|
366
|
+
const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
|
|
367
|
+
|
|
368
|
+
// Add line decoration for math block
|
|
369
|
+
decorations.push(mathMarkDecorations["math-block"].range(from));
|
|
370
|
+
|
|
371
|
+
// Always add the math block widget below the node (like image plugin)
|
|
372
|
+
decorations.push(
|
|
373
|
+
Decoration.widget({
|
|
374
|
+
widget: new MathBlockWidget(latexContent, from, to),
|
|
375
|
+
side: 1,
|
|
376
|
+
block: false,
|
|
377
|
+
}).range(to)
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
|
|
381
|
+
const line = view.state.doc.line(i);
|
|
382
|
+
decorations.push(mathMarkDecorations["math-block"].range(line.from));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Cursor in range: show raw LaTeX with styling
|
|
386
|
+
if (cursorInRange) {
|
|
387
|
+
// Style the $$ markers
|
|
388
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
389
|
+
if (child.name === "MathBlockMark") {
|
|
390
|
+
decorations.push(mathMarkDecorations["math-marker"].range(child.from, child.to));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
// Cursor out of range: hide the raw math text
|
|
395
|
+
decorations.push(mathMarkDecorations["math-hidden"].range(from, to));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Render math to HTML for preview mode
|
|
404
|
+
*/
|
|
405
|
+
override renderToHTML(
|
|
406
|
+
node: SyntaxNode,
|
|
407
|
+
_children: string,
|
|
408
|
+
ctx: { sliceDoc(from: number, to: number): string; sanitize(html: string): string }
|
|
409
|
+
): string | null {
|
|
410
|
+
if (node.name === "InlineMath") {
|
|
411
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
412
|
+
const latex = content.slice(1, -1);
|
|
413
|
+
const { html, error } = renderMath(latex, false);
|
|
414
|
+
|
|
415
|
+
if (error) {
|
|
416
|
+
return `<span class="cm-mardora-math-error">[Math Error: ${ctx.sanitize(error)}]</span>`;
|
|
417
|
+
}
|
|
418
|
+
return `<span class="cm-mardora-math-rendered cm-mardora-math-rendered-inline">${html}</span>`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (node.name === "MathBlock") {
|
|
422
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
423
|
+
const lines = content.split("\n");
|
|
424
|
+
const latex = lines.length > 1 ? lines.slice(1, -1).join("\n").trim() : content.slice(2, -2).trim();
|
|
425
|
+
const { html, error } = renderMath(latex, true);
|
|
426
|
+
|
|
427
|
+
if (error) {
|
|
428
|
+
return `<div class="cm-mardora-math-error">[Math Error: ${ctx.sanitize(error)}]</div>`;
|
|
429
|
+
}
|
|
430
|
+
return `<div class="cm-mardora-math-rendered cm-mardora-math-rendered-block">${html}</div>`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Hide math markers in preview
|
|
434
|
+
if (node.name === "InlineMathMark" || node.name === "MathBlockMark") {
|
|
435
|
+
return "";
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Theme for math styling
|
|
444
|
+
*/
|
|
445
|
+
const theme = createTheme({
|
|
446
|
+
default: {
|
|
447
|
+
".cm-mardora-math-block": {
|
|
448
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
".cm-mardora-math-block br": {
|
|
452
|
+
display: "none",
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
// Math markers ($ $$)
|
|
456
|
+
".cm-mardora-math-marker": {
|
|
457
|
+
color: "#6a737d",
|
|
458
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
// Inline math styling when editing
|
|
462
|
+
".cm-mardora-math-inline": {
|
|
463
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
464
|
+
fontSize: "0.9em",
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
// Hidden math syntax (when cursor is not in range)
|
|
468
|
+
".cm-mardora-math-hidden": {
|
|
469
|
+
display: "none",
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
// Hidden line (for multi-line blocks)
|
|
473
|
+
".cm-mardora-hidden-line": {
|
|
474
|
+
display: "none",
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
// Rendered math container (both inline and block)
|
|
478
|
+
".cm-mardora-math-rendered": {
|
|
479
|
+
fontFamily: "KaTeX_Main, 'Times New Roman', serif",
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
// Inline rendered math
|
|
483
|
+
".cm-mardora-math-rendered-inline": {
|
|
484
|
+
display: "inline",
|
|
485
|
+
verticalAlign: "baseline",
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
// Block rendered math (display mode)
|
|
489
|
+
".cm-mardora-math-rendered-block": {
|
|
490
|
+
display: "flex",
|
|
491
|
+
justifyContent: "center",
|
|
492
|
+
alignItems: "center",
|
|
493
|
+
padding: "1em 0",
|
|
494
|
+
backgroundColor: "rgba(0, 0, 0, 0.02)",
|
|
495
|
+
borderRadius: "4px",
|
|
496
|
+
overflow: "auto",
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
// Math error styling
|
|
500
|
+
".cm-mardora-math-error": {
|
|
501
|
+
display: "inline-block",
|
|
502
|
+
padding: "0.25em 0.5em",
|
|
503
|
+
backgroundColor: "rgba(255, 0, 0, 0.1)",
|
|
504
|
+
color: "#d73a49",
|
|
505
|
+
borderRadius: "4px",
|
|
506
|
+
fontSize: "0.875em",
|
|
507
|
+
fontStyle: "italic",
|
|
508
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
dark: {
|
|
513
|
+
".cm-mardora-math-marker": {
|
|
514
|
+
color: "#8b949e",
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
".cm-mardora-math-rendered-block": {
|
|
518
|
+
backgroundColor: "rgba(255, 255, 255, 0.02)",
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
".cm-mardora-math-error": {
|
|
522
|
+
backgroundColor: "rgba(255, 0, 0, 0.15)",
|
|
523
|
+
color: "#f85149",
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
});
|