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.
Files changed (68) hide show
  1. package/dist/chunk-2B3A3VSQ.cjs +3382 -0
  2. package/dist/chunk-2B3A3VSQ.cjs.map +1 -0
  3. package/dist/{chunk-ZDSZRRUY.cjs → chunk-72ZYRGRT.cjs} +48 -104
  4. package/dist/chunk-72ZYRGRT.cjs.map +1 -0
  5. package/dist/{chunk-MOG6E2LY.js → chunk-CG4M4TC7.js} +46 -103
  6. package/dist/chunk-CG4M4TC7.js.map +1 -0
  7. package/dist/{chunk-LCQALOEI.js → chunk-DFQYXFOP.js} +36 -3
  8. package/dist/chunk-DFQYXFOP.js.map +1 -0
  9. package/dist/{chunk-6LQ2VR4I.js → chunk-HPSMS2WB.js} +38 -22
  10. package/dist/chunk-HPSMS2WB.js.map +1 -0
  11. package/dist/{chunk-7Z3SRTPZ.cjs → chunk-KBQDZ5IW.cjs} +38 -22
  12. package/dist/chunk-KBQDZ5IW.cjs.map +1 -0
  13. package/dist/{chunk-RV2SYFA6.cjs → chunk-KDEDLC3D.cjs} +36 -2
  14. package/dist/chunk-KDEDLC3D.cjs.map +1 -0
  15. package/dist/chunk-N3WL3XPB.js +3360 -0
  16. package/dist/chunk-N3WL3XPB.js.map +1 -0
  17. package/dist/{draftly-Bxu_H4nw.d.ts → draftly-BLnx3uGX.d.cts} +12 -6
  18. package/dist/{draftly-Bxu_H4nw.d.cts → draftly-BLnx3uGX.d.ts} +12 -6
  19. package/dist/editor/index.cjs +20 -12
  20. package/dist/editor/index.d.cts +3 -2
  21. package/dist/editor/index.d.ts +3 -2
  22. package/dist/editor/index.js +2 -2
  23. package/dist/index.cjs +59 -27
  24. package/dist/index.d.cts +3 -3
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.js +4 -4
  27. package/dist/plugins/index.cjs +35 -11
  28. package/dist/plugins/index.d.cts +321 -3
  29. package/dist/plugins/index.d.ts +321 -3
  30. package/dist/plugins/index.js +3 -3
  31. package/dist/preview/index.cjs +7 -7
  32. package/dist/preview/index.d.cts +9 -4
  33. package/dist/preview/index.d.ts +9 -4
  34. package/dist/preview/index.js +2 -2
  35. package/package.json +2 -1
  36. package/src/editor/draftly.ts +9 -13
  37. package/src/editor/plugin.ts +4 -1
  38. package/src/editor/theme.ts +34 -1
  39. package/src/editor/utils.ts +49 -0
  40. package/src/editor/view-plugin.ts +6 -131
  41. package/src/plugins/code-plugin.ts +1119 -0
  42. package/src/plugins/heading-plugin.ts +23 -11
  43. package/src/plugins/hr-plugin.ts +102 -0
  44. package/src/plugins/image-plugin.ts +96 -2
  45. package/src/plugins/index.ts +57 -39
  46. package/src/plugins/inline-plugin.ts +125 -6
  47. package/src/plugins/link-plugin.ts +509 -0
  48. package/src/plugins/list-plugin.ts +116 -2
  49. package/src/plugins/math-plugin.ts +5 -1
  50. package/src/plugins/mermaid-plugin.ts +500 -0
  51. package/src/plugins/paragraph-plugin.ts +38 -0
  52. package/src/plugins/quote-plugin.ts +146 -0
  53. package/src/preview/context.ts +1 -1
  54. package/src/preview/css-generator.ts +3 -1
  55. package/src/preview/default-renderers.ts +0 -5
  56. package/src/preview/preview.ts +2 -2
  57. package/src/preview/renderer.ts +34 -12
  58. package/src/preview/types.ts +1 -1
  59. package/dist/chunk-6LQ2VR4I.js.map +0 -1
  60. package/dist/chunk-7Z3SRTPZ.cjs.map +0 -1
  61. package/dist/chunk-GA6NYY77.cjs +0 -1400
  62. package/dist/chunk-GA6NYY77.cjs.map +0 -1
  63. package/dist/chunk-LCQALOEI.js.map +0 -1
  64. package/dist/chunk-MOG6E2LY.js.map +0 -1
  65. package/dist/chunk-RV2SYFA6.cjs.map +0 -1
  66. package/dist/chunk-TKZNKWGF.js +0 -1385
  67. package/dist/chunk-TKZNKWGF.js.map +0 -1
  68. package/dist/chunk-ZDSZRRUY.cjs.map +0 -1
@@ -47,7 +47,40 @@ function selectionOverlapsRange(view, from, to) {
47
47
  }
48
48
  return false;
49
49
  }
