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,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
+ }