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,83 @@
|
|
|
1
|
+
import { createMardoraIcon } from "../icons";
|
|
2
|
+
import type { MardoraTocItem } from "./types";
|
|
3
|
+
|
|
4
|
+
export interface TocPanelRenderState {
|
|
5
|
+
expanded: boolean;
|
|
6
|
+
width: number;
|
|
7
|
+
items: MardoraTocItem[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface TocPanelCallbacks {
|
|
11
|
+
onSelect(item: MardoraTocItem): void;
|
|
12
|
+
onToggle(): void;
|
|
13
|
+
onResizeStart(event: MouseEvent): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function appendIcon(parent: HTMLElement, name: string): void {
|
|
17
|
+
const icon = createMardoraIcon(name);
|
|
18
|
+
if (icon) parent.appendChild(icon);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createExpandedToggle(callbacks: TocPanelCallbacks): HTMLButtonElement {
|
|
22
|
+
const toggle = document.createElement("button");
|
|
23
|
+
toggle.type = "button";
|
|
24
|
+
toggle.className = "cm-mardora-toc-toggle";
|
|
25
|
+
toggle.setAttribute("aria-label", "Toggle table of contents");
|
|
26
|
+
toggle.textContent = "‹";
|
|
27
|
+
toggle.addEventListener("click", callbacks.onToggle);
|
|
28
|
+
return toggle;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createCollapsed(callbacks: TocPanelCallbacks): HTMLElement {
|
|
32
|
+
const button = document.createElement("button");
|
|
33
|
+
button.type = "button";
|
|
34
|
+
button.className = "cm-mardora-toc-collapsed";
|
|
35
|
+
button.setAttribute("aria-label", "Open table of contents");
|
|
36
|
+
appendIcon(button, "table-of-contents");
|
|
37
|
+
button.addEventListener("click", callbacks.onToggle);
|
|
38
|
+
return button;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createItem(item: MardoraTocItem, callbacks: TocPanelCallbacks): HTMLButtonElement {
|
|
42
|
+
const button = document.createElement("button");
|
|
43
|
+
button.type = "button";
|
|
44
|
+
button.className = item.active ? "cm-mardora-toc-item cm-mardora-toc-item-active" : "cm-mardora-toc-item";
|
|
45
|
+
button.dataset.mardoraTocId = item.id;
|
|
46
|
+
button.dataset.mardoraTocLevel = String(item.level);
|
|
47
|
+
button.title = item.text;
|
|
48
|
+
button.textContent = item.text;
|
|
49
|
+
button.addEventListener("click", () => callbacks.onSelect(item));
|
|
50
|
+
return button;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createTocPanelElement(state: TocPanelRenderState, callbacks: TocPanelCallbacks): HTMLElement {
|
|
54
|
+
const root = document.createElement("aside");
|
|
55
|
+
root.className = "cm-mardora-toc";
|
|
56
|
+
root.dataset.mardoraTocExpanded = String(state.expanded);
|
|
57
|
+
root.style.setProperty("--mardora-toc-width", `${state.width}px`);
|
|
58
|
+
|
|
59
|
+
const resize = document.createElement("div");
|
|
60
|
+
resize.className = "cm-mardora-toc-resize";
|
|
61
|
+
resize.addEventListener("mousedown", callbacks.onResizeStart);
|
|
62
|
+
root.appendChild(resize);
|
|
63
|
+
|
|
64
|
+
if (!state.expanded) {
|
|
65
|
+
root.appendChild(createCollapsed(callbacks));
|
|
66
|
+
return root;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
root.appendChild(createExpandedToggle(callbacks));
|
|
70
|
+
|
|
71
|
+
const list = document.createElement("nav");
|
|
72
|
+
list.className = "cm-mardora-toc-list";
|
|
73
|
+
if (state.items.length === 0) {
|
|
74
|
+
const empty = document.createElement("div");
|
|
75
|
+
empty.className = "cm-mardora-toc-empty";
|
|
76
|
+
empty.textContent = "暂无目录";
|
|
77
|
+
list.appendChild(empty);
|
|
78
|
+
} else {
|
|
79
|
+
for (const item of state.items) list.appendChild(createItem(item, callbacks));
|
|
80
|
+
}
|
|
81
|
+
root.appendChild(list);
|
|
82
|
+
return root;
|
|
83
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { MardoraTocConfig, ResolvedMardoraTocConfig } from "./types";
|
|
2
|
+
|
|
3
|
+
const defaultMinWidth = 180;
|
|
4
|
+
const defaultMaxWidth = 360;
|
|
5
|
+
const defaultWidth = 240;
|
|
6
|
+
|
|
7
|
+
export function clampTocWidth(width: number, config: Pick<ResolvedMardoraTocConfig, "minWidth" | "maxWidth">): number {
|
|
8
|
+
return Math.min(Math.max(width, config.minWidth), config.maxWidth);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveTocConfig(config: MardoraTocConfig = {}): ResolvedMardoraTocConfig {
|
|
12
|
+
const minWidth = Math.max(120, config.minWidth ?? defaultMinWidth);
|
|
13
|
+
const maxWidth = Math.max(minWidth, config.maxWidth ?? defaultMaxWidth);
|
|
14
|
+
const requestedMinLevel = config.minLevel ?? 2;
|
|
15
|
+
const requestedMaxLevel = config.maxLevel ?? 6;
|
|
16
|
+
const minLevel = requestedMinLevel <= requestedMaxLevel ? requestedMinLevel : requestedMaxLevel;
|
|
17
|
+
const maxLevel = requestedMaxLevel >= requestedMinLevel ? requestedMaxLevel : requestedMinLevel;
|
|
18
|
+
const resolved: ResolvedMardoraTocConfig = {
|
|
19
|
+
enabled: config.enabled !== false,
|
|
20
|
+
minLevel,
|
|
21
|
+
maxLevel,
|
|
22
|
+
defaultExpanded: config.defaultExpanded !== false,
|
|
23
|
+
defaultWidth,
|
|
24
|
+
minWidth,
|
|
25
|
+
maxWidth,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (config.onTocChange) resolved.onTocChange = config.onTocChange;
|
|
29
|
+
if (config.storageKey) resolved.storageKey = config.storageKey;
|
|
30
|
+
resolved.defaultWidth = clampTocWidth(config.defaultWidth ?? defaultWidth, resolved);
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeHeadingText(value: string): string {
|
|
35
|
+
return value
|
|
36
|
+
.trim()
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/[^\p{L}\p{N}]+/gu, "-")
|
|
39
|
+
.replace(/^-+|-+$/g, "");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createTocSlugger(): (text: string) => string {
|
|
43
|
+
const seen = new Map<string, number>();
|
|
44
|
+
return (text: string) => {
|
|
45
|
+
const base = normalizeHeadingText(text) || "heading";
|
|
46
|
+
const count = (seen.get(base) ?? 0) + 1;
|
|
47
|
+
seen.set(base, count);
|
|
48
|
+
return count === 1 ? base : `${base}-${count}`;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TocPanelState, TocStorageAdapter } from "./types";
|
|
2
|
+
|
|
3
|
+
function defaultStorage(): TocStorageAdapter | null {
|
|
4
|
+
if (typeof window === "undefined") return null;
|
|
5
|
+
return window.localStorage;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function readTocPanelState(
|
|
9
|
+
storageKey?: string,
|
|
10
|
+
storage: TocStorageAdapter | null = defaultStorage()
|
|
11
|
+
): TocPanelState | null {
|
|
12
|
+
if (!storageKey || !storage) return null;
|
|
13
|
+
try {
|
|
14
|
+
const raw = storage.getItem(storageKey);
|
|
15
|
+
if (!raw) return null;
|
|
16
|
+
const parsed = JSON.parse(raw) as Partial<TocPanelState>;
|
|
17
|
+
if (typeof parsed.expanded !== "boolean" || typeof parsed.width !== "number") return null;
|
|
18
|
+
return { expanded: parsed.expanded, width: parsed.width };
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function writeTocPanelState(
|
|
25
|
+
storageKey: string | undefined,
|
|
26
|
+
state: TocPanelState,
|
|
27
|
+
storage: TocStorageAdapter | null = defaultStorage()
|
|
28
|
+
): void {
|
|
29
|
+
if (!storageKey || !storage) return;
|
|
30
|
+
try {
|
|
31
|
+
storage.setItem(storageKey, JSON.stringify({ expanded: state.expanded, width: state.width }));
|
|
32
|
+
} catch {
|
|
33
|
+
// Storage can fail in private mode or restricted host environments.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { EditorView } from "@codemirror/view";
|
|
2
|
+
|
|
3
|
+
export const tocTheme = EditorView.baseTheme({
|
|
4
|
+
".cm-mardora": {
|
|
5
|
+
position: "relative",
|
|
6
|
+
},
|
|
7
|
+
".cm-mardora .cm-scroller": {
|
|
8
|
+
boxSizing: "border-box",
|
|
9
|
+
paddingRight: "calc(var(--mardora-toc-layout-width, 0px) + var(--mardora-toc-scrollbar-gutter, 0px))",
|
|
10
|
+
scrollbarGutter: "stable",
|
|
11
|
+
},
|
|
12
|
+
".cm-mardora .cm-scroller::-webkit-scrollbar-track": {
|
|
13
|
+
background: "transparent",
|
|
14
|
+
},
|
|
15
|
+
".cm-mardora-toc": {
|
|
16
|
+
position: "absolute",
|
|
17
|
+
top: "0",
|
|
18
|
+
right: "var(--mardora-toc-scrollbar-gutter, 0px)",
|
|
19
|
+
bottom: "0",
|
|
20
|
+
zIndex: "8",
|
|
21
|
+
width: "var(--mardora-toc-width, 240px)",
|
|
22
|
+
minWidth: "0",
|
|
23
|
+
borderLeft: "1px solid var(--mardora-toc-border, #ececef)",
|
|
24
|
+
background: "var(--mardora-toc-bg, transparent)",
|
|
25
|
+
color: "var(--mardora-toc-fg, #27272a)",
|
|
26
|
+
userSelect: "none",
|
|
27
|
+
},
|
|
28
|
+
".cm-mardora-toc[data-mardora-toc-expanded='false']": {
|
|
29
|
+
width: "42px",
|
|
30
|
+
},
|
|
31
|
+
".cm-mardora-toc-resize": {
|
|
32
|
+
position: "absolute",
|
|
33
|
+
top: "0",
|
|
34
|
+
bottom: "0",
|
|
35
|
+
left: "-4px",
|
|
36
|
+
zIndex: "2",
|
|
37
|
+
width: "8px",
|
|
38
|
+
cursor: "col-resize",
|
|
39
|
+
},
|
|
40
|
+
".cm-mardora-toc-resize::after": {
|
|
41
|
+
position: "absolute",
|
|
42
|
+
top: "50%",
|
|
43
|
+
left: "3px",
|
|
44
|
+
width: "2px",
|
|
45
|
+
height: "64px",
|
|
46
|
+
borderRadius: "999px",
|
|
47
|
+
background: "var(--mardora-toc-handle, #d4d4d8)",
|
|
48
|
+
opacity: "0",
|
|
49
|
+
transform: "translateY(-50%)",
|
|
50
|
+
transition: "opacity 120ms ease",
|
|
51
|
+
content: "''",
|
|
52
|
+
},
|
|
53
|
+
".cm-mardora-toc:hover .cm-mardora-toc-resize::after, .cm-mardora-toc-resizing .cm-mardora-toc-resize::after": {
|
|
54
|
+
opacity: "1",
|
|
55
|
+
},
|
|
56
|
+
".cm-mardora-toc-collapsed svg": {
|
|
57
|
+
width: "16px",
|
|
58
|
+
height: "16px",
|
|
59
|
+
},
|
|
60
|
+
".cm-mardora-toc-toggle, .cm-mardora-toc-collapsed, .cm-mardora-toc-item": {
|
|
61
|
+
border: "0",
|
|
62
|
+
background: "transparent",
|
|
63
|
+
color: "inherit",
|
|
64
|
+
cursor: "default",
|
|
65
|
+
font: "inherit",
|
|
66
|
+
},
|
|
67
|
+
".cm-mardora-toc-toggle": {
|
|
68
|
+
position: "absolute",
|
|
69
|
+
top: "8px",
|
|
70
|
+
right: "8px",
|
|
71
|
+
zIndex: "1",
|
|
72
|
+
width: "28px",
|
|
73
|
+
height: "28px",
|
|
74
|
+
borderRadius: "6px",
|
|
75
|
+
color: "var(--mardora-toc-muted, #71717a)",
|
|
76
|
+
opacity: "0.55",
|
|
77
|
+
transition: "background 120ms ease, color 120ms ease, opacity 120ms ease",
|
|
78
|
+
},
|
|
79
|
+
".cm-mardora-toc-toggle:hover": {
|
|
80
|
+
color: "var(--mardora-toc-active, #18181b)",
|
|
81
|
+
opacity: "1",
|
|
82
|
+
},
|
|
83
|
+
".cm-mardora-toc-toggle:hover, .cm-mardora-toc-collapsed:hover, .cm-mardora-toc-item:hover": {
|
|
84
|
+
background: "var(--mardora-toc-hover, #f4f4f5)",
|
|
85
|
+
},
|
|
86
|
+
".cm-mardora-toc-collapsed": {
|
|
87
|
+
display: "flex",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
justifyContent: "center",
|
|
90
|
+
width: "100%",
|
|
91
|
+
height: "100%",
|
|
92
|
+
color: "var(--mardora-toc-muted, #71717a)",
|
|
93
|
+
},
|
|
94
|
+
".cm-mardora-toc-list": {
|
|
95
|
+
overflow: "auto",
|
|
96
|
+
height: "100%",
|
|
97
|
+
padding: "12px 10px 16px 18px",
|
|
98
|
+
scrollbarWidth: "thin",
|
|
99
|
+
},
|
|
100
|
+
".cm-mardora-toc-list::-webkit-scrollbar": {
|
|
101
|
+
width: "8px",
|
|
102
|
+
},
|
|
103
|
+
".cm-mardora-toc-list::-webkit-scrollbar-track": {
|
|
104
|
+
background: "transparent",
|
|
105
|
+
},
|
|
106
|
+
".cm-mardora-toc-list::-webkit-scrollbar-thumb": {
|
|
107
|
+
borderRadius: "999px",
|
|
108
|
+
background: "var(--mardora-toc-scrollbar, #c7c7cc)",
|
|
109
|
+
},
|
|
110
|
+
".cm-mardora-toc-item": {
|
|
111
|
+
display: "block",
|
|
112
|
+
width: "100%",
|
|
113
|
+
overflow: "hidden",
|
|
114
|
+
minHeight: "30px",
|
|
115
|
+
padding: "0 8px",
|
|
116
|
+
borderLeft: "2px solid transparent",
|
|
117
|
+
borderRadius: "6px",
|
|
118
|
+
color: "var(--mardora-toc-muted, #71717a)",
|
|
119
|
+
lineHeight: "30px",
|
|
120
|
+
textAlign: "left",
|
|
121
|
+
textOverflow: "ellipsis",
|
|
122
|
+
whiteSpace: "nowrap",
|
|
123
|
+
transition: "background 120ms ease, color 120ms ease",
|
|
124
|
+
},
|
|
125
|
+
".cm-mardora-toc-item[data-mardora-toc-level='3']": { paddingLeft: "18px" },
|
|
126
|
+
".cm-mardora-toc-item[data-mardora-toc-level='4']": { paddingLeft: "28px" },
|
|
127
|
+
".cm-mardora-toc-item[data-mardora-toc-level='5']": { paddingLeft: "38px" },
|
|
128
|
+
".cm-mardora-toc-item[data-mardora-toc-level='6']": { paddingLeft: "48px" },
|
|
129
|
+
".cm-mardora-toc-item-active": {
|
|
130
|
+
borderLeftColor: "var(--mardora-toc-active, #18181b)",
|
|
131
|
+
background: "var(--mardora-toc-hover, #f4f4f5)",
|
|
132
|
+
color: "var(--mardora-toc-active, #18181b)",
|
|
133
|
+
fontWeight: "600",
|
|
134
|
+
},
|
|
135
|
+
".cm-mardora-toc-empty": {
|
|
136
|
+
padding: "12px 8px",
|
|
137
|
+
color: "var(--mardora-toc-muted, #71717a)",
|
|
138
|
+
fontSize: "13px",
|
|
139
|
+
},
|
|
140
|
+
".cm-mardora-toc-resizing": {
|
|
141
|
+
userSelect: "none",
|
|
142
|
+
},
|
|
143
|
+
"&dark .cm-mardora-toc": {
|
|
144
|
+
"--mardora-toc-bg": "#18181b",
|
|
145
|
+
"--mardora-toc-fg": "#f4f4f5",
|
|
146
|
+
"--mardora-toc-border": "#3f3f46",
|
|
147
|
+
"--mardora-toc-handle": "#52525b",
|
|
148
|
+
"--mardora-toc-hover": "#27272a",
|
|
149
|
+
"--mardora-toc-muted": "#a1a1aa",
|
|
150
|
+
"--mardora-toc-active": "#f4f4f5",
|
|
151
|
+
"--mardora-toc-scrollbar": "#52525b",
|
|
152
|
+
},
|
|
153
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type MardoraTocLevel = 2 | 3 | 4 | 5 | 6;
|
|
2
|
+
|
|
3
|
+
export interface MardoraTocItem {
|
|
4
|
+
id: string;
|
|
5
|
+
level: MardoraTocLevel;
|
|
6
|
+
text: string;
|
|
7
|
+
from?: number;
|
|
8
|
+
to?: number;
|
|
9
|
+
active: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface MardoraTocConfig {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
onTocChange?: (items: MardoraTocItem[]) => void;
|
|
15
|
+
minLevel?: MardoraTocLevel;
|
|
16
|
+
maxLevel?: MardoraTocLevel;
|
|
17
|
+
defaultExpanded?: boolean;
|
|
18
|
+
defaultWidth?: number;
|
|
19
|
+
minWidth?: number;
|
|
20
|
+
maxWidth?: number;
|
|
21
|
+
storageKey?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ResolvedMardoraTocConfig {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
onTocChange?: (items: MardoraTocItem[]) => void;
|
|
27
|
+
minLevel: MardoraTocLevel;
|
|
28
|
+
maxLevel: MardoraTocLevel;
|
|
29
|
+
defaultExpanded: boolean;
|
|
30
|
+
defaultWidth: number;
|
|
31
|
+
minWidth: number;
|
|
32
|
+
maxWidth: number;
|
|
33
|
+
storageKey?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface TocPanelState {
|
|
37
|
+
expanded: boolean;
|
|
38
|
+
width: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TocStorageAdapter {
|
|
42
|
+
getItem(key: string): string | null;
|
|
43
|
+
setItem(key: string, value: string): unknown;
|
|
44
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { EditorView } from "@codemirror/view";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base theme for mardora styling
|
|
5
|
+
* Note: Layout styles are scoped under .cm-mardora which is added by the view plugin
|
|
6
|
+
*/
|
|
7
|
+
export const mardoraBaseTheme = EditorView.theme({
|
|
8
|
+
// Container styles - only apply when view plugin is enabled
|
|
9
|
+
"&.cm-mardora": {
|
|
10
|
+
fontSize: "16px",
|
|
11
|
+
lineHeight: "1.6",
|
|
12
|
+
minHeight: "100%",
|
|
13
|
+
backgroundColor: "transparent !important",
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"&.cm-mardora.cm-focused": {
|
|
17
|
+
outline: "none",
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
"&.cm-mardora .cm-scroller": {
|
|
21
|
+
minHeight: "100%",
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
"&.cm-mardora .cm-content": {
|
|
25
|
+
width: "100%",
|
|
26
|
+
maxWidth: "48rem",
|
|
27
|
+
padding: "0 0.5rem",
|
|
28
|
+
margin: "0 auto",
|
|
29
|
+
fontFamily: "var(--font-sans, sans-serif)",
|
|
30
|
+
fontSize: "16px",
|
|
31
|
+
lineHeight: "1.6",
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
"&.cm-mardora .cm-content .cm-line": {
|
|
35
|
+
paddingInline: 0,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
"&.cm-mardora .cm-content .cm-widgetBuffer": {
|
|
39
|
+
display: "none !important",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
|
44
|
+
import { tags as t } from "@lezer/highlight";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reset syntax highlighting for markdown elements
|
|
48
|
+
* Used to disable theme colors for markdown syntax
|
|
49
|
+
*/
|
|
50
|
+
const markdownResetStyle = HighlightStyle.define([
|
|
51
|
+
{
|
|
52
|
+
tag: [
|
|
53
|
+
t.heading,
|
|
54
|
+
t.strong,
|
|
55
|
+
t.emphasis,
|
|
56
|
+
t.strikethrough,
|
|
57
|
+
t.link,
|
|
58
|
+
t.url,
|
|
59
|
+
t.quote,
|
|
60
|
+
t.list,
|
|
61
|
+
t.meta,
|
|
62
|
+
t.contentSeparator,
|
|
63
|
+
t.labelName,
|
|
64
|
+
],
|
|
65
|
+
color: "inherit",
|
|
66
|
+
fontWeight: "inherit",
|
|
67
|
+
fontStyle: "inherit",
|
|
68
|
+
textDecoration: "none",
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
export const markdownResetExtension = syntaxHighlighting(markdownResetStyle, { fallback: false });
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { EditorView } from "@codemirror/view";
|
|
2
|
+
import { StyleSpec } from "style-mod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Deep merge two objects
|
|
6
|
+
* @param a - First object
|
|
7
|
+
* @param b - Second object
|
|
8
|
+
* @returns Merged object
|
|
9
|
+
*/
|
|
10
|
+
export function deepMerge<T>(a: T, b?: T): T {
|
|
11
|
+
const result = { ...a };
|
|
12
|
+
|
|
13
|
+
if (!b) {
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (const key in b as T) {
|
|
18
|
+
if (b[key] && typeof b[key] === "object" && !Array.isArray(b[key]) && typeof a[key] === "object") {
|
|
19
|
+
result[key] = deepMerge(a[key], b[key]);
|
|
20
|
+
} else {
|
|
21
|
+
result[key] = b[key];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Theme style
|
|
30
|
+
*/
|
|
31
|
+
export type ThemeStyle = {
|
|
32
|
+
[selector: string]: StyleSpec;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Theme Enum
|
|
37
|
+
*/
|
|
38
|
+
export enum ThemeEnum {
|
|
39
|
+
DARK = "dark",
|
|
40
|
+
LIGHT = "light",
|
|
41
|
+
AUTO = "auto",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Function to create the themes
|
|
46
|
+
*
|
|
47
|
+
* @param defaultTheme - Default theme -- Always applied
|
|
48
|
+
* @param darkTheme - Dark theme -- Applied when theme is "dark" or "auto" and system is dark
|
|
49
|
+
* @param lightTheme - Light theme -- Applied when theme is "light" or "auto" and system is light
|
|
50
|
+
* @returns Theme function
|
|
51
|
+
*/
|
|
52
|
+
export function createTheme({
|
|
53
|
+
default: defaultTheme,
|
|
54
|
+
dark: darkTheme,
|
|
55
|
+
light: lightTheme,
|
|
56
|
+
}: {
|
|
57
|
+
default: ThemeStyle;
|
|
58
|
+
dark?: ThemeStyle;
|
|
59
|
+
light?: ThemeStyle;
|
|
60
|
+
}): (theme: ThemeEnum) => ThemeStyle {
|
|
61
|
+
return (theme: ThemeEnum) => {
|
|
62
|
+
defaultTheme = flattenThemeStyles(defaultTheme);
|
|
63
|
+
darkTheme = flattenThemeStyles(darkTheme || {});
|
|
64
|
+
lightTheme = flattenThemeStyles(lightTheme || {});
|
|
65
|
+
|
|
66
|
+
let style: ThemeStyle = defaultTheme;
|
|
67
|
+
|
|
68
|
+
if (theme === ThemeEnum.DARK) {
|
|
69
|
+
style = deepMerge(style, darkTheme);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (theme === ThemeEnum.LIGHT) {
|
|
73
|
+
style = deepMerge(style, lightTheme);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return style;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function flattenThemeStyles(themeStyles: ThemeStyle, parentSelector?: string): ThemeStyle {
|
|
81
|
+
const flattened: ThemeStyle = {};
|
|
82
|
+
|
|
83
|
+
for (const [selectors, styles] of Object.entries(themeStyles)) {
|
|
84
|
+
for (const selector of selectors.split(",")) {
|
|
85
|
+
if (typeof styles === "object" && !Array.isArray(styles)) {
|
|
86
|
+
// Flatten nested styles
|
|
87
|
+
const fullSelector = fixSelector(parentSelector ? `${parentSelector} ${selector}` : selector);
|
|
88
|
+
const nestedStyles = flattenThemeStyles(styles as ThemeStyle, fullSelector);
|
|
89
|
+
Object.assign(flattened, nestedStyles);
|
|
90
|
+
} else {
|
|
91
|
+
// Add styles to the flattened object
|
|
92
|
+
if (parentSelector) {
|
|
93
|
+
flattened[parentSelector] = { ...flattened[parentSelector], [selector]: styles };
|
|
94
|
+
} else {
|
|
95
|
+
flattened[selector] = styles as StyleSpec;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return flattened;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function fixSelector(selector: string): string {
|
|
105
|
+
// Replace all occurrences of "&" with the parent selector
|
|
106
|
+
return selector.replace(/\s&/g, "");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if cursor is within the given range
|
|
111
|
+
*/
|
|
112
|
+
export function cursorInRange(view: EditorView, from: number, to: number): boolean {
|
|
113
|
+
const selection = view.state.selection.main;
|
|
114
|
+
return selection.from <= to && selection.to >= from;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if any selection overlaps with the given range
|
|
119
|
+
*/
|
|
120
|
+
export function selectionOverlapsRange(view: EditorView, from: number, to: number): boolean {
|
|
121
|
+
for (const range of view.state.selection.ranges) {
|
|
122
|
+
if (range.from <= to && range.to >= from) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Toggle markdown style on selection or insert markers at cursor
|
|
131
|
+
* @param marker - The markdown marker (e.g., "**" for bold, "*" for italic)
|
|
132
|
+
* @returns Command function for EditorView
|
|
133
|
+
*/
|
|
134
|
+
export function toggleMarkdownStyle(marker: string): (view: EditorView) => boolean {
|
|
135
|
+
return (view: EditorView) => {
|
|
136
|
+
const { state } = view;
|
|
137
|
+
const { from, to, empty } = state.selection.main;
|
|
138
|
+
|
|
139
|
+
// Get selected text
|
|
140
|
+
const selectedText = state.sliceDoc(from, to);
|
|
141
|
+
|
|
142
|
+
// Check if already wrapped with markers
|
|
143
|
+
const markerLen = marker.length;
|
|
144
|
+
const beforeFrom = Math.max(0, from - markerLen);
|
|
145
|
+
const afterTo = Math.min(state.doc.length, to + markerLen);
|
|
146
|
+
const textBefore = state.sliceDoc(beforeFrom, from);
|
|
147
|
+
const textAfter = state.sliceDoc(to, afterTo);
|
|
148
|
+
|
|
149
|
+
const isWrapped = textBefore === marker && textAfter === marker;
|
|
150
|
+
|
|
151
|
+
if (isWrapped) {
|
|
152
|
+
// Remove markers
|
|
153
|
+
view.dispatch({
|
|
154
|
+
changes: [
|
|
155
|
+
{ from: beforeFrom, to: from, insert: "" },
|
|
156
|
+
{ from: to, to: afterTo, insert: "" },
|
|
157
|
+
],
|
|
158
|
+
selection: { anchor: beforeFrom, head: beforeFrom + selectedText.length },
|
|
159
|
+
});
|
|
160
|
+
} else if (empty) {
|
|
161
|
+
// No selection - insert markers and place cursor between them
|
|
162
|
+
view.dispatch({
|
|
163
|
+
changes: { from, to, insert: marker + marker },
|
|
164
|
+
selection: { anchor: from + markerLen },
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
// Wrap selection with markers
|
|
168
|
+
view.dispatch({
|
|
169
|
+
changes: { from, to, insert: marker + selectedText + marker },
|
|
170
|
+
selection: { anchor: from + markerLen, head: to + markerLen },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return true;
|
|
175
|
+
};
|
|
176
|
+
}
|