50
+ function toggleMarkdownStyle(marker) {
51
+ return (view) => {
52
+ const { state } = view;
53
+ const { from, to, empty } = state.selection.main;
54
+ const selectedText = state.sliceDoc(from, to);
55
+ const markerLen = marker.length;
56
+ const beforeFrom = Math.max(0, from - markerLen);
57
+ const afterTo = Math.min(state.doc.length, to + markerLen);
58
+ const textBefore = state.sliceDoc(beforeFrom, from);
59
+ const textAfter = state.sliceDoc(to, afterTo);
60
+ const isWrapped = textBefore === marker && textAfter === marker;
61
+ if (isWrapped) {
62
+ view.dispatch({
63
+ changes: [
64
+ { from: beforeFrom, to: from, insert: "" },
65
+ { from: to, to: afterTo, insert: "" }
66
+ ],
67
+ selection: { anchor: beforeFrom, head: beforeFrom + selectedText.length }
68
+ });
69
+ } else if (empty) {
70
+ view.dispatch({
71
+ changes: { from, to, insert: marker + marker },
72
+ selection: { anchor: from + markerLen }
73
+ });
74
+ } else {
75
+ view.dispatch({
76
+ changes: { from, to, insert: marker + selectedText + marker },
77
+ selection: { anchor: from + markerLen, head: to + markerLen }
78
+ });
79
+ }
80
+ return true;
81
+ };
82
+ }
50
83
 
