draftly 1.0.0-alpha.2 → 2.0.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/README.md +12 -0
- package/dist/chunk-3T55CBNZ.cjs +33 -0
- package/dist/chunk-3T55CBNZ.cjs.map +1 -0
- package/dist/chunk-5MC4T7JH.cjs +58 -0
- package/dist/chunk-5MC4T7JH.cjs.map +1 -0
- package/dist/{chunk-72ZYRGRT.cjs → chunk-BWJLMREN.cjs} +11 -9
- package/dist/chunk-BWJLMREN.cjs.map +1 -0
- package/dist/{chunk-KBQDZ5IW.cjs → chunk-CLW73JRX.cjs} +100 -75
- package/dist/chunk-CLW73JRX.cjs.map +1 -0
- package/dist/{chunk-DFQYXFOP.js → chunk-EEHILRG5.js} +26 -3
- package/dist/chunk-EEHILRG5.js.map +1 -0
- package/dist/{chunk-HPSMS2WB.js → chunk-I563H35S.js} +101 -75
- package/dist/chunk-I563H35S.js.map +1 -0
- package/dist/chunk-IAXF4SJL.js +55 -0
- package/dist/chunk-IAXF4SJL.js.map +1 -0
- package/dist/chunk-JF3WXXMJ.js +31 -0
- package/dist/chunk-JF3WXXMJ.js.map +1 -0
- package/dist/{chunk-N3WL3XPB.js → chunk-L2XSK57Y.js} +1761 -478
- package/dist/chunk-L2XSK57Y.js.map +1 -0
- package/dist/{chunk-KDEDLC3D.cjs → chunk-TBVZEK2H.cjs} +27 -2
- package/dist/chunk-TBVZEK2H.cjs.map +1 -0
- package/dist/{chunk-2B3A3VSQ.cjs → chunk-W5ALMXG2.cjs} +1808 -504
- package/dist/chunk-W5ALMXG2.cjs.map +1 -0
- package/dist/{chunk-CG4M4TC7.js → chunk-ZUI3GI3W.js} +7 -5
- package/dist/chunk-ZUI3GI3W.js.map +1 -0
- package/dist/{draftly-BLnx3uGX.d.cts → draftly-BBL-AdOl.d.cts} +5 -1
- package/dist/{draftly-BLnx3uGX.d.ts → draftly-BBL-AdOl.d.ts} +5 -1
- package/dist/editor/index.cjs +22 -14
- package/dist/editor/index.d.cts +2 -1
- package/dist/editor/index.d.ts +2 -1
- package/dist/editor/index.js +2 -2
- package/dist/index.cjs +65 -39
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +6 -4
- 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/plugins/index.cjs +27 -17
- package/dist/plugins/index.d.cts +144 -9
- package/dist/plugins/index.d.ts +144 -9
- package/dist/plugins/index.js +5 -3
- package/dist/preview/index.cjs +16 -11
- package/dist/preview/index.d.cts +19 -4
- package/dist/preview/index.d.ts +19 -4
- package/dist/preview/index.js +3 -2
- package/package.json +8 -1
- package/src/editor/draftly.ts +1 -0
- package/src/editor/plugin.ts +5 -1
- package/src/editor/theme.ts +1 -0
- package/src/editor/utils.ts +31 -0
- package/src/index.ts +5 -4
- package/src/lib/index.ts +2 -0
- package/src/lib/input-handler.ts +45 -0
- package/src/plugins/code-plugin.theme.ts +426 -0
- package/src/plugins/code-plugin.ts +810 -561
- package/src/plugins/emoji-plugin.ts +140 -0
- package/src/plugins/index.ts +63 -57
- package/src/plugins/inline-plugin.ts +305 -291
- package/src/plugins/math-plugin.ts +12 -0
- package/src/plugins/table-plugin.ts +900 -0
- package/src/preview/context.ts +4 -1
- package/src/preview/css-generator.ts +14 -1
- package/src/preview/index.ts +9 -1
- package/src/preview/preview.ts +2 -1
- package/src/preview/renderer.ts +21 -20
- package/src/preview/syntax-theme.ts +110 -0
- package/src/preview/types.ts +14 -0
- package/dist/chunk-2B3A3VSQ.cjs.map +0 -1
- package/dist/chunk-72ZYRGRT.cjs.map +0 -1
- package/dist/chunk-CG4M4TC7.js.map +0 -1
- package/dist/chunk-DFQYXFOP.js.map +0 -1
- package/dist/chunk-HPSMS2WB.js.map +0 -1
- package/dist/chunk-KBQDZ5IW.cjs.map +0 -1
- package/dist/chunk-KDEDLC3D.cjs.map +0 -1
- package/dist/chunk-N3WL3XPB.js.map +0 -1
package/src/preview/context.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SyntaxNode } from "@lezer/common";
|
|
2
|
+
import { Highlighter } from "@lezer/highlight";
|
|
2
3
|
import { ThemeEnum } from "../editor/utils";
|
|
3
4
|
import { PreviewContext } from "./types";
|
|
4
5
|
import DOMPurify from "dompurify";
|
|
@@ -10,11 +11,13 @@ export function createPreviewContext(
|
|
|
10
11
|
doc: string,
|
|
11
12
|
theme: ThemeEnum,
|
|
12
13
|
renderChildren: (node: SyntaxNode) => Promise<string>,
|
|
13
|
-
sanitizeHtml: boolean = true
|
|
14
|
+
sanitizeHtml: boolean = true,
|
|
15
|
+
syntaxHighlighters: readonly Highlighter[] = []
|
|
14
16
|
): PreviewContext {
|
|
15
17
|
return {
|
|
16
18
|
doc,
|
|
17
19
|
theme,
|
|
20
|
+
syntaxHighlighters,
|
|
18
21
|
|
|
19
22
|
sliceDoc(from: number, to: number): string {
|
|
20
23
|
return doc.slice(from, to);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ThemeEnum } from "../editor/utils";
|
|
2
2
|
import { GenerateCSSConfig } from "./types";
|
|
3
|
+
import { generateSyntaxThemeCSS } from "./syntax-theme";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Base CSS styles for preview rendering
|
|
@@ -27,7 +28,13 @@ const baseStyles = `.draftly-preview {
|
|
|
27
28
|
* ```
|
|
28
29
|
*/
|
|
29
30
|
export function generateCSS(config: GenerateCSSConfig = {}): string {
|
|
30
|
-
const {
|
|
31
|
+
const {
|
|
32
|
+
plugins = [],
|
|
33
|
+
theme = ThemeEnum.AUTO,
|
|
34
|
+
wrapperClass = "draftly-preview",
|
|
35
|
+
includeBase = true,
|
|
36
|
+
syntaxTheme,
|
|
37
|
+
} = config;
|
|
31
38
|
|
|
32
39
|
const cssChunks: string[] = [];
|
|
33
40
|
|
|
@@ -41,6 +48,12 @@ export function generateCSS(config: GenerateCSSConfig = {}): string {
|
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
// Collect syntax highlight styles (`tok-*` classes) from CodeMirror theme/extensions
|
|
52
|
+
const syntaxCSS = generateSyntaxThemeCSS(syntaxTheme, wrapperClass);
|
|
53
|
+
if (syntaxCSS) {
|
|
54
|
+
cssChunks.push("/* syntax-theme */\n" + syntaxCSS);
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
// Collect styles from plugins
|
|
45
58
|
for (const plugin of plugins) {
|
|
46
59
|
const pluginCSS = plugin.getPreviewStyles(theme, wrapperClass);
|
package/src/preview/index.ts
CHANGED
|
@@ -9,9 +9,17 @@ export { preview } from "./preview";
|
|
|
9
9
|
|
|
10
10
|
// CSS generation
|
|
11
11
|
export { generateCSS } from "./css-generator";
|
|
12
|
+
export { generateSyntaxThemeCSS } from "./syntax-theme";
|
|
12
13
|
|
|
13
14
|
// Types
|
|
14
|
-
export type {
|
|
15
|
+
export type {
|
|
16
|
+
PreviewConfig,
|
|
17
|
+
PreviewContext,
|
|
18
|
+
GenerateCSSConfig,
|
|
19
|
+
SyntaxThemeInput,
|
|
20
|
+
NodeRenderer,
|
|
21
|
+
NodeRendererMap,
|
|
22
|
+
} from "./types";
|
|
15
23
|
|
|
16
24
|
// Utilities
|
|
17
25
|
export { escapeHtml, defaultRenderers } from "./default-renderers";
|
package/src/preview/preview.ts
CHANGED
|
@@ -28,10 +28,11 @@ export async function preview(markdown: string, config: PreviewConfig = {}): Pro
|
|
|
28
28
|
wrapperTag = "article",
|
|
29
29
|
sanitize = true,
|
|
30
30
|
theme = ThemeEnum.AUTO,
|
|
31
|
+
syntaxTheme,
|
|
31
32
|
} = config;
|
|
32
33
|
|
|
33
34
|
// Create renderer and generate HTML
|
|
34
|
-
const renderer = new PreviewRenderer(markdown, plugins, markdownConfig, theme, sanitize);
|
|
35
|
+
const renderer = new PreviewRenderer(markdown, plugins, markdownConfig, theme, sanitize, syntaxTheme);
|
|
35
36
|
const content = await renderer.render();
|
|
36
37
|
|
|
37
38
|
// Wrap in container
|
package/src/preview/renderer.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { SyntaxNode } from "@lezer/common";
|
|
2
|
-
import {
|
|
2
|
+
import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
|
|
3
|
+
import { MarkdownConfig } from "@lezer/markdown";
|
|
4
|
+
import { languages } from "@codemirror/language-data";
|
|
3
5
|
|
|
4
6
|
import { DraftlyPlugin } from "../editor/plugin";
|
|
5
7
|
import { ThemeEnum } from "../editor/utils";
|
|
6
8
|
import { createPreviewContext } from "./context";
|
|
7
9
|
import { defaultRenderers, escapeHtml } from "./default-renderers";
|
|
10
|
+
import { resolveSyntaxHighlighters } from "./syntax-theme";
|
|
8
11
|
import { NodeRendererMap, PreviewContext } from "./types";
|
|
9
|
-
import { foldNodeProp } from "@codemirror/language";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Renderer class that walks the syntax tree and produces HTML
|
|
@@ -17,6 +19,7 @@ export class PreviewRenderer {
|
|
|
17
19
|
private plugins: DraftlyPlugin[];
|
|
18
20
|
private markdown: MarkdownConfig[];
|
|
19
21
|
private sanitizeHtml: boolean;
|
|
22
|
+
private syntaxTheme: import("./types").SyntaxThemeInput | import("./types").SyntaxThemeInput[] | undefined;
|
|
20
23
|
private renderers: NodeRendererMap;
|
|
21
24
|
private ctx: PreviewContext;
|
|
22
25
|
private nodeToPlugins: Map<string, DraftlyPlugin[]>;
|
|
@@ -26,17 +29,21 @@ export class PreviewRenderer {
|
|
|
26
29
|
plugins: DraftlyPlugin[] = [],
|
|
27
30
|
markdown: MarkdownConfig[],
|
|
28
31
|
theme: ThemeEnum = ThemeEnum.AUTO,
|
|
29
|
-
sanitize: boolean = true
|
|
32
|
+
sanitize: boolean = true,
|
|
33
|
+
syntaxTheme?: import("./types").SyntaxThemeInput | import("./types").SyntaxThemeInput[]
|
|
30
34
|
) {
|
|
31
35
|
this.doc = doc;
|
|
32
36
|
this.theme = theme;
|
|
33
37
|
this.plugins = plugins;
|
|
34
38
|
this.markdown = markdown;
|
|
35
39
|
this.sanitizeHtml = sanitize;
|
|
40
|
+
this.syntaxTheme = syntaxTheme;
|
|
36
41
|
this.renderers = { ...defaultRenderers };
|
|
37
42
|
|
|
43
|
+
const syntaxHighlighters = resolveSyntaxHighlighters(this.syntaxTheme, true);
|
|
44
|
+
|
|
38
45
|
// Create context with reference to renderChildren
|
|
39
|
-
this.ctx = createPreviewContext(doc, theme, this.renderChildren.bind(this), sanitize);
|
|
46
|
+
this.ctx = createPreviewContext(doc, theme, this.renderChildren.bind(this), sanitize, syntaxHighlighters);
|
|
40
47
|
|
|
41
48
|
// Build node-to-plugin map for O(1) lookup
|
|
42
49
|
this.nodeToPlugins = this.buildNodePluginMap();
|
|
@@ -69,22 +76,16 @@ export class PreviewRenderer {
|
|
|
69
76
|
...this.plugins.map((p) => p.getMarkdownConfig()).filter((ext): ext is NonNullable<typeof ext> => ext !== null),
|
|
70
77
|
];
|
|
71
78
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Table: (tree, state) => ({ from: state.doc.lineAt(tree.from).to, to: tree.to }),
|
|
83
|
-
}),
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
]);
|
|
87
|
-
const parser = extensions.length > 0 ? baseParser.configure(extensions) : baseParser;
|
|
79
|
+
// Build parser through @codemirror/lang-markdown to match editor behavior exactly
|
|
80
|
+
const markdownSupport = markdown({
|
|
81
|
+
base: markdownLanguage,
|
|
82
|
+
codeLanguages: languages,
|
|
83
|
+
extensions,
|
|
84
|
+
addKeymap: true,
|
|
85
|
+
completeHTMLTags: true,
|
|
86
|
+
pasteURLAsLink: true,
|
|
87
|
+
});
|
|
88
|
+
const parser = markdownSupport.language.parser;
|
|
88
89
|
|
|
89
90
|
// Parse the document
|
|
90
91
|
const tree = parser.parse(this.doc);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { classHighlighter, Highlighter } from "@lezer/highlight";
|
|
2
|
+
import type { SyntaxThemeInput } from "./types";
|
|
3
|
+
|
|
4
|
+
type HighlightSpec = {
|
|
5
|
+
tag?: unknown;
|
|
6
|
+
class?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type RuntimeHighlightStyle = {
|
|
11
|
+
specs?: HighlightSpec[];
|
|
12
|
+
style?: (tags: readonly import("@lezer/highlight").Tag[]) => string | null;
|
|
13
|
+
module?: { getRules(): string } | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const MAX_WALK_DEPTH = 8;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extract syntax highlight CSS from resolved CodeMirror HighlightStyle modules.
|
|
20
|
+
*/
|
|
21
|
+
export function generateSyntaxThemeCSS(
|
|
22
|
+
syntaxTheme: SyntaxThemeInput | SyntaxThemeInput[] | undefined,
|
|
23
|
+
_wrapperClass: string
|
|
24
|
+
): string {
|
|
25
|
+
if (!syntaxTheme) return "";
|
|
26
|
+
|
|
27
|
+
const styles = extractRuntimeHighlightStyles(syntaxTheme);
|
|
28
|
+
if (!styles.length) return "";
|
|
29
|
+
|
|
30
|
+
const cssChunks: string[] = [];
|
|
31
|
+
|
|
32
|
+
for (const style of styles) {
|
|
33
|
+
const rules = style.module?.getRules();
|
|
34
|
+
if (!rules) continue;
|
|
35
|
+
cssChunks.push(rules);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!cssChunks.length) return "";
|
|
39
|
+
|
|
40
|
+
return Array.from(new Set(cssChunks))
|
|
41
|
+
.join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveSyntaxHighlighters(
|
|
45
|
+
syntaxTheme: SyntaxThemeInput | SyntaxThemeInput[] | undefined,
|
|
46
|
+
includeLegacyClassHighlighter: boolean = true
|
|
47
|
+
): readonly Highlighter[] {
|
|
48
|
+
const resolved: Highlighter[] = [];
|
|
49
|
+
if (includeLegacyClassHighlighter) {
|
|
50
|
+
resolved.push(classHighlighter);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const styles = extractRuntimeHighlightStyles(syntaxTheme);
|
|
54
|
+
for (const style of styles) {
|
|
55
|
+
if (typeof style.style === "function") {
|
|
56
|
+
resolved.push(style as unknown as Highlighter);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return Array.from(new Set(resolved));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function extractRuntimeHighlightStyles(input: SyntaxThemeInput | SyntaxThemeInput[] | undefined): RuntimeHighlightStyle[] {
|
|
64
|
+
if (!input) return [];
|
|
65
|
+
|
|
66
|
+
const values = Array.isArray(input) ? input : [input];
|
|
67
|
+
const styles: RuntimeHighlightStyle[] = [];
|
|
68
|
+
const visited = new WeakSet<object>();
|
|
69
|
+
|
|
70
|
+
for (const value of values) {
|
|
71
|
+
walk(value, 0, visited, styles);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return styles;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function walk(value: unknown, depth: number, visited: WeakSet<object>, out: RuntimeHighlightStyle[]): void {
|
|
78
|
+
if (value === null || value === undefined) return;
|
|
79
|
+
if (depth > MAX_WALK_DEPTH) return;
|
|
80
|
+
|
|
81
|
+
if (isRuntimeHighlightStyle(value)) {
|
|
82
|
+
out.push(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
for (const item of value) {
|
|
87
|
+
walk(item, depth + 1, visited, out);
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (typeof value !== "object") return;
|
|
93
|
+
if (visited.has(value)) return;
|
|
94
|
+
visited.add(value);
|
|
95
|
+
|
|
96
|
+
const keys = Object.getOwnPropertyNames(value);
|
|
97
|
+
for (const key of keys) {
|
|
98
|
+
try {
|
|
99
|
+
walk((value as Record<string, unknown>)[key], depth + 1, visited, out);
|
|
100
|
+
} catch {
|
|
101
|
+
// Ignore inaccessible properties
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isRuntimeHighlightStyle(value: unknown): value is RuntimeHighlightStyle {
|
|
107
|
+
if (!value || typeof value !== "object") return false;
|
|
108
|
+
const style = value as RuntimeHighlightStyle;
|
|
109
|
+
return Array.isArray(style.specs) && typeof style.style === "function";
|
|
110
|
+
}
|
package/src/preview/types.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { SyntaxNode } from "@lezer/common";
|
|
2
2
|
import { ThemeEnum } from "../editor/utils";
|
|
3
3
|
|
|
4
|
+
export type SyntaxThemeInput =
|
|
5
|
+
| import("@codemirror/language").HighlightStyle
|
|
6
|
+
| import("@codemirror/state").Extension
|
|
7
|
+
| readonly import("@codemirror/state").Extension[];
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* Context passed to plugin preview methods
|
|
6
11
|
*/
|
|
@@ -19,6 +24,9 @@ export interface PreviewContext {
|
|
|
19
24
|
|
|
20
25
|
/** Render children of a node to HTML */
|
|
21
26
|
renderChildren(node: SyntaxNode): Promise<string>;
|
|
27
|
+
|
|
28
|
+
/** Active syntax highlighters used for code rendering */
|
|
29
|
+
readonly syntaxHighlighters?: readonly import("@lezer/highlight").Highlighter[];
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
/**
|
|
@@ -42,6 +50,9 @@ export interface PreviewConfig {
|
|
|
42
50
|
|
|
43
51
|
/** Theme to use */
|
|
44
52
|
theme?: ThemeEnum;
|
|
53
|
+
|
|
54
|
+
/** CodeMirror syntax theme input used for static preview highlighting */
|
|
55
|
+
syntaxTheme?: SyntaxThemeInput | SyntaxThemeInput[];
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
/**
|
|
@@ -59,6 +70,9 @@ export interface GenerateCSSConfig {
|
|
|
59
70
|
|
|
60
71
|
/** Include base styles */
|
|
61
72
|
includeBase?: boolean;
|
|
73
|
+
|
|
74
|
+
/** CodeMirror syntax theme input used for static preview syntax highlighting */
|
|
75
|
+
syntaxTheme?: SyntaxThemeInput | SyntaxThemeInput[];
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
/**
|