draftly 0.1.0-alpha.1 → 1.0.7
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/dist/chunk-2B3A3VSQ.cjs +3382 -0
- package/dist/chunk-2B3A3VSQ.cjs.map +1 -0
- package/dist/{chunk-ZDSZRRUY.cjs → chunk-72ZYRGRT.cjs} +48 -104
- package/dist/chunk-72ZYRGRT.cjs.map +1 -0
- package/dist/{chunk-MOG6E2LY.js → chunk-CG4M4TC7.js} +46 -103
- package/dist/chunk-CG4M4TC7.js.map +1 -0
- package/dist/{chunk-LCQALOEI.js → chunk-DFQYXFOP.js} +36 -3
- package/dist/chunk-DFQYXFOP.js.map +1 -0
- package/dist/{chunk-6LQ2VR4I.js → chunk-HPSMS2WB.js} +38 -22
- package/dist/chunk-HPSMS2WB.js.map +1 -0
- package/dist/{chunk-7Z3SRTPZ.cjs → chunk-KBQDZ5IW.cjs} +38 -22
- package/dist/chunk-KBQDZ5IW.cjs.map +1 -0
- package/dist/{chunk-RV2SYFA6.cjs → chunk-KDEDLC3D.cjs} +36 -2
- package/dist/chunk-KDEDLC3D.cjs.map +1 -0
- package/dist/chunk-N3WL3XPB.js +3360 -0
- package/dist/chunk-N3WL3XPB.js.map +1 -0
- package/dist/{draftly-Bxu_H4nw.d.ts → draftly-BLnx3uGX.d.cts} +12 -6
- package/dist/{draftly-Bxu_H4nw.d.cts → draftly-BLnx3uGX.d.ts} +12 -6
- package/dist/editor/index.cjs +20 -12
- package/dist/editor/index.d.cts +3 -2
- package/dist/editor/index.d.ts +3 -2
- package/dist/editor/index.js +2 -2
- package/dist/index.cjs +59 -27
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/plugins/index.cjs +35 -11
- package/dist/plugins/index.d.cts +321 -3
- package/dist/plugins/index.d.ts +321 -3
- package/dist/plugins/index.js +3 -3
- package/dist/preview/index.cjs +7 -7
- package/dist/preview/index.d.cts +9 -4
- package/dist/preview/index.d.ts +9 -4
- package/dist/preview/index.js +2 -2
- package/package.json +2 -1
- package/src/editor/draftly.ts +9 -13
- package/src/editor/plugin.ts +4 -1
- package/src/editor/theme.ts +34 -1
- package/src/editor/utils.ts +49 -0
- package/src/editor/view-plugin.ts +6 -131
- package/src/plugins/code-plugin.ts +1119 -0
- package/src/plugins/heading-plugin.ts +23 -11
- package/src/plugins/hr-plugin.ts +102 -0
- package/src/plugins/image-plugin.ts +96 -2
- package/src/plugins/index.ts +57 -39
- package/src/plugins/inline-plugin.ts +125 -6
- package/src/plugins/link-plugin.ts +509 -0
- package/src/plugins/list-plugin.ts +116 -2
- package/src/plugins/math-plugin.ts +5 -1
- package/src/plugins/mermaid-plugin.ts +500 -0
- package/src/plugins/paragraph-plugin.ts +38 -0
- package/src/plugins/quote-plugin.ts +146 -0
- package/src/preview/context.ts +1 -1
- package/src/preview/css-generator.ts +3 -1
- package/src/preview/default-renderers.ts +0 -5
- package/src/preview/preview.ts +2 -2
- package/src/preview/renderer.ts +34 -12
- package/src/preview/types.ts +1 -1
- package/dist/chunk-6LQ2VR4I.js.map +0 -1
- package/dist/chunk-7Z3SRTPZ.cjs.map +0 -1
- package/dist/chunk-GA6NYY77.cjs +0 -1400
- package/dist/chunk-GA6NYY77.cjs.map +0 -1
- package/dist/chunk-LCQALOEI.js.map +0 -1
- package/dist/chunk-MOG6E2LY.js.map +0 -1
- package/dist/chunk-RV2SYFA6.cjs.map +0 -1
- package/dist/chunk-TKZNKWGF.js +0 -1385
- package/dist/chunk-TKZNKWGF.js.map +0 -1
- package/dist/chunk-ZDSZRRUY.cjs.map +0 -1
|
@@ -20,15 +20,10 @@ const renderDocument: NodeRenderer = (_node, children) => {
|
|
|
20
20
|
return children;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
const renderParagraph: NodeRenderer = (_node, children) => {
|
|
24
|
-
return `<p>${children}</p>\n`;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
23
|
/**
|
|
28
24
|
* Default node renderers for all markdown node types
|
|
29
25
|
*/
|
|
30
26
|
export const defaultRenderers: NodeRendererMap = {
|
|
31
27
|
// Document structure
|
|
32
28
|
Document: renderDocument,
|
|
33
|
-
Paragraph: renderParagraph,
|
|
34
29
|
};
|
package/src/preview/preview.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { PreviewConfig } from "./types";
|
|
|
20
20
|
* });
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
|
-
export function preview(markdown: string, config: PreviewConfig = {}): string {
|
|
23
|
+
export async function preview(markdown: string, config: PreviewConfig = {}): Promise<string> {
|
|
24
24
|
const {
|
|
25
25
|
plugins = [],
|
|
26
26
|
markdown: markdownConfig = [],
|
|
@@ -32,7 +32,7 @@ export function preview(markdown: string, config: PreviewConfig = {}): string {
|
|
|
32
32
|
|
|
33
33
|
// Create renderer and generate HTML
|
|
34
34
|
const renderer = new PreviewRenderer(markdown, plugins, markdownConfig, theme, sanitize);
|
|
35
|
-
const content = renderer.render();
|
|
35
|
+
const content = await renderer.render();
|
|
36
36
|
|
|
37
37
|
// Wrap in container
|
|
38
38
|
const classAttr = wrapperClass ? ` class="${wrapperClass}"` : "";
|
package/src/preview/renderer.ts
CHANGED
|
@@ -19,6 +19,7 @@ export class PreviewRenderer {
|
|
|
19
19
|
private sanitizeHtml: boolean;
|
|
20
20
|
private renderers: NodeRendererMap;
|
|
21
21
|
private ctx: PreviewContext;
|
|
22
|
+
private nodeToPlugins: Map<string, DraftlyPlugin[]>;
|
|
22
23
|
|
|
23
24
|
constructor(
|
|
24
25
|
doc: string,
|
|
@@ -36,12 +37,32 @@ export class PreviewRenderer {
|
|
|
36
37
|
|
|
37
38
|
// Create context with reference to renderChildren
|
|
38
39
|
this.ctx = createPreviewContext(doc, theme, this.renderChildren.bind(this), sanitize);
|
|
40
|
+
|
|
41
|
+
// Build node-to-plugin map for O(1) lookup
|
|
42
|
+
this.nodeToPlugins = this.buildNodePluginMap();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build a map from node names to plugins that handle them
|
|
47
|
+
*/
|
|
48
|
+
private buildNodePluginMap(): Map<string, DraftlyPlugin[]> {
|
|
49
|
+
const map = new Map<string, DraftlyPlugin[]>();
|
|
50
|
+
for (const plugin of this.plugins) {
|
|
51
|
+
if (plugin.renderToHTML && plugin.requiredNodes.length > 0) {
|
|
52
|
+
for (const nodeName of plugin.requiredNodes) {
|
|
53
|
+
const list = map.get(nodeName) || [];
|
|
54
|
+
list.push(plugin);
|
|
55
|
+
map.set(nodeName, list);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
39
60
|
}
|
|
40
61
|
|
|
41
62
|
/**
|
|
42
63
|
* Render the document to HTML
|
|
43
64
|
*/
|
|
44
|
-
render(): string {
|
|
65
|
+
async render(): Promise<string> {
|
|
45
66
|
// Collect markdown extensions from plugins
|
|
46
67
|
const extensions = [
|
|
47
68
|
...this.markdown,
|
|
@@ -69,18 +90,19 @@ export class PreviewRenderer {
|
|
|
69
90
|
const tree = parser.parse(this.doc);
|
|
70
91
|
|
|
71
92
|
// Render from root
|
|
72
|
-
return this.renderNode(tree.topNode);
|
|
93
|
+
return await this.renderNode(tree.topNode);
|
|
73
94
|
}
|
|
74
95
|
|
|
75
96
|
/**
|
|
76
97
|
* Render a single node to HTML
|
|
77
98
|
*/
|
|
78
|
-
private renderNode(node: SyntaxNode): string {
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
99
|
+
private async renderNode(node: SyntaxNode): Promise<string> {
|
|
100
|
+
// Get plugins that handle this node type (O(1) lookup)
|
|
101
|
+
const plugins = this.nodeToPlugins.get(node.name);
|
|
102
|
+
if (plugins) {
|
|
103
|
+
for (const plugin of plugins) {
|
|
104
|
+
const children = await this.renderChildren(node);
|
|
105
|
+
const result = await plugin.renderToHTML!(node, children, this.ctx);
|
|
84
106
|
if (result !== null) {
|
|
85
107
|
return result;
|
|
86
108
|
}
|
|
@@ -90,13 +112,13 @@ export class PreviewRenderer {
|
|
|
90
112
|
// Use default renderer
|
|
91
113
|
const renderer = this.renderers[node.name];
|
|
92
114
|
if (renderer) {
|
|
93
|
-
const children = this.renderChildren(node);
|
|
115
|
+
const children = await this.renderChildren(node);
|
|
94
116
|
return renderer(node, children, this.ctx);
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
// Unknown node - render children or text
|
|
98
120
|
if (node.firstChild) {
|
|
99
|
-
return this.renderChildren(node);
|
|
121
|
+
return await this.renderChildren(node);
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
// Leaf node - return text content
|
|
@@ -106,7 +128,7 @@ export class PreviewRenderer {
|
|
|
106
128
|
/**
|
|
107
129
|
* Render all children of a node, including text between nodes
|
|
108
130
|
*/
|
|
109
|
-
private renderChildren(node: SyntaxNode): string {
|
|
131
|
+
private async renderChildren(node: SyntaxNode): Promise<string> {
|
|
110
132
|
let result = "";
|
|
111
133
|
let pos = node.from; // Track position to find text gaps
|
|
112
134
|
let child = node.firstChild;
|
|
@@ -118,7 +140,7 @@ export class PreviewRenderer {
|
|
|
118
140
|
}
|
|
119
141
|
|
|
120
142
|
// Render the child node
|
|
121
|
-
result += this.renderNode(child);
|
|
143
|
+
result += await this.renderNode(child);
|
|
122
144
|
|
|
123
145
|
// Update position to end of this child
|
|
124
146
|
pos = child.to;
|
package/src/preview/types.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/preview/context.ts","../src/preview/default-renderers.ts","../src/preview/renderer.ts","../src/preview/preview.ts","../src/preview/css-generator.ts"],"names":["markdownParser","tree","parser"],"mappings":"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","file":"chunk-6LQ2VR4I.js","sourcesContent":["import { SyntaxNode } from \"@lezer/common\";\r\nimport { ThemeEnum } from \"../editor/utils\";\r\nimport { PreviewContext } from \"./types\";\r\nimport DOMPurify from \"dompurify\";\r\n\r\n/**\r\n * Creates a PreviewContext for rendering\r\n */\r\nexport function createPreviewContext(\r\n doc: string,\r\n theme: ThemeEnum,\r\n renderChildren: (node: SyntaxNode) => string,\r\n sanitizeHtml: boolean = true\r\n): PreviewContext {\r\n return {\r\n doc,\r\n theme,\r\n\r\n sliceDoc(from: number, to: number): string {\r\n return doc.slice(from, to);\r\n },\r\n\r\n sanitize(html: string): string {\r\n if (!sanitizeHtml) return html;\r\n\r\n // DOMPurify works in browser; in Node, it needs jsdom\r\n if (typeof window !== \"undefined\") {\r\n return DOMPurify.sanitize(html);\r\n }\r\n\r\n // Server-side: return as-is (user should sanitize at application level)\r\n // or use isomorphic-dompurify in their setup\r\n return html;\r\n },\r\n\r\n renderChildren,\r\n };\r\n}\r\n","import { NodeRenderer, NodeRendererMap } from \"./types\";\r\n\r\n/**\r\n * Escape HTML special characters\r\n */\r\nexport function escapeHtml(text: string): string {\r\n return text\r\n .replace(/&/g, \"&\")\r\n .replace(/</g, \"<\")\r\n .replace(/>/g, \">\")\r\n .replace(/\"/g, \""\")\r\n .replace(/'/g, \"'\");\r\n}\r\n\r\n// ============================================\r\n// DEFAULT RENDERERS\r\n// ============================================\r\n\r\nconst renderDocument: NodeRenderer = (_node, children) => {\r\n return children;\r\n};\r\n\r\nconst renderParagraph: NodeRenderer = (_node, children) => {\r\n return `<p>${children}</p>\\n`;\r\n};\r\n\r\n/**\r\n * Default node renderers for all markdown node types\r\n */\r\nexport const defaultRenderers: NodeRendererMap = {\r\n // Document structure\r\n Document: renderDocument,\r\n Paragraph: renderParagraph,\r\n};\r\n","import { SyntaxNode } from \"@lezer/common\";\r\nimport { Emoji, GFM, MarkdownConfig, parser as markdownParser, Subscript, Superscript } from \"@lezer/markdown\";\r\n\r\nimport { DraftlyPlugin } from \"../editor/plugin\";\r\nimport { ThemeEnum } from \"../editor/utils\";\r\nimport { createPreviewContext } from \"./context\";\r\nimport { defaultRenderers, escapeHtml } from \"./default-renderers\";\r\nimport { NodeRendererMap, PreviewContext } from \"./types\";\r\nimport { foldNodeProp } from \"@codemirror/language\";\r\n\r\n/**\r\n * Renderer class that walks the syntax tree and produces HTML\r\n */\r\nexport class PreviewRenderer {\r\n private doc: string;\r\n private theme: ThemeEnum;\r\n private plugins: DraftlyPlugin[];\r\n private markdown: MarkdownConfig[];\r\n private sanitizeHtml: boolean;\r\n private renderers: NodeRendererMap;\r\n private ctx: PreviewContext;\r\n\r\n constructor(\r\n doc: string,\r\n plugins: DraftlyPlugin[] = [],\r\n markdown: MarkdownConfig[],\r\n theme: ThemeEnum = ThemeEnum.AUTO,\r\n sanitize: boolean = true\r\n ) {\r\n this.doc = doc;\r\n this.theme = theme;\r\n this.plugins = plugins;\r\n this.markdown = markdown;\r\n this.sanitizeHtml = sanitize;\r\n this.renderers = { ...defaultRenderers };\r\n\r\n // Create context with reference to renderChildren\r\n this.ctx = createPreviewContext(doc, theme, this.renderChildren.bind(this), sanitize);\r\n }\r\n\r\n /**\r\n * Render the document to HTML\r\n */\r\n render(): string {\r\n // Collect markdown extensions from plugins\r\n const extensions = [\r\n ...this.markdown,\r\n ...this.plugins.map((p) => p.getMarkdownConfig()).filter((ext): ext is NonNullable<typeof ext> => ext !== null),\r\n ];\r\n\r\n // Use GFM extensions to match the editor (markdownLanguage includes GFM by default)\r\n // GFM includes: Table, TaskList, Strikethrough, Autolink\r\n const baseParser = markdownParser.configure([\r\n GFM,\r\n Subscript,\r\n Superscript,\r\n Emoji,\r\n {\r\n props: [\r\n foldNodeProp.add({\r\n Table: (tree, state) => ({ from: state.doc.lineAt(tree.from).to, to: tree.to }),\r\n }),\r\n ],\r\n },\r\n ]);\r\n const parser = extensions.length > 0 ? baseParser.configure(extensions) : baseParser;\r\n\r\n // Parse the document\r\n const tree = parser.parse(this.doc);\r\n\r\n // Render from root\r\n return this.renderNode(tree.topNode);\r\n }\r\n\r\n /**\r\n * Render a single node to HTML\r\n */\r\n private renderNode(node: SyntaxNode): string {\r\n // First, let plugins try to render\r\n for (const plugin of this.plugins) {\r\n if (plugin.renderToHTML) {\r\n const children = this.renderChildren(node);\r\n const result = plugin.renderToHTML(node, children, this.ctx);\r\n if (result !== null) {\r\n return result;\r\n }\r\n }\r\n }\r\n\r\n // Use default renderer\r\n const renderer = this.renderers[node.name];\r\n if (renderer) {\r\n const children = this.renderChildren(node);\r\n return renderer(node, children, this.ctx);\r\n }\r\n\r\n // Unknown node - render children or text\r\n if (node.firstChild) {\r\n return this.renderChildren(node);\r\n }\r\n\r\n // Leaf node - return text content\r\n return this.ctx.sliceDoc(node.from, node.to);\r\n }\r\n\r\n /**\r\n * Render all children of a node, including text between nodes\r\n */\r\n private renderChildren(node: SyntaxNode): string {\r\n let result = \"\";\r\n let pos = node.from; // Track position to find text gaps\r\n let child = node.firstChild;\r\n\r\n while (child) {\r\n // Add any text between the last position and this child\r\n if (child.from > pos) {\r\n result += escapeHtml(this.ctx.sliceDoc(pos, child.from));\r\n }\r\n\r\n // Render the child node\r\n result += this.renderNode(child);\r\n\r\n // Update position to end of this child\r\n pos = child.to;\r\n child = child.nextSibling;\r\n }\r\n\r\n // Add any trailing text after the last child\r\n if (pos < node.to) {\r\n result += escapeHtml(this.ctx.sliceDoc(pos, node.to));\r\n }\r\n\r\n return result;\r\n }\r\n}\r\n","import { ThemeEnum } from \"../editor/utils\";\r\nimport { PreviewRenderer } from \"./renderer\";\r\nimport { PreviewConfig } from \"./types\";\r\n\r\n/**\r\n * Render markdown to semantic HTML\r\n *\r\n * @param markdown - Markdown string to render\r\n * @param config - Preview configuration\r\n * @returns HTML string\r\n *\r\n * @example\r\n * ```ts\r\n * import { preview } from 'draftly/preview';\r\n * import { HeadingPlugin, ListPlugin } from 'draftly/plugins';\r\n *\r\n * const html = preview('# Hello World', {\r\n * plugins: [new HeadingPlugin(), new ListPlugin()],\r\n * wrapperClass: 'draftly-preview',\r\n * });\r\n * ```\r\n */\r\nexport function preview(markdown: string, config: PreviewConfig = {}): string {\r\n const {\r\n plugins = [],\r\n markdown: markdownConfig = [],\r\n wrapperClass = \"draftly-preview\",\r\n wrapperTag = \"article\",\r\n sanitize = true,\r\n theme = ThemeEnum.AUTO,\r\n } = config;\r\n\r\n // Create renderer and generate HTML\r\n const renderer = new PreviewRenderer(markdown, plugins, markdownConfig, theme, sanitize);\r\n const content = renderer.render();\r\n\r\n // Wrap in container\r\n const classAttr = wrapperClass ? ` class=\"${wrapperClass}\"` : \"\";\r\n return `<${wrapperTag}${classAttr}>\\n${content}</${wrapperTag}>`;\r\n}\r\n","import { ThemeEnum } from \"../editor/utils\";\r\nimport { GenerateCSSConfig } from \"./types\";\r\n\r\n/**\r\n * Base CSS styles for preview rendering\r\n */\r\nconst baseStyles = ``;\r\n\r\n/**\r\n * Generate CSS for preview rendering\r\n *\r\n * @param config - CSS generation configuration\r\n * @returns CSS string\r\n *\r\n * @example\r\n * ```ts\r\n * import { generateCSS } from 'draftly/preview';\r\n * import { HeadingPlugin, ListPlugin } from 'draftly/plugins';\r\n *\r\n * const css = generateCSS({\r\n * plugins: [new HeadingPlugin(), new ListPlugin()],\r\n * theme: ThemeEnum.AUTO,\r\n * includeBase: true,\r\n * });\r\n * ```\r\n */\r\nexport function generateCSS(config: GenerateCSSConfig = {}): string {\r\n const { plugins = [], theme = ThemeEnum.AUTO, wrapperClass = \"draftly-preview\", includeBase = true } = config;\r\n\r\n const cssChunks: string[] = [];\r\n\r\n // Include base styles\r\n if (includeBase) {\r\n // Replace default wrapper class if custom one is provided\r\n if (wrapperClass !== \"draftly-preview\") {\r\n cssChunks.push(baseStyles.replace(/\\.draftly-preview/g, `.${wrapperClass}`));\r\n } else {\r\n cssChunks.push(baseStyles);\r\n }\r\n }\r\n\r\n // Collect styles from plugins\r\n for (const plugin of plugins) {\r\n const pluginCSS = plugin.getPreviewStyles(theme, wrapperClass);\r\n if (pluginCSS) cssChunks.push(`/* ${plugin.name} - ${plugin.version} */\\n` + pluginCSS);\r\n }\r\n\r\n return cssChunks.join(\"\\n\\n\");\r\n}\r\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/preview/context.ts","../src/preview/default-renderers.ts","../src/preview/renderer.ts","../src/preview/preview.ts","../src/preview/css-generator.ts"],"names":["DOMPurify","markdownParser","GFM","Subscript","Superscript","Emoji","foldNodeProp","tree"],"mappings":"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","file":"chunk-7Z3SRTPZ.cjs","sourcesContent":["import { SyntaxNode } from \"@lezer/common\";\r\nimport { ThemeEnum } from \"../editor/utils\";\r\nimport { PreviewContext } from \"./types\";\r\nimport DOMPurify from \"dompurify\";\r\n\r\n/**\r\n * Creates a PreviewContext for rendering\r\n */\r\nexport function createPreviewContext(\r\n doc: string,\r\n theme: ThemeEnum,\r\n renderChildren: (node: SyntaxNode) => string,\r\n sanitizeHtml: boolean = true\r\n): PreviewContext {\r\n return {\r\n doc,\r\n theme,\r\n\r\n sliceDoc(from: number, to: number): string {\r\n return doc.slice(from, to);\r\n },\r\n\r\n sanitize(html: string): string {\r\n if (!sanitizeHtml) return html;\r\n\r\n // DOMPurify works in browser; in Node, it needs jsdom\r\n if (typeof window !== \"undefined\") {\r\n return DOMPurify.sanitize(html);\r\n }\r\n\r\n // Server-side: return as-is (user should sanitize at application level)\r\n // or use isomorphic-dompurify in their setup\r\n return html;\r\n },\r\n\r\n renderChildren,\r\n };\r\n}\r\n","import { NodeRenderer, NodeRendererMap } from \"./types\";\r\n\r\n/**\r\n * Escape HTML special characters\r\n */\r\nexport function escapeHtml(text: string): string {\r\n return text\r\n .replace(/&/g, \"&\")\r\n .replace(/</g, \"<\")\r\n .replace(/>/g, \">\")\r\n .replace(/\"/g, \""\")\r\n .replace(/'/g, \"'\");\r\n}\r\n\r\n// ============================================\r\n// DEFAULT RENDERERS\r\n// ============================================\r\n\r\nconst renderDocument: NodeRenderer = (_node, children) => {\r\n return children;\r\n};\r\n\r\nconst renderParagraph: NodeRenderer = (_node, children) => {\r\n return `<p>${children}</p>\\n`;\r\n};\r\n\r\n/**\r\n * Default node renderers for all markdown node types\r\n */\r\nexport const defaultRenderers: NodeRendererMap = {\r\n // Document structure\r\n Document: renderDocument,\r\n Paragraph: renderParagraph,\r\n};\r\n","import { SyntaxNode } from \"@lezer/common\";\r\nimport { Emoji, GFM, MarkdownConfig, parser as markdownParser, Subscript, Superscript } from \"@lezer/markdown\";\r\n\r\nimport { DraftlyPlugin } from \"../editor/plugin\";\r\nimport { ThemeEnum } from \"../editor/utils\";\r\nimport { createPreviewContext } from \"./context\";\r\nimport { defaultRenderers, escapeHtml } from \"./default-renderers\";\r\nimport { NodeRendererMap, PreviewContext } from \"./types\";\r\nimport { foldNodeProp } from \"@codemirror/language\";\r\n\r\n/**\r\n * Renderer class that walks the syntax tree and produces HTML\r\n */\r\nexport class PreviewRenderer {\r\n private doc: string;\r\n private theme: ThemeEnum;\r\n private plugins: DraftlyPlugin[];\r\n private markdown: MarkdownConfig[];\r\n private sanitizeHtml: boolean;\r\n private renderers: NodeRendererMap;\r\n private ctx: PreviewContext;\r\n\r\n constructor(\r\n doc: string,\r\n plugins: DraftlyPlugin[] = [],\r\n markdown: MarkdownConfig[],\r\n theme: ThemeEnum = ThemeEnum.AUTO,\r\n sanitize: boolean = true\r\n ) {\r\n this.doc = doc;\r\n this.theme = theme;\r\n this.plugins = plugins;\r\n this.markdown = markdown;\r\n this.sanitizeHtml = sanitize;\r\n this.renderers = { ...defaultRenderers };\r\n\r\n // Create context with reference to renderChildren\r\n this.ctx = createPreviewContext(doc, theme, this.renderChildren.bind(this), sanitize);\r\n }\r\n\r\n /**\r\n * Render the document to HTML\r\n */\r\n render(): string {\r\n // Collect markdown extensions from plugins\r\n const extensions = [\r\n ...this.markdown,\r\n ...this.plugins.map((p) => p.getMarkdownConfig()).filter((ext): ext is NonNullable<typeof ext> => ext !== null),\r\n ];\r\n\r\n // Use GFM extensions to match the editor (markdownLanguage includes GFM by default)\r\n // GFM includes: Table, TaskList, Strikethrough, Autolink\r\n const baseParser = markdownParser.configure([\r\n GFM,\r\n Subscript,\r\n Superscript,\r\n Emoji,\r\n {\r\n props: [\r\n foldNodeProp.add({\r\n Table: (tree, state) => ({ from: state.doc.lineAt(tree.from).to, to: tree.to }),\r\n }),\r\n ],\r\n },\r\n ]);\r\n const parser = extensions.length > 0 ? baseParser.configure(extensions) : baseParser;\r\n\r\n // Parse the document\r\n const tree = parser.parse(this.doc);\r\n\r\n // Render from root\r\n return this.renderNode(tree.topNode);\r\n }\r\n\r\n /**\r\n * Render a single node to HTML\r\n */\r\n private renderNode(node: SyntaxNode): string {\r\n // First, let plugins try to render\r\n for (const plugin of this.plugins) {\r\n if (plugin.renderToHTML) {\r\n const children = this.renderChildren(node);\r\n const result = plugin.renderToHTML(node, children, this.ctx);\r\n if (result !== null) {\r\n return result;\r\n }\r\n }\r\n }\r\n\r\n // Use default renderer\r\n const renderer = this.renderers[node.name];\r\n if (renderer) {\r\n const children = this.renderChildren(node);\r\n return renderer(node, children, this.ctx);\r\n }\r\n\r\n // Unknown node - render children or text\r\n if (node.firstChild) {\r\n return this.renderChildren(node);\r\n }\r\n\r\n // Leaf node - return text content\r\n return this.ctx.sliceDoc(node.from, node.to);\r\n }\r\n\r\n /**\r\n * Render all children of a node, including text between nodes\r\n */\r\n private renderChildren(node: SyntaxNode): string {\r\n let result = \"\";\r\n let pos = node.from; // Track position to find text gaps\r\n let child = node.firstChild;\r\n\r\n while (child) {\r\n // Add any text between the last position and this child\r\n if (child.from > pos) {\r\n result += escapeHtml(this.ctx.sliceDoc(pos, child.from));\r\n }\r\n\r\n // Render the child node\r\n result += this.renderNode(child);\r\n\r\n // Update position to end of this child\r\n pos = child.to;\r\n child = child.nextSibling;\r\n }\r\n\r\n // Add any trailing text after the last child\r\n if (pos < node.to) {\r\n result += escapeHtml(this.ctx.sliceDoc(pos, node.to));\r\n }\r\n\r\n return result;\r\n }\r\n}\r\n","import { ThemeEnum } from \"../editor/utils\";\r\nimport { PreviewRenderer } from \"./renderer\";\r\nimport { PreviewConfig } from \"./types\";\r\n\r\n/**\r\n * Render markdown to semantic HTML\r\n *\r\n * @param markdown - Markdown string to render\r\n * @param config - Preview configuration\r\n * @returns HTML string\r\n *\r\n * @example\r\n * ```ts\r\n * import { preview } from 'draftly/preview';\r\n * import { HeadingPlugin, ListPlugin } from 'draftly/plugins';\r\n *\r\n * const html = preview('# Hello World', {\r\n * plugins: [new HeadingPlugin(), new ListPlugin()],\r\n * wrapperClass: 'draftly-preview',\r\n * });\r\n * ```\r\n */\r\nexport function preview(markdown: string, config: PreviewConfig = {}): string {\r\n const {\r\n plugins = [],\r\n markdown: markdownConfig = [],\r\n wrapperClass = \"draftly-preview\",\r\n wrapperTag = \"article\",\r\n sanitize = true,\r\n theme = ThemeEnum.AUTO,\r\n } = config;\r\n\r\n // Create renderer and generate HTML\r\n const renderer = new PreviewRenderer(markdown, plugins, markdownConfig, theme, sanitize);\r\n const content = renderer.render();\r\n\r\n // Wrap in container\r\n const classAttr = wrapperClass ? ` class=\"${wrapperClass}\"` : \"\";\r\n return `<${wrapperTag}${classAttr}>\\n${content}</${wrapperTag}>`;\r\n}\r\n","import { ThemeEnum } from \"../editor/utils\";\r\nimport { GenerateCSSConfig } from \"./types\";\r\n\r\n/**\r\n * Base CSS styles for preview rendering\r\n */\r\nconst baseStyles = ``;\r\n\r\n/**\r\n * Generate CSS for preview rendering\r\n *\r\n * @param config - CSS generation configuration\r\n * @returns CSS string\r\n *\r\n * @example\r\n * ```ts\r\n * import { generateCSS } from 'draftly/preview';\r\n * import { HeadingPlugin, ListPlugin } from 'draftly/plugins';\r\n *\r\n * const css = generateCSS({\r\n * plugins: [new HeadingPlugin(), new ListPlugin()],\r\n * theme: ThemeEnum.AUTO,\r\n * includeBase: true,\r\n * });\r\n * ```\r\n */\r\nexport function generateCSS(config: GenerateCSSConfig = {}): string {\r\n const { plugins = [], theme = ThemeEnum.AUTO, wrapperClass = \"draftly-preview\", includeBase = true } = config;\r\n\r\n const cssChunks: string[] = [];\r\n\r\n // Include base styles\r\n if (includeBase) {\r\n // Replace default wrapper class if custom one is provided\r\n if (wrapperClass !== \"draftly-preview\") {\r\n cssChunks.push(baseStyles.replace(/\\.draftly-preview/g, `.${wrapperClass}`));\r\n } else {\r\n cssChunks.push(baseStyles);\r\n }\r\n }\r\n\r\n // Collect styles from plugins\r\n for (const plugin of plugins) {\r\n const pluginCSS = plugin.getPreviewStyles(theme, wrapperClass);\r\n if (pluginCSS) cssChunks.push(`/* ${plugin.name} - ${plugin.version} */\\n` + pluginCSS);\r\n }\r\n\r\n return cssChunks.join(\"\\n\\n\");\r\n}\r\n"]}
|