51
- export { ThemeEnum, createTheme, cursorInRange, deepMerge, selectionOverlapsRange };
52
- //# sourceMappingURL=chunk-LCQALOEI.js.map
53
- //# sourceMappingURL=chunk-LCQALOEI.js.map
84
+ export { ThemeEnum, createTheme, cursorInRange, deepMerge, selectionOverlapsRange, toggleMarkdownStyle };
85
+ //# sourceMappingURL=chunk-DFQYXFOP.js.map
86
+ //# sourceMappingURL=chunk-DFQYXFOP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/editor/utils.ts"],"names":["ThemeEnum"],"mappings":"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","file":"chunk-DFQYXFOP.js","sourcesContent":["import { EditorView } from \"@codemirror/view\";\nimport { StyleSpec } from \"style-mod\";\n\n/**\n * Deep merge two objects\n * @param a - First object\n * @param b - Second object\n * @returns Merged object\n */\nexport function deepMerge<T>(a: T, b?: T): T {\n const result = { ...a };\n\n if (!b) {\n return result;\n }\n\n for (const key in b as T) {\n if (b[key] && typeof b[key] === \"object\" && !Array.isArray(b[key]) && typeof a[key] === \"object\") {\n result[key] = deepMerge(a[key], b[key]);\n } else {\n result[key] = b[key];\n }\n }\n\n return result;\n}\n\n/**\n * Theme style\n */\nexport type ThemeStyle = {\n [selector: string]: StyleSpec;\n};\n\n/**\n * Theme Enum\n */\nexport enum ThemeEnum {\n DARK = \"dark\",\n LIGHT = \"light\",\n AUTO = \"auto\",\n}\n\n/**\n * Function to create the themes\n *\n * @param defaultTheme - Default theme -- Always applied\n * @param darkTheme - Dark theme -- Applied when theme is \"dark\" or \"auto\" and system is dark\n * @param lightTheme - Light theme -- Applied when theme is \"light\" or \"auto\" and system is light\n * @returns Theme function\n */\nexport function createTheme({\n default: defaultTheme,\n dark: darkTheme,\n light: lightTheme,\n}: {\n default: ThemeStyle;\n dark?: ThemeStyle;\n light?: ThemeStyle;\n}): (theme: ThemeEnum) => ThemeStyle {\n return (theme: ThemeEnum) => {\n let style: ThemeStyle = defaultTheme;\n\n if (theme === ThemeEnum.DARK) {\n style = deepMerge(style, darkTheme);\n }\n\n if (theme === ThemeEnum.LIGHT) {\n style = deepMerge(style, lightTheme);\n }\n\n return style;\n };\n}\n\n/**\n * Check if cursor is within the given range\n */\nexport function cursorInRange(view: EditorView, from: number, to: number): boolean {\n const selection = view.state.selection.main;\n return selection.from <= to && selection.to >= from;\n}\n\n/**\n * Check if any selection overlaps with the given range\n */\nexport function selectionOverlapsRange(view: EditorView, from: number, to: number): boolean {\n for (const range of view.state.selection.ranges) {\n if (range.from <= to && range.to >= from) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Toggle markdown style on selection or insert markers at cursor\n * @param marker - The markdown marker (e.g., \"**\" for bold, \"*\" for italic)\n * @returns Command function for EditorView\n */\nexport function toggleMarkdownStyle(marker: string): (view: EditorView) => boolean {\n return (view: EditorView) => {\n const { state } = view;\n const { from, to, empty } = state.selection.main;\n\n // Get selected text\n const selectedText = state.sliceDoc(from, to);\n\n // Check if already wrapped with markers\n const markerLen = marker.length;\n const beforeFrom = Math.max(0, from - markerLen);\n const afterTo = Math.min(state.doc.length, to + markerLen);\n const textBefore = state.sliceDoc(beforeFrom, from);\n const textAfter = state.sliceDoc(to, afterTo);\n\n const isWrapped = textBefore === marker && textAfter === marker;\n\n if (isWrapped) {\n // Remove markers\n view.dispatch({\n changes: [\n { from: beforeFrom, to: from, insert: \"\" },\n { from: to, to: afterTo, insert: \"\" },\n ],\n selection: { anchor: beforeFrom, head: beforeFrom + selectedText.length },\n });\n } else if (empty) {\n // No selection - insert markers and place cursor between them\n view.dispatch({\n changes: { from, to, insert: marker + marker },\n selection: { anchor: from + markerLen },\n });\n } else {\n // Wrap selection with markers\n view.dispatch({\n changes: { from, to, insert: marker + selectedText + marker },\n selection: { anchor: from + markerLen, head: to + markerLen },\n });\n }\n\n return true;\n };\n}\n"]}
@@ -28,14 +28,9 @@ function escapeHtml(text) {
28
28
  var renderDocument = (_node, children) => {
29
29
  return children;
30
30
  };
31
- var renderParagraph = (_node, children) => {
32
- return `<p>${children}</p>
33
- `;
34
- };
35
31
  var defaultRenderers = {
36
32
  // Document structure
37
- Document: renderDocument,
38
- Paragraph: renderParagraph
33
+ Document: renderDocument
39
34
  };
40
35
  var PreviewRenderer = class {
41
36
  doc;
@@ -45,6 +40,7 @@ var PreviewRenderer = class {
45
40
  sanitizeHtml;
46
41
  renderers;
47
42
  ctx;
43
+ nodeToPlugins;
48
44
  constructor(doc, plugins = [], markdown, theme = "auto" /* AUTO */, sanitize = true) {
49
45
  this.doc = doc;
50
46
  this.theme = theme;
@@ -53,11 +49,28 @@ var PreviewRenderer = class {
53
49
  this.sanitizeHtml = sanitize;
54
50
  this.renderers = { ...defaultRenderers };
55
51
  this.ctx = createPreviewContext(doc, theme, this.renderChildren.bind(this), sanitize);
52
+ this.nodeToPlugins = this.buildNodePluginMap();
53
+ }
54
+ /**
55
+ * Build a map from node names to plugins that handle them
56
+ */
57
+ buildNodePluginMap() {
58
+ const map = /* @__PURE__ */ new Map();
59
+ for (const plugin of this.plugins) {
60
+ if (plugin.renderToHTML && plugin.requiredNodes.length > 0) {
61
+ for (const nodeName of plugin.requiredNodes) {
62
+ const list = map.get(nodeName) || [];
63
+ list.push(plugin);
64
+ map.set(nodeName, list);
65
+ }
66
+ }
67
+ }
68
+ return map;
56
69
  }
57
70
  /**
58
71
  * Render the document to HTML
59
72
  */
60
- render() {
73
+ async render() {
61
74
  const extensions = [
62
75
  ...this.markdown,
63
76
  ...this.plugins.map((p) => p.getMarkdownConfig()).filter((ext) => ext !== null)
@@ -77,16 +90,17 @@ var PreviewRenderer = class {
77
90
  ]);
78
91
  const parser$1 = extensions.length > 0 ? baseParser.configure(extensions) : baseParser;
79
92
  const tree = parser$1.parse(this.doc);
80
- return this.renderNode(tree.topNode);
93
+ return await this.renderNode(tree.topNode);
81
94
  }
82
95
  /**
83
96
  * Render a single node to HTML
84
97
  */
85
- renderNode(node) {
86
- for (const plugin of this.plugins) {
87
- if (plugin.renderToHTML) {
88
- const children = this.renderChildren(node);
89
- const result = plugin.renderToHTML(node, children, this.ctx);
98
+ async renderNode(node) {
99
+ const plugins = this.nodeToPlugins.get(node.name);
100
+ if (plugins) {
101
+ for (const plugin of plugins) {
102
+ const children = await this.renderChildren(node);
103
+ const result = await plugin.renderToHTML(node, children, this.ctx);
90
104
  if (result !== null) {
91
105
  return result;
92
106
  }
@@ -94,18 +108,18 @@ var PreviewRenderer = class {
94
108
  }
95
109
  const renderer = this.renderers[node.name];
96
110
  if (renderer) {
97
- const children = this.renderChildren(node);
111
+ const children = await this.renderChildren(node);
98
112
  return renderer(node, children, this.ctx);
99
113
  }
100
114
  if (node.firstChild) {
101
- return this.renderChildren(node);
115
+ return await this.renderChildren(node);
102
116
  }
103
117
  return this.ctx.sliceDoc(node.from, node.to);
104
118
  }
105
119
  /**
106
120
  * Render all children of a node, including text between nodes
107
121
  */
108
- renderChildren(node) {
122
+ async renderChildren(node) {
109
123
  let result = "";
110
124
  let pos = node.from;
111
125
  let child = node.firstChild;
@@ -113,7 +127,7 @@ var PreviewRenderer = class {
113
127
  if (child.from > pos) {
114
128
  result += escapeHtml(this.ctx.sliceDoc(pos, child.from));
115
129
  }
116
- result += this.renderNode(child);
130
+ result += await this.renderNode(child);
117
131
  pos = child.to;
118
132
  child = child.nextSibling;
119
133
  }
@@ -125,7 +139,7 @@ var PreviewRenderer = class {
125
139
  };
126
140
 
127
141
  // src/preview/preview.ts
128
- function preview(markdown, config = {}) {
142
+ async function preview(markdown, config = {}) {
129
143
  const {
130
144
  plugins = [],
131
145
  markdown: markdownConfig = [],
@@ -135,14 +149,16 @@ function preview(markdown, config = {}) {
135
149
  theme = "auto" /* AUTO */
136
150
  } = config;
137
151
  const renderer = new PreviewRenderer(markdown, plugins, markdownConfig, theme, sanitize);
138
- const content = renderer.render();
152
+ const content = await renderer.render();
139
153
  const classAttr = wrapperClass ? ` class="${wrapperClass}"` : "";
140
154
  return `<${wrapperTag}${classAttr}>
141
155
  ${content}</${wrapperTag}>`;
142
156
  }
143
157
 
144
158
  // src/preview/css-generator.ts
145
- var baseStyles = ``;
159
+ var baseStyles = `.draftly-preview {
160
+ padding: 0 0.5rem;
161
+ }`;
146
162
  function generateCSS(config = {}) {
147
163
  const { plugins = [], theme = "auto" /* AUTO */, wrapperClass = "draftly-preview", includeBase = true } = config;
148
164
  const cssChunks = [];
@@ -162,5 +178,5 @@ function generateCSS(config = {}) {
162
178
  }
163
179
 
164
180
  export { PreviewRenderer, defaultRenderers, escapeHtml, generateCSS, preview };
165
- //# sourceMappingURL=chunk-6LQ2VR4I.js.map
166
- //# sourceMappingURL=chunk-6LQ2VR4I.js.map
181
+ //# sourceMappingURL=chunk-HPSMS2WB.js.map
182
+ //# sourceMappingURL=chunk-HPSMS2WB.js.map
@@ -0,0 +1 @@
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-HPSMS2WB.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) => Promise<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, \"&amp;\")\r\n .replace(/</g, \"&lt;\")\r\n .replace(/>/g, \"&gt;\")\r\n .replace(/\"/g, \"&quot;\")\r\n .replace(/'/g, \"&#39;\");\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\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};\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 private nodeToPlugins: Map<string, DraftlyPlugin[]>;\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 // Build node-to-plugin map for O(1) lookup\r\n this.nodeToPlugins = this.buildNodePluginMap();\r\n }\r\n\r\n /**\r\n * Build a map from node names to plugins that handle them\r\n */\r\n private buildNodePluginMap(): Map<string, DraftlyPlugin[]> {\r\n const map = new Map<string, DraftlyPlugin[]>();\r\n for (const plugin of this.plugins) {\r\n if (plugin.renderToHTML && plugin.requiredNodes.length > 0) {\r\n for (const nodeName of plugin.requiredNodes) {\r\n const list = map.get(nodeName) || [];\r\n list.push(plugin);\r\n map.set(nodeName, list);\r\n }\r\n }\r\n }\r\n return map;\r\n }\r\n\r\n /**\r\n * Render the document to HTML\r\n */\r\n async render(): Promise<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 await this.renderNode(tree.topNode);\r\n }\r\n\r\n /**\r\n * Render a single node to HTML\r\n */\r\n private async renderNode(node: SyntaxNode): Promise<string> {\r\n // Get plugins that handle this node type (O(1) lookup)\r\n const plugins = this.nodeToPlugins.get(node.name);\r\n if (plugins) {\r\n for (const plugin of plugins) {\r\n const children = await this.renderChildren(node);\r\n const result = await 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 = await 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 await 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 async renderChildren(node: SyntaxNode): Promise<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 += await 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 async function preview(markdown: string, config: PreviewConfig = {}): Promise<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 = await 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 = `.draftly-preview {\r\n padding: 0 0.5rem;\r\n}`;\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"]}
@@ -34,14 +34,9 @@ function escapeHtml(text) {
34
34
  var renderDocument = (_node, children) => {
35
35
  return children;
36
36
  };
37
- var renderParagraph = (_node, children) => {
38
- return `<p>${children}</p>
39
- `;
40
- };
41
37
  var defaultRenderers = {
42
38
  // Document structure
43
- Document: renderDocument,
44
- Paragraph: renderParagraph
39
+ Document: renderDocument
45
40
  };
46
41
  var PreviewRenderer = class {
47
42
  doc;
@@ -51,6 +46,7 @@ var PreviewRenderer = class {
51
46
  sanitizeHtml;
52
47
  renderers;
53
48
  ctx;
49
+ nodeToPlugins;
54
50
  constructor(doc, plugins = [], markdown, theme = "auto" /* AUTO */, sanitize = true) {
55
51
  this.doc = doc;
56
52
  this.theme = theme;
@@ -59,11 +55,28 @@ var PreviewRenderer = class {
59
55
  this.sanitizeHtml = sanitize;
60
56
  this.renderers = { ...defaultRenderers };
61
57
  this.ctx = createPreviewContext(doc, theme, this.renderChildren.bind(this), sanitize);
58
+ this.nodeToPlugins = this.buildNodePluginMap();
59
+ }
60
+ /**
61
+ * Build a map from node names to plugins that handle them
62
+ */
63
+ buildNodePluginMap() {
64
+ const map = /* @__PURE__ */ new Map();
65
+ for (const plugin of this.plugins) {
66
+ if (plugin.renderToHTML && plugin.requiredNodes.length > 0) {
67
+ for (const nodeName of plugin.requiredNodes) {
68
+ const list = map.get(nodeName) || [];
69
+ list.push(plugin);
70
+ map.set(nodeName, list);
71
+ }
72
+ }
73
+ }
74
+ return map;
62
75
  }
63
76
  /**
64
77
  * Render the document to HTML
65
78
  */
66
- render() {
79
+ async render() {
67
80
  const extensions = [
68
81
  ...this.markdown,
69
82
  ...this.plugins.map((p) => p.getMarkdownConfig()).filter((ext) => ext !== null)
@@ -83,16 +96,17 @@ var PreviewRenderer = class {
83
96
  ]);
84
97
  const parser = extensions.length > 0 ? baseParser.configure(extensions) : baseParser;
85
98
  const tree = parser.parse(this.doc);
86
- return this.renderNode(tree.topNode);
99
+ return await this.renderNode(tree.topNode);
87
100
  }
88
101
  /**
89
102
  * Render a single node to HTML
90
103
  */
91
- renderNode(node) {
92
- for (const plugin of this.plugins) {
93
- if (plugin.renderToHTML) {
94
- const children = this.renderChildren(node);
95
- const result = plugin.renderToHTML(node, children, this.ctx);
104
+ async renderNode(node) {
105
+ const plugins = this.nodeToPlugins.get(node.name);
106
+ if (plugins) {
107
+ for (const plugin of plugins) {
108
+ const children = await this.renderChildren(node);
109
+ const result = await plugin.renderToHTML(node, children, this.ctx);
96
110
  if (result !== null) {
97
111
  return result;
98
112
  }
@@ -100,18 +114,18 @@ var PreviewRenderer = class {
100
114
  }
101
115
  const renderer = this.renderers[node.name];
102
116
  if (renderer) {
103
- const children = this.renderChildren(node);
117
+ const children = await this.renderChildren(node);
104
118
  return renderer(node, children, this.ctx);
105
119
  }
106
120
  if (node.firstChild) {
107
- return this.renderChildren(node);
121
+ return await this.renderChildren(node);
108
122
  }
109
123
  return this.ctx.sliceDoc(node.from, node.to);
110
124
  }
111
125
  /**
112
126
  * Render all children of a node, including text between nodes
113
127
  */
114
- renderChildren(node) {
128
+ async renderChildren(node) {
115
129
  let result = "";
116
130
  let pos = node.from;
117
131
  let child = node.firstChild;
@@ -119,7 +133,7 @@ var PreviewRenderer = class {
119
133
  if (child.from > pos) {
120
134
  result += escapeHtml(this.ctx.sliceDoc(pos, child.from));
121
135
  }
122
- result += this.renderNode(child);
136
+ result += await this.renderNode(child);
123
137
  pos = child.to;
124
138
  child = child.nextSibling;
125
139
  }
@@ -131,7 +145,7 @@ var PreviewRenderer = class {
131
145
  };
132
146
 
133
147
  // src/preview/preview.ts
134
- function preview(markdown, config = {}) {
148
+ async function preview(markdown, config = {}) {
135
149
  const {
136
150
  plugins = [],
137
151
  markdown: markdownConfig = [],
@@ -141,14 +155,16 @@ function preview(markdown, config = {}) {
141
155
  theme = "auto" /* AUTO */
142
156
  } = config;
143
157
  const renderer = new PreviewRenderer(markdown, plugins, markdownConfig, theme, sanitize);
144
- const content = renderer.render();
158
+ const content = await renderer.render();
145
159
  const classAttr = wrapperClass ? ` class="${wrapperClass}"` : "";
146
160
  return `<${wrapperTag}${classAttr}>
147
161
  ${content}</${wrapperTag}>`;
148
162
  }
149
163
 
150
164
  // src/preview/css-generator.ts
151
- var baseStyles = ``;
165
+ var baseStyles = `.draftly-preview {
166
+ padding: 0 0.5rem;
167
+ }`;
152
168
  function generateCSS(config = {}) {
153
169
  const { plugins = [], theme = "auto" /* AUTO */, wrapperClass = "draftly-preview", includeBase = true } = config;
154
170
  const cssChunks = [];
@@ -172,5 +188,5 @@ exports.defaultRenderers = defaultRenderers;
172
188
  exports.escapeHtml = escapeHtml;
173
189
  exports.generateCSS = generateCSS;
174
190
  exports.preview = preview;
175
- //# sourceMappingURL=chunk-7Z3SRTPZ.cjs.map
176
- //# sourceMappingURL=chunk-7Z3SRTPZ.cjs.map
191
+ //# sourceMappingURL=chunk-KBQDZ5IW.cjs.map
192
+ //# sourceMappingURL=chunk-KBQDZ5IW.cjs.map
@@ -0,0 +1 @@
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-KBQDZ5IW.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) => Promise<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, \"&amp;\")\r\n .replace(/</g, \"&lt;\")\r\n .replace(/>/g, \"&gt;\")\r\n .replace(/\"/g, \"&quot;\")\r\n .replace(/'/g, \"&#39;\");\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\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};\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 private nodeToPlugins: Map<string, DraftlyPlugin[]>;\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 // Build node-to-plugin map for O(1) lookup\r\n this.nodeToPlugins = this.buildNodePluginMap();\r\n }\r\n\r\n /**\r\n * Build a map from node names to plugins that handle them\r\n */\r\n private buildNodePluginMap(): Map<string, DraftlyPlugin[]> {\r\n const map = new Map<string, DraftlyPlugin[]>();\r\n for (const plugin of this.plugins) {\r\n if (plugin.renderToHTML && plugin.requiredNodes.length > 0) {\r\n for (const nodeName of plugin.requiredNodes) {\r\n const list = map.get(nodeName) || [];\r\n list.push(plugin);\r\n map.set(nodeName, list);\r\n }\r\n }\r\n }\r\n return map;\r\n }\r\n\r\n /**\r\n * Render the document to HTML\r\n */\r\n async render(): Promise<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 await this.renderNode(tree.topNode);\r\n }\r\n\r\n /**\r\n * Render a single node to HTML\r\n */\r\n private async renderNode(node: SyntaxNode): Promise<string> {\r\n // Get plugins that handle this node type (O(1) lookup)\r\n const plugins = this.nodeToPlugins.get(node.name);\r\n if (plugins) {\r\n for (const plugin of plugins) {\r\n const children = await this.renderChildren(node);\r\n const result = await 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 = await 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 await 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 async renderChildren(node: SyntaxNode): Promise<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 += await 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 async function preview(markdown: string, config: PreviewConfig = {}): Promise<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 = await 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 = `.draftly-preview {\r\n padding: 0 0.5rem;\r\n}`;\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"]}
@@ -49,11 +49,45 @@ function selectionOverlapsRange(view, from, to) {
49
49
  }
50
50
  return false;
51
51
  }
52
+ function toggleMarkdownStyle(marker) {
53
+ return (view) => {
54
+ const { state } = view;
55
+ const { from, to, empty } = state.selection.main;
56
+ const selectedText = state.sliceDoc(from, to);
57
+ const markerLen = marker.length;
58
+ const beforeFrom = Math.max(0, from - markerLen);
59
+ const afterTo = Math.min(state.doc.length, to + markerLen);
60
+ const textBefore = state.sliceDoc(beforeFrom, from);
61
+ const textAfter = state.sliceDoc(to, afterTo);
62
+ const isWrapped = textBefore === marker && textAfter === marker;
63
+ if (isWrapped) {
64
+ view.dispatch({
65
+ changes: [
66
+ { from: beforeFrom, to: from, insert: "" },
67
+ { from: to, to: afterTo, insert: "" }
68
+ ],
69
+ selection: { anchor: beforeFrom, head: beforeFrom + selectedText.length }
70
+ });
71
+ } else if (empty) {
72
+ view.dispatch({
73
+ changes: { from, to, insert: marker + marker },
74
+ selection: { anchor: from + markerLen }
75
+ });
76
+ } else {
77
+ view.dispatch({
78
+ changes: { from, to, insert: marker + selectedText + marker },
79
+ selection: { anchor: from + markerLen, head: to + markerLen }
80
+ });
81
+ }
82
+ return true;
83
+ };
84
+ }
52
85
 
53
86
  exports.ThemeEnum = ThemeEnum;
54
87
  exports.createTheme = createTheme;
55
88
  exports.cursorInRange = cursorInRange;
56
89
  exports.deepMerge = deepMerge;
57
90
  exports.selectionOverlapsRange = selectionOverlapsRange;
58
- //# sourceMappingURL=chunk-RV2SYFA6.cjs.map
59
- //# sourceMappingURL=chunk-RV2SYFA6.cjs.map
91
+ exports.toggleMarkdownStyle = toggleMarkdownStyle;
92
+ //# sourceMappingURL=chunk-KDEDLC3D.cjs.map
93
+ //# sourceMappingURL=chunk-KDEDLC3D.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/editor/utils.ts"],"names":["ThemeEnum"],"mappings":";;;AASO,SAAS,SAAA,CAAa,GAAM,CAAA,EAAU;AAC3C,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,CAAA,EAAE;AAEtB,EAAA,IAAI,CAAC,CAAA,EAAG;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,KAAA,MAAW,OAAO,CAAA,EAAQ;AACxB,IAAA,IAAI,EAAE,GAAG,CAAA,IAAK,OAAO,CAAA,CAAE,GAAG,MAAM,QAAA,IAAY,CAAC,MAAM,OAAA,CAAQ,CAAA,CAAE,GAAG,CAAC,CAAA,IAAK,OAAO,CAAA,CAAE,GAAG,MAAM,QAAA,EAAU;AAChG,MAAA,MAAA,CAAO,GAAG,IAAI,SAAA,CAAU,CAAA,CAAE,GAAG,CAAA,EAAG,CAAA,CAAE,GAAG,CAAC,CAAA;AAAA,IACxC,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,CAAA,CAAE,GAAG,CAAA;AAAA,IACrB;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAYO,IAAK,SAAA,qBAAAA,UAAAA,KAAL;AACL,EAAAA,WAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,WAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,WAAA,MAAA,CAAA,GAAO,MAAA;AAHG,EAAA,OAAAA,UAAAA;AAAA,CAAA,EAAA,SAAA,IAAA,EAAA;AAcL,SAAS,WAAA,CAAY;AAAA,EAC1B,OAAA,EAAS,YAAA;AAAA,EACT,IAAA,EAAM,SAAA;AAAA,EACN,KAAA,EAAO;AACT,CAAA,EAIqC;AACnC,EAAA,OAAO,CAAC,KAAA,KAAqB;AAC3B,IAAA,IAAI,KAAA,GAAoB,YAAA;AAExB,IAAA,IAAI,UAAU,MAAA,aAAgB;AAC5B,MAAA,KAAA,GAAQ,SAAA,CAAU,OAAO,SAAS,CAAA;AAAA,IACpC;AAEA,IAAA,IAAI,UAAU,OAAA,cAAiB;AAC7B,MAAA,KAAA,GAAQ,SAAA,CAAU,OAAO,UAAU,CAAA;AAAA,IACrC;AAEA,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AACF;AAKO,SAAS,aAAA,CAAc,IAAA,EAAkB,IAAA,EAAc,EAAA,EAAqB;AACjF,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,IAAA;AACvC,EAAA,OAAO,SAAA,CAAU,IAAA,IAAQ,EAAA,IAAM,SAAA,CAAU,EAAA,IAAM,IAAA;AACjD;AAKO,SAAS,sBAAA,CAAuB,IAAA,EAAkB,IAAA,EAAc,EAAA,EAAqB;AAC1F,EAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,MAAA,EAAQ;AAC/C,IAAA,IAAI,KAAA,CAAM,IAAA,IAAQ,EAAA,IAAM,KAAA,CAAM,MAAM,IAAA,EAAM;AACxC,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAOO,SAAS,oBAAoB,MAAA,EAA+C;AACjF,EAAA,OAAO,CAAC,IAAA,KAAqB;AAC3B,IAAA,MAAM,EAAE,OAAM,GAAI,IAAA;AAClB,IAAA,MAAM,EAAE,IAAA,EAAM,EAAA,EAAI,KAAA,EAAM,GAAI,MAAM,SAAA,CAAU,IAAA;AAG5C,IAAA,MAAM,YAAA,GAAe,KAAA,CAAM,QAAA,CAAS,IAAA,EAAM,EAAE,CAAA;AAG5C,IAAA,MAAM,YAAY,MAAA,CAAO,MAAA;AACzB,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAO,SAAS,CAAA;AAC/C,IAAA,MAAM,UAAU,IAAA,CAAK,GAAA,CAAI,MAAM,GAAA,CAAI,MAAA,EAAQ,KAAK,SAAS,CAAA;AACzD,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,QAAA,CAAS,UAAA,EAAY,IAAI,CAAA;AAClD,IAAA,MAAM,SAAA,GAAY,KAAA,CAAM,QAAA,CAAS,EAAA,EAAI,OAAO,CAAA;AAE5C,IAAA,MAAM,SAAA,GAAY,UAAA,KAAe,MAAA,IAAU,SAAA,KAAc,MAAA;AAEzD,IAAA,IAAI,SAAA,EAAW;AAEb,MAAA,IAAA,CAAK,QAAA,CAAS;AAAA,QACZ,OAAA,EAAS;AAAA,UACP,EAAE,IAAA,EAAM,UAAA,EAAY,EAAA,EAAI,IAAA,EAAM,QAAQ,EAAA,EAAG;AAAA,UACzC,EAAE,IAAA,EAAM,EAAA,EAAI,EAAA,EAAI,OAAA,EAAS,QAAQ,EAAA;AAAG,SACtC;AAAA,QACA,WAAW,EAAE,MAAA,EAAQ,YAAY,IAAA,EAAM,UAAA,GAAa,aAAa,MAAA;AAAO,OACzE,CAAA;AAAA,IACH,WAAW,KAAA,EAAO;AAEhB,MAAA,IAAA,CAAK,QAAA,CAAS;AAAA,QACZ,SAAS,EAAE,IAAA,EAAM,EAAA,EAAI,MAAA,EAAQ,SAAS,MAAA,EAAO;AAAA,QAC7C,SAAA,EAAW,EAAE,MAAA,EAAQ,IAAA,GAAO,SAAA;AAAU,OACvC,CAAA;AAAA,IACH,CAAA,MAAO;AAEL,MAAA,IAAA,CAAK,QAAA,CAAS;AAAA,QACZ,SAAS,EAAE,IAAA,EAAM,IAAI,MAAA,EAAQ,MAAA,GAAS,eAAe,MAAA,EAAO;AAAA,QAC5D,WAAW,EAAE,MAAA,EAAQ,OAAO,SAAA,EAAW,IAAA,EAAM,KAAK,SAAA;AAAU,OAC7D,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AACF","file":"chunk-KDEDLC3D.cjs","sourcesContent":["import { EditorView } from \"@codemirror/view\";\nimport { StyleSpec } from \"style-mod\";\n\n/**\n * Deep merge two objects\n * @param a - First object\n * @param b - Second object\n * @returns Merged object\n */\nexport function deepMerge<T>(a: T, b?: T): T {\n const result = { ...a };\n\n if (!b) {\n return result;\n }\n\n for (const key in b as T) {\n if (b[key] && typeof b[key] === \"object\" && !Array.isArray(b[key]) && typeof a[key] === \"object\") {\n result[key] = deepMerge(a[key], b[key]);\n } else {\n result[key] = b[key];\n }\n }\n\n return result;\n}\n\n/**\n * Theme style\n */\nexport type ThemeStyle = {\n [selector: string]: StyleSpec;\n};\n\n/**\n * Theme Enum\n */\nexport enum ThemeEnum {\n DARK = \"dark\",\n LIGHT = \"light\",\n AUTO = \"auto\",\n}\n\n/**\n * Function to create the themes\n *\n * @param defaultTheme - Default theme -- Always applied\n * @param darkTheme - Dark theme -- Applied when theme is \"dark\" or \"auto\" and system is dark\n * @param lightTheme - Light theme -- Applied when theme is \"light\" or \"auto\" and system is light\n * @returns Theme function\n */\nexport function createTheme({\n default: defaultTheme,\n dark: darkTheme,\n light: lightTheme,\n}: {\n default: ThemeStyle;\n dark?: ThemeStyle;\n light?: ThemeStyle;\n}): (theme: ThemeEnum) => ThemeStyle {\n return (theme: ThemeEnum) => {\n let style: ThemeStyle = defaultTheme;\n\n if (theme === ThemeEnum.DARK) {\n style = deepMerge(style, darkTheme);\n }\n\n if (theme === ThemeEnum.LIGHT) {\n style = deepMerge(style, lightTheme);\n }\n\n return style;\n };\n}\n\n/**\n * Check if cursor is within the given range\n */\nexport function cursorInRange(view: EditorView, from: number, to: number): boolean {\n const selection = view.state.selection.main;\n return selection.from <= to && selection.to >= from;\n}\n\n/**\n * Check if any selection overlaps with the given range\n */\nexport function selectionOverlapsRange(view: EditorView, from: number, to: number): boolean {\n for (const range of view.state.selection.ranges) {\n if (range.from <= to && range.to >= from) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Toggle markdown style on selection or insert markers at cursor\n * @param marker - The markdown marker (e.g., \"**\" for bold, \"*\" for italic)\n * @returns Command function for EditorView\n */\nexport function toggleMarkdownStyle(marker: string): (view: EditorView) => boolean {\n return (view: EditorView) => {\n const { state } = view;\n const { from, to, empty } = state.selection.main;\n\n // Get selected text\n const selectedText = state.sliceDoc(from, to);\n\n // Check if already wrapped with markers\n const markerLen = marker.length;\n const beforeFrom = Math.max(0, from - markerLen);\n const afterTo = Math.min(state.doc.length, to + markerLen);\n const textBefore = state.sliceDoc(beforeFrom, from);\n const textAfter = state.sliceDoc(to, afterTo);\n\n const isWrapped = textBefore === marker && textAfter === marker;\n\n if (isWrapped) {\n // Remove markers\n view.dispatch({\n changes: [\n { from: beforeFrom, to: from, insert: \"\" },\n { from: to, to: afterTo, insert: \"\" },\n ],\n selection: { anchor: beforeFrom, head: beforeFrom + selectedText.length },\n });\n } else if (empty) {\n // No selection - insert markers and place cursor between them\n view.dispatch({\n changes: { from, to, insert: marker + marker },\n selection: { anchor: from + markerLen },\n });\n } else {\n // Wrap selection with markers\n view.dispatch({\n changes: { from, to, insert: marker + selectedText + marker },\n selection: { anchor: from + markerLen, head: to + markerLen },\n });\n }\n\n return true;\n };\n}\n"]}