draftly 1.0.7 → 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.
Files changed (79) hide show
  1. package/README.md +12 -0
  2. package/dist/chunk-3T55CBNZ.cjs +33 -0
  3. package/dist/chunk-3T55CBNZ.cjs.map +1 -0
  4. package/dist/chunk-5MC4T7JH.cjs +58 -0
  5. package/dist/chunk-5MC4T7JH.cjs.map +1 -0
  6. package/dist/{chunk-72ZYRGRT.cjs → chunk-BWJLMREN.cjs} +11 -9
  7. package/dist/chunk-BWJLMREN.cjs.map +1 -0
  8. package/dist/{chunk-KBQDZ5IW.cjs → chunk-CLW73JRX.cjs} +100 -75
  9. package/dist/chunk-CLW73JRX.cjs.map +1 -0
  10. package/dist/{chunk-DFQYXFOP.js → chunk-EEHILRG5.js} +26 -3
  11. package/dist/chunk-EEHILRG5.js.map +1 -0
  12. package/dist/{chunk-HPSMS2WB.js → chunk-I563H35S.js} +101 -75
  13. package/dist/chunk-I563H35S.js.map +1 -0
  14. package/dist/chunk-IAXF4SJL.js +55 -0
  15. package/dist/chunk-IAXF4SJL.js.map +1 -0
  16. package/dist/chunk-JF3WXXMJ.js +31 -0
  17. package/dist/chunk-JF3WXXMJ.js.map +1 -0
  18. package/dist/{chunk-N3WL3XPB.js → chunk-L2XSK57Y.js} +1761 -478
  19. package/dist/chunk-L2XSK57Y.js.map +1 -0
  20. package/dist/{chunk-KDEDLC3D.cjs → chunk-TBVZEK2H.cjs} +27 -2
  21. package/dist/chunk-TBVZEK2H.cjs.map +1 -0
  22. package/dist/{chunk-2B3A3VSQ.cjs → chunk-W5ALMXG2.cjs} +1808 -504
  23. package/dist/chunk-W5ALMXG2.cjs.map +1 -0
  24. package/dist/{chunk-CG4M4TC7.js → chunk-ZUI3GI3W.js} +7 -5
  25. package/dist/chunk-ZUI3GI3W.js.map +1 -0
  26. package/dist/{draftly-BLnx3uGX.d.cts → draftly-BBL-AdOl.d.cts} +5 -1
  27. package/dist/{draftly-BLnx3uGX.d.ts → draftly-BBL-AdOl.d.ts} +5 -1
  28. package/dist/editor/index.cjs +22 -14
  29. package/dist/editor/index.d.cts +2 -1
  30. package/dist/editor/index.d.ts +2 -1
  31. package/dist/editor/index.js +2 -2
  32. package/dist/index.cjs +65 -39
  33. package/dist/index.d.cts +6 -3
  34. package/dist/index.d.ts +6 -3
  35. package/dist/index.js +6 -4
  36. package/dist/lib/index.cjs +12 -0
  37. package/dist/lib/index.cjs.map +1 -0
  38. package/dist/lib/index.d.cts +16 -0
  39. package/dist/lib/index.d.ts +16 -0
  40. package/dist/lib/index.js +3 -0
  41. package/dist/lib/index.js.map +1 -0
  42. package/dist/plugins/index.cjs +27 -17
  43. package/dist/plugins/index.d.cts +144 -9
  44. package/dist/plugins/index.d.ts +144 -9
  45. package/dist/plugins/index.js +5 -3
  46. package/dist/preview/index.cjs +16 -11
  47. package/dist/preview/index.d.cts +19 -4
  48. package/dist/preview/index.d.ts +19 -4
  49. package/dist/preview/index.js +3 -2
  50. package/package.json +8 -1
  51. package/src/editor/draftly.ts +1 -0
  52. package/src/editor/plugin.ts +5 -1
  53. package/src/editor/theme.ts +1 -0
  54. package/src/editor/utils.ts +31 -0
  55. package/src/index.ts +5 -4
  56. package/src/lib/index.ts +2 -0
  57. package/src/lib/input-handler.ts +45 -0
  58. package/src/plugins/code-plugin.theme.ts +426 -0
  59. package/src/plugins/code-plugin.ts +810 -561
  60. package/src/plugins/emoji-plugin.ts +140 -0
  61. package/src/plugins/index.ts +63 -57
  62. package/src/plugins/inline-plugin.ts +305 -291
  63. package/src/plugins/math-plugin.ts +12 -0
  64. package/src/plugins/table-plugin.ts +900 -0
  65. package/src/preview/context.ts +4 -1
  66. package/src/preview/css-generator.ts +14 -1
  67. package/src/preview/index.ts +9 -1
  68. package/src/preview/preview.ts +2 -1
  69. package/src/preview/renderer.ts +21 -20
  70. package/src/preview/syntax-theme.ts +110 -0
  71. package/src/preview/types.ts +14 -0
  72. package/dist/chunk-2B3A3VSQ.cjs.map +0 -1
  73. package/dist/chunk-72ZYRGRT.cjs.map +0 -1
  74. package/dist/chunk-CG4M4TC7.js.map +0 -1
  75. package/dist/chunk-DFQYXFOP.js.map +0 -1
  76. package/dist/chunk-HPSMS2WB.js.map +0 -1
  77. package/dist/chunk-KBQDZ5IW.cjs.map +0 -1
  78. package/dist/chunk-KDEDLC3D.cjs.map +0 -1
  79. package/dist/chunk-N3WL3XPB.js.map +0 -1
@@ -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 { plugins = [], theme = ThemeEnum.AUTO, wrapperClass = "draftly-preview", includeBase = true } = config;
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);
@@ -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 { PreviewConfig, PreviewContext, GenerateCSSConfig, NodeRenderer, NodeRendererMap } from "./types";
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";
@@ -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
@@ -1,12 +1,14 @@
1
1
  import { SyntaxNode } from "@lezer/common";
2
- import { Emoji, GFM, MarkdownConfig, parser as markdownParser, Subscript, Superscript } from "@lezer/markdown";
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
- // Use GFM extensions to match the editor (markdownLanguage includes GFM by default)
73
- // GFM includes: Table, TaskList, Strikethrough, Autolink
74
- const baseParser = markdownParser.configure([
75
- GFM,
76
- Subscript,
77
- Superscript,
78
- Emoji,
79
- {
80
- props: [
81
- foldNodeProp.add({
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
+ }
@@ -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
  /**