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,140 @@
|
|
|
1
|
+
import { Decoration, WidgetType } from "@codemirror/view";
|
|
2
|
+
import { syntaxTree } from "@codemirror/language";
|
|
3
|
+
import { DecorationContext, DecorationPlugin } from "../editor/plugin";
|
|
4
|
+
import { createTheme } from "../editor";
|
|
5
|
+
import { SyntaxNode } from "@lezer/common";
|
|
6
|
+
import * as emoji from "node-emoji";
|
|
7
|
+
|
|
8
|
+
function shortcodeToEmoji(raw: string): string | null {
|
|
9
|
+
const rendered = emoji.emojify(raw);
|
|
10
|
+
return rendered !== raw ? rendered : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class EmojiWidget extends WidgetType {
|
|
14
|
+
constructor(readonly rendered: string) {
|
|
15
|
+
super();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override eq(other: EmojiWidget): boolean {
|
|
19
|
+
return other.rendered === this.rendered;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toDOM() {
|
|
23
|
+
const span = document.createElement("span");
|
|
24
|
+
span.className = "cm-mardora-emoji";
|
|
25
|
+
span.textContent = this.rendered;
|
|
26
|
+
return span;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override ignoreEvent(): boolean {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Mark decorations for emoji syntax elements
|
|
36
|
+
*/
|
|
37
|
+
const emojiMarkDecorations = {
|
|
38
|
+
"emoji-source": Decoration.mark({ class: "cm-mardora-emoji-source" }),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* EmojiPlugin - Decorates markdown emojis
|
|
43
|
+
*
|
|
44
|
+
* Parses and decorates emoji shortcodes like :smile:
|
|
45
|
+
* - Converts valid shortcodes to Unicode emoji when cursor is outside
|
|
46
|
+
* - Keeps raw shortcode visible while editing (cursor inside token)
|
|
47
|
+
*/
|
|
48
|
+
export class EmojiPlugin extends DecorationPlugin {
|
|
49
|
+
readonly name = "emoji";
|
|
50
|
+
readonly version = "1.0.0";
|
|
51
|
+
override decorationPriority = 20;
|
|
52
|
+
override readonly requiredNodes = ["Emoji", "EmojiMark"] as const;
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
super();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Plugin theme
|
|
60
|
+
*/
|
|
61
|
+
override get theme() {
|
|
62
|
+
return theme;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build emoji decorations by iterating the syntax tree
|
|
67
|
+
*/
|
|
68
|
+
buildDecorations(ctx: DecorationContext): void {
|
|
69
|
+
const { view, decorations } = ctx;
|
|
70
|
+
const tree = syntaxTree(view.state);
|
|
71
|
+
|
|
72
|
+
tree.iterate({
|
|
73
|
+
enter: (node) => {
|
|
74
|
+
const { from, to, name } = node;
|
|
75
|
+
|
|
76
|
+
if (name !== "Emoji") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const raw = view.state.sliceDoc(from, to);
|
|
81
|
+
const rendered = shortcodeToEmoji(raw);
|
|
82
|
+
if (!rendered) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const cursorInNode = ctx.selectionOverlapsRange(from, to);
|
|
87
|
+
if (cursorInNode) {
|
|
88
|
+
decorations.push(emojiMarkDecorations["emoji-source"].range(from, to));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
decorations.push(
|
|
93
|
+
Decoration.replace({
|
|
94
|
+
widget: new EmojiWidget(rendered),
|
|
95
|
+
}).range(from, to)
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override renderToHTML(
|
|
102
|
+
node: SyntaxNode,
|
|
103
|
+
children: string,
|
|
104
|
+
ctx: {
|
|
105
|
+
sliceDoc(from: number, to: number): string;
|
|
106
|
+
sanitize(html: string): string;
|
|
107
|
+
syntaxHighlighters?: readonly import("@lezer/highlight").Highlighter[];
|
|
108
|
+
}
|
|
109
|
+
): string | null {
|
|
110
|
+
if (node.name === "EmojiMark") {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (node.name !== "Emoji") {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const raw = ctx.sliceDoc(node.from, node.to);
|
|
119
|
+
const rendered = shortcodeToEmoji(raw);
|
|
120
|
+
if (!rendered) {
|
|
121
|
+
return `<span class="cm-mardora-emoji-source">${children}</span>`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return `<span class="cm-mardora-emoji">${rendered}</span>`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const theme = createTheme({
|
|
129
|
+
default: {
|
|
130
|
+
".cm-mardora-emoji": {
|
|
131
|
+
fontFamily: '"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", sans-serif',
|
|
132
|
+
fontVariantEmoji: "emoji",
|
|
133
|
+
lineHeight: "1.2",
|
|
134
|
+
},
|
|
135
|
+
".cm-mardora-emoji-source": {
|
|
136
|
+
fontFamily: "inherit",
|
|
137
|
+
lineHeight: "inherit",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Decoration } from "@codemirror/view";
|
|
2
|
+
import { syntaxTree } from "@codemirror/language";
|
|
3
|
+
import { DecorationContext, DecorationPlugin } from "../editor/plugin";
|
|
4
|
+
import { createTheme } from "../editor";
|
|
5
|
+
import { SyntaxNode } from "@lezer/common";
|
|
6
|
+
import type { PreviewContext } from "../preview/types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Node types for ATX headings in markdown
|
|
10
|
+
*/
|
|
11
|
+
const HEADING_TYPES = ["ATXHeading1", "ATXHeading2", "ATXHeading3", "ATXHeading4", "ATXHeading5", "ATXHeading6"];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Mark decorations for heading content
|
|
15
|
+
*/
|
|
16
|
+
const headingMarkDecorations = {
|
|
17
|
+
"heading-1": Decoration.mark({ class: "cm-mardora-h1" }),
|
|
18
|
+
"heading-2": Decoration.mark({ class: "cm-mardora-h2" }),
|
|
19
|
+
"heading-3": Decoration.mark({ class: "cm-mardora-h3" }),
|
|
20
|
+
"heading-4": Decoration.mark({ class: "cm-mardora-h4" }),
|
|
21
|
+
"heading-5": Decoration.mark({ class: "cm-mardora-h5" }),
|
|
22
|
+
"heading-6": Decoration.mark({ class: "cm-mardora-h6" }),
|
|
23
|
+
"header-mark-class": Decoration.mark({ class: "cm-mardora-header-mark" }),
|
|
24
|
+
"heading-mark": Decoration.replace({}),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Line decorations for heading lines
|
|
29
|
+
*/
|
|
30
|
+
const headingLineDecorations = {
|
|
31
|
+
"heading-1": Decoration.line({ class: "cm-mardora-line-h1" }),
|
|
32
|
+
"heading-2": Decoration.line({ class: "cm-mardora-line-h2" }),
|
|
33
|
+
"heading-3": Decoration.line({ class: "cm-mardora-line-h3" }),
|
|
34
|
+
"heading-4": Decoration.line({ class: "cm-mardora-line-h4" }),
|
|
35
|
+
"heading-5": Decoration.line({ class: "cm-mardora-line-h5" }),
|
|
36
|
+
"heading-6": Decoration.line({ class: "cm-mardora-line-h6" }),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* HeadingPlugin - Decorates markdown headings
|
|
41
|
+
*
|
|
42
|
+
* Adds visual styling to ATX headings (# through ######)
|
|
43
|
+
* - Line decorations for the entire heading line
|
|
44
|
+
* - Mark decorations for heading content
|
|
45
|
+
* - Hides # markers when cursor is not in the heading
|
|
46
|
+
*/
|
|
47
|
+
export class HeadingPlugin extends DecorationPlugin {
|
|
48
|
+
readonly name = "heading";
|
|
49
|
+
readonly version = "1.0.0";
|
|
50
|
+
override decorationPriority = 10;
|
|
51
|
+
override readonly requiredNodes = [
|
|
52
|
+
"ATXHeading1",
|
|
53
|
+
"ATXHeading2",
|
|
54
|
+
"ATXHeading3",
|
|
55
|
+
"ATXHeading4",
|
|
56
|
+
"ATXHeading5",
|
|
57
|
+
"ATXHeading6",
|
|
58
|
+
"HeaderMark",
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Constructor - calls super constructor
|
|
63
|
+
*/
|
|
64
|
+
constructor() {
|
|
65
|
+
super();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Plugin theme
|
|
70
|
+
*/
|
|
71
|
+
override get theme() {
|
|
72
|
+
return theme;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build heading decorations by iterating the syntax tree
|
|
77
|
+
*/
|
|
78
|
+
buildDecorations(ctx: DecorationContext): void {
|
|
79
|
+
const { view, decorations } = ctx;
|
|
80
|
+
const tree = syntaxTree(view.state);
|
|
81
|
+
|
|
82
|
+
tree.iterate({
|
|
83
|
+
enter: (node) => {
|
|
84
|
+
const { from, to, name } = node;
|
|
85
|
+
|
|
86
|
+
if (!HEADING_TYPES.includes(name)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const level = parseInt(name.slice(-1), 10);
|
|
91
|
+
const headingClass = `heading-${level}` as keyof typeof headingMarkDecorations;
|
|
92
|
+
const lineClass = `heading-${level}` as keyof typeof headingLineDecorations;
|
|
93
|
+
|
|
94
|
+
// Add line decoration
|
|
95
|
+
const line = view.state.doc.lineAt(from);
|
|
96
|
+
decorations.push(headingLineDecorations[lineClass].range(line.from));
|
|
97
|
+
|
|
98
|
+
// Add mark decoration for the heading content
|
|
99
|
+
decorations.push(headingMarkDecorations[headingClass].range(from, to));
|
|
100
|
+
|
|
101
|
+
// Find and hide the heading marker (#). Heading level changes are handled by the selection toolbar.
|
|
102
|
+
const headingMark = node.node.getChild("HeaderMark");
|
|
103
|
+
if (headingMark) {
|
|
104
|
+
const markEnd = Math.min(headingMark.to + 1, line.to);
|
|
105
|
+
// Clamp to line end so replace decoration never spans a newline.
|
|
106
|
+
decorations.push(headingMarkDecorations["heading-mark"].range(headingMark.from, markEnd));
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override renderToHTML(node: SyntaxNode, children: string, ctx: PreviewContext): string | null {
|
|
113
|
+
if (node.name === "HeaderMark") {
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!HEADING_TYPES.includes(node.name)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const level = parseInt(node.name.slice(-1), 10);
|
|
122
|
+
const lineClass = headingLineDecorations[`heading-${level}` as keyof typeof headingLineDecorations].spec.class;
|
|
123
|
+
const headingClass = headingMarkDecorations[`heading-${level}` as keyof typeof headingMarkDecorations].spec.class;
|
|
124
|
+
const id = level >= 2 ? ctx.headingIdForNode?.(node) : null;
|
|
125
|
+
const idAttr = id ? ` id="${id}"` : "";
|
|
126
|
+
|
|
127
|
+
return `<div class="${lineClass}">
|
|
128
|
+
<h${level}${idAttr} class="${headingClass}">${children}</h${level}>
|
|
129
|
+
</div>\n`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const theme = createTheme({
|
|
134
|
+
default: {
|
|
135
|
+
".cm-mardora-h1": {
|
|
136
|
+
fontSize: "2em",
|
|
137
|
+
fontWeight: "bold",
|
|
138
|
+
fontFamily: "sans-serif",
|
|
139
|
+
textDecoration: "none",
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
".cm-mardora-h2": {
|
|
143
|
+
fontSize: "1.75em",
|
|
144
|
+
fontWeight: "bold",
|
|
145
|
+
fontFamily: "sans-serif",
|
|
146
|
+
textDecoration: "none",
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
".cm-mardora-h3": {
|
|
150
|
+
fontSize: "1.5em",
|
|
151
|
+
fontWeight: "bold",
|
|
152
|
+
fontFamily: "sans-serif",
|
|
153
|
+
textDecoration: "none",
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
".cm-mardora-h4": {
|
|
157
|
+
fontSize: "1.25em",
|
|
158
|
+
fontWeight: "bold",
|
|
159
|
+
fontFamily: "sans-serif",
|
|
160
|
+
textDecoration: "none",
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
".cm-mardora-h5": {
|
|
164
|
+
fontSize: "1em",
|
|
165
|
+
fontWeight: "bold",
|
|
166
|
+
fontFamily: "sans-serif",
|
|
167
|
+
textDecoration: "none",
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
".cm-mardora-h6": {
|
|
171
|
+
fontSize: "0.75em",
|
|
172
|
+
fontWeight: "bold",
|
|
173
|
+
fontFamily: "sans-serif",
|
|
174
|
+
textDecoration: "none",
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// Heading line styles
|
|
178
|
+
".cm-mardora-line-h1": {
|
|
179
|
+
paddingTop: "1.5em",
|
|
180
|
+
paddingBottom: "0.5em",
|
|
181
|
+
},
|
|
182
|
+
".cm-mardora-line-h2": {
|
|
183
|
+
paddingTop: "1.25em",
|
|
184
|
+
paddingBottom: "0.5em",
|
|
185
|
+
},
|
|
186
|
+
".cm-mardora-line-h3, .cm-mardora-line-h4, .cm-mardora-line-h5, .cm-mardora-line-h6": {
|
|
187
|
+
paddingTop: "1em",
|
|
188
|
+
paddingBottom: "0.5em",
|
|
189
|
+
},
|
|
190
|
+
".cm-mardora-header-mark": {
|
|
191
|
+
opacity: 0.5,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Decoration } from "@codemirror/view";
|
|
2
|
+
import { syntaxTree } from "@codemirror/language";
|
|
3
|
+
import { DecorationContext, DecorationPlugin } from "../editor/plugin";
|
|
4
|
+
import { createTheme } from "../editor";
|
|
5
|
+
import { SyntaxNode } from "@lezer/common";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Line decoration for horizontal rule lines
|
|
9
|
+
*/
|
|
10
|
+
const hrLineDecoration = Decoration.line({ class: "cm-mardora-hr-line" });
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Mark decoration to hide raw markers (---, ***, ___) when unfocused
|
|
14
|
+
*/
|
|
15
|
+
const hrMarkDecoration = Decoration.replace({});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* HRPlugin - Decorates markdown horizontal rules
|
|
19
|
+
*
|
|
20
|
+
* Adds visual styling to thematic breaks (---, ***, ___)
|
|
21
|
+
* - Line decoration that renders a centered horizontal line
|
|
22
|
+
* - Hides raw marker characters when the cursor is not on the line
|
|
23
|
+
*/
|
|
24
|
+
export class HRPlugin extends DecorationPlugin {
|
|
25
|
+
readonly name = "hr";
|
|
26
|
+
readonly version = "1.0.0";
|
|
27
|
+
override decorationPriority = 10;
|
|
28
|
+
override readonly requiredNodes = ["HorizontalRule"] as const;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Constructor - calls super constructor
|
|
32
|
+
*/
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Plugin theme
|
|
39
|
+
*/
|
|
40
|
+
override get theme() {
|
|
41
|
+
return theme;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build horizontal rule decorations by iterating the syntax tree
|
|
46
|
+
*/
|
|
47
|
+
buildDecorations(ctx: DecorationContext): void {
|
|
48
|
+
const { view, decorations } = ctx;
|
|
49
|
+
const tree = syntaxTree(view.state);
|
|
50
|
+
|
|
51
|
+
tree.iterate({
|
|
52
|
+
enter: (node) => {
|
|
53
|
+
const { from, to, name } = node;
|
|
54
|
+
|
|
55
|
+
if (name !== "HorizontalRule") {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Add line decoration for the horizontal rule styling
|
|
60
|
+
const line = view.state.doc.lineAt(from);
|
|
61
|
+
decorations.push(hrLineDecoration.range(line.from));
|
|
62
|
+
|
|
63
|
+
// Hide the raw markers when cursor is not on the line
|
|
64
|
+
const cursorInNode = ctx.selectionOverlapsRange(from, to);
|
|
65
|
+
if (!cursorInNode) {
|
|
66
|
+
// Clamp to line end so replace decoration never spans a newline
|
|
67
|
+
const markEnd = Math.min(to, line.to);
|
|
68
|
+
decorations.push(hrMarkDecoration.range(from, markEnd));
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
75
|
+
override renderToHTML(node: SyntaxNode, _children: string): string | null {
|
|
76
|
+
if (node.name !== "HorizontalRule") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return `<hr class="cm-mardora-hr-line" />\n`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const theme = createTheme({
|
|
85
|
+
default: {
|
|
86
|
+
// Line styling — displays a centered horizontal line
|
|
87
|
+
".cm-mardora-hr-line": {
|
|
88
|
+
display: "flex",
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
paddingTop: "0.75em",
|
|
91
|
+
paddingBottom: "0.75em",
|
|
92
|
+
border: "none",
|
|
93
|
+
"&::after": {
|
|
94
|
+
content: '""',
|
|
95
|
+
flex: "1",
|
|
96
|
+
height: "2px",
|
|
97
|
+
background: "currentColor",
|
|
98
|
+
opacity: "0.3",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|