draftly 0.1.0-alpha.0 → 1.0.0-alpha.2

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 (74) hide show
  1. package/README.md +346 -0
  2. package/dist/chunk-2B3A3VSQ.cjs +3382 -0
  3. package/dist/chunk-2B3A3VSQ.cjs.map +1 -0
  4. package/dist/chunk-72ZYRGRT.cjs +399 -0
  5. package/dist/chunk-72ZYRGRT.cjs.map +1 -0
  6. package/dist/chunk-CG4M4TC7.js +392 -0
  7. package/dist/chunk-CG4M4TC7.js.map +1 -0
  8. package/dist/chunk-DFQYXFOP.js +86 -0
  9. package/dist/chunk-DFQYXFOP.js.map +1 -0
  10. package/dist/chunk-HPSMS2WB.js +182 -0
  11. package/dist/chunk-HPSMS2WB.js.map +1 -0
  12. package/dist/chunk-KBQDZ5IW.cjs +192 -0
  13. package/dist/chunk-KBQDZ5IW.cjs.map +1 -0
  14. package/dist/chunk-KDEDLC3D.cjs +93 -0
  15. package/dist/chunk-KDEDLC3D.cjs.map +1 -0
  16. package/dist/chunk-N3WL3XPB.js +3360 -0
  17. package/dist/chunk-N3WL3XPB.js.map +1 -0
  18. package/dist/draftly-BLnx3uGX.d.cts +293 -0
  19. package/dist/draftly-BLnx3uGX.d.ts +293 -0
  20. package/dist/editor/index.cjs +57 -0
  21. package/dist/editor/index.cjs.map +1 -0
  22. package/dist/editor/index.d.cts +15 -0
  23. package/dist/editor/index.d.ts +15 -0
  24. package/dist/editor/index.js +4 -0
  25. package/dist/editor/index.js.map +1 -0
  26. package/dist/index.cjs +120 -1129
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +9 -257
  29. package/dist/index.d.ts +9 -257
  30. package/dist/index.js +4 -1126
  31. package/dist/index.js.map +1 -1
  32. package/dist/plugins/index.cjs +66 -0
  33. package/dist/plugins/index.cjs.map +1 -0
  34. package/dist/plugins/index.d.cts +515 -0
  35. package/dist/plugins/index.d.ts +515 -0
  36. package/dist/plugins/index.js +5 -0
  37. package/dist/plugins/index.js.map +1 -0
  38. package/dist/preview/index.cjs +29 -0
  39. package/dist/preview/index.cjs.map +1 -0
  40. package/dist/preview/index.d.cts +143 -0
  41. package/dist/preview/index.d.ts +143 -0
  42. package/dist/preview/index.js +4 -0
  43. package/dist/preview/index.js.map +1 -0
  44. package/package.json +22 -1
  45. package/src/{draftly.ts → editor/draftly.ts} +28 -27
  46. package/src/editor/index.ts +5 -0
  47. package/src/{plugin.ts → editor/plugin.ts} +62 -34
  48. package/src/editor/theme.ts +62 -0
  49. package/src/editor/utils.ts +143 -0
  50. package/src/{view-plugin.ts → editor/view-plugin.ts} +25 -140
  51. package/src/index.ts +4 -7
  52. package/src/plugins/code-plugin.ts +1119 -0
  53. package/src/plugins/heading-plugin.ts +108 -74
  54. package/src/plugins/hr-plugin.ts +102 -0
  55. package/src/plugins/html-plugin.ts +59 -53
  56. package/src/plugins/image-plugin.ts +447 -0
  57. package/src/plugins/index.ts +57 -0
  58. package/src/plugins/inline-plugin.ts +178 -39
  59. package/src/plugins/link-plugin.ts +509 -0
  60. package/src/plugins/list-plugin.ts +492 -211
  61. package/src/plugins/math-plugin.ts +514 -0
  62. package/src/plugins/mermaid-plugin.ts +500 -0
  63. package/src/plugins/paragraph-plugin.ts +38 -0
  64. package/src/plugins/quote-plugin.ts +146 -0
  65. package/src/preview/context.ts +38 -0
  66. package/src/preview/css-generator.ts +51 -0
  67. package/src/preview/default-renderers.ts +29 -0
  68. package/src/preview/index.ts +20 -0
  69. package/src/preview/preview.ts +40 -0
  70. package/src/preview/renderer.ts +157 -0
  71. package/src/preview/types.ts +72 -0
  72. package/src/plugins/plugins.ts +0 -9
  73. package/src/theme.ts +0 -86
  74. package/src/utils.ts +0 -21
@@ -0,0 +1,143 @@
1
+ import { EditorView } from "@codemirror/view";
2
+ import { StyleSpec } from "style-mod";
3
+
4
+ /**
5
+ * Deep merge two objects
6
+ * @param a - First object
7
+ * @param b - Second object
8
+ * @returns Merged object
9
+ */
10
+ export function deepMerge<T>(a: T, b?: T): T {
11
+ const result = { ...a };
12
+
13
+ if (!b) {
14
+ return result;
15
+ }
16
+
17
+ for (const key in b as T) {
18
+ if (b[key] && typeof b[key] === "object" && !Array.isArray(b[key]) && typeof a[key] === "object") {
19
+ result[key] = deepMerge(a[key], b[key]);
20
+ } else {
21
+ result[key] = b[key];
22
+ }
23
+ }
24
+
25
+ return result;
26
+ }
27
+
28
+ /**
29
+ * Theme style
30
+ */
31
+ export type ThemeStyle = {
32
+ [selector: string]: StyleSpec;
33
+ };
34
+
35
+ /**
36
+ * Theme Enum
37
+ */
38
+ export enum ThemeEnum {
39
+ DARK = "dark",
40
+ LIGHT = "light",
41
+ AUTO = "auto",
42
+ }
43
+
44
+ /**
45
+ * Function to create the themes
46
+ *
47
+ * @param defaultTheme - Default theme -- Always applied
48
+ * @param darkTheme - Dark theme -- Applied when theme is "dark" or "auto" and system is dark
49
+ * @param lightTheme - Light theme -- Applied when theme is "light" or "auto" and system is light
50
+ * @returns Theme function
51
+ */
52
+ export function createTheme({
53
+ default: defaultTheme,
54
+ dark: darkTheme,
55
+ light: lightTheme,
56
+ }: {
57
+ default: ThemeStyle;
58
+ dark?: ThemeStyle;
59
+ light?: ThemeStyle;
60
+ }): (theme: ThemeEnum) => ThemeStyle {
61
+ return (theme: ThemeEnum) => {
62
+ let style: ThemeStyle = defaultTheme;
63
+
64
+ if (theme === ThemeEnum.DARK) {
65
+ style = deepMerge(style, darkTheme);
66
+ }
67
+
68
+ if (theme === ThemeEnum.LIGHT) {
69
+ style = deepMerge(style, lightTheme);
70
+ }
71
+
72
+ return style;
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Check if cursor is within the given range
78
+ */
79
+ export function cursorInRange(view: EditorView, from: number, to: number): boolean {
80
+ const selection = view.state.selection.main;
81
+ return selection.from <= to && selection.to >= from;
82
+ }
83
+
84
+ /**
85
+ * Check if any selection overlaps with the given range
86
+ */
87
+ export function selectionOverlapsRange(view: EditorView, from: number, to: number): boolean {
88
+ for (const range of view.state.selection.ranges) {
89
+ if (range.from <= to && range.to >= from) {
90
+ return true;
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+
96
+ /**
97
+ * Toggle markdown style on selection or insert markers at cursor
98
+ * @param marker - The markdown marker (e.g., "**" for bold, "*" for italic)
99
+ * @returns Command function for EditorView
100
+ */
101
+ export function toggleMarkdownStyle(marker: string): (view: EditorView) => boolean {
102
+ return (view: EditorView) => {
103
+ const { state } = view;
104
+ const { from, to, empty } = state.selection.main;
105
+
106
+ // Get selected text
107
+ const selectedText = state.sliceDoc(from, to);
108
+
109
+ // Check if already wrapped with markers
110
+ const markerLen = marker.length;
111
+ const beforeFrom = Math.max(0, from - markerLen);
112
+ const afterTo = Math.min(state.doc.length, to + markerLen);
113
+ const textBefore = state.sliceDoc(beforeFrom, from);
114
+ const textAfter = state.sliceDoc(to, afterTo);
115
+
116
+ const isWrapped = textBefore === marker && textAfter === marker;
117
+
118
+ if (isWrapped) {
119
+ // Remove markers
120
+ view.dispatch({
121
+ changes: [
122
+ { from: beforeFrom, to: from, insert: "" },
123
+ { from: to, to: afterTo, insert: "" },
124
+ ],
125
+ selection: { anchor: beforeFrom, head: beforeFrom + selectedText.length },
126
+ });
127
+ } else if (empty) {
128
+ // No selection - insert markers and place cursor between them
129
+ view.dispatch({
130
+ changes: { from, to, insert: marker + marker },
131
+ selection: { anchor: from + markerLen },
132
+ });
133
+ } else {
134
+ // Wrap selection with markers
135
+ view.dispatch({
136
+ changes: { from, to, insert: marker + selectedText + marker },
137
+ selection: { anchor: from + markerLen, head: to + markerLen },
138
+ });
139
+ }
140
+
141
+ return true;
142
+ };
143
+ }
@@ -1,49 +1,11 @@
1
1
  import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";
2
2
  import { Extension, Facet, Range, RangeSetBuilder } from "@codemirror/state";
3
- import { syntaxHighlighting, syntaxTree } from "@codemirror/language";
4
- import { cursorInRange, selectionOverlapsRange } from "./utils";
5
- import { highlightStyle, draftlyBaseTheme } from "./theme";
3
+ import { syntaxTree } from "@codemirror/language";
4
+ import { cursorInRange, selectionOverlapsRange, ThemeEnum } from "./utils";
5
+ import { draftlyBaseTheme } from "./theme";
6
6
  import { DecorationContext, DraftlyPlugin } from "./plugin";
7
7
  import { DraftlyNode } from "./draftly";
8
8
 
9
- /**
10
- * Mark decorations for inline styling
11
- */
12
- const markDecorations = {
13
- // Inline styles
14
- "inline-code": Decoration.mark({ class: "cm-draftly-inline-code" }),
15
-
16
- // Links and images
17
- link: Decoration.mark({ class: "cm-draftly-link" }),
18
- "link-text": Decoration.mark({ class: "cm-draftly-link-text" }),
19
- url: Decoration.mark({ class: "cm-draftly-url" }),
20
- image: Decoration.mark({ class: "cm-draftly-image" }),
21
-
22
- // Emphasis markers (* _ ~~ `)
23
- "emphasis-mark": Decoration.mark({ class: "cm-draftly-emphasis-mark" }),
24
-
25
- // Code blocks
26
- "fenced-code": Decoration.mark({ class: "cm-draftly-fenced-code" }),
27
- "code-mark": Decoration.mark({ class: "cm-draftly-code-mark" }),
28
- "code-info": Decoration.mark({ class: "cm-draftly-code-info" }),
29
-
30
- // Blockquote
31
- blockquote: Decoration.mark({ class: "cm-draftly-blockquote" }),
32
- "quote-mark": Decoration.mark({ class: "cm-draftly-quote-mark" }),
33
-
34
- // Horizontal rule
35
- hr: Decoration.mark({ class: "cm-draftly-hr" }),
36
- };
37
-
38
- /**
39
- * Line decorations for block-level elements
40
- */
41
- const lineDecorations = {
42
- blockquote: Decoration.line({ class: "cm-draftly-line-blockquote" }),
43
- "code-block": Decoration.line({ class: "cm-draftly-line-code" }),
44
- hr: Decoration.line({ class: "cm-draftly-line-hr" }),
45
- };
46
-
47
9
  /**
48
10
  * Facet to register plugins with the view plugin
49
11
  */
@@ -61,6 +23,13 @@ export const draftlyOnNodesChangeFacet = Facet.define<
61
23
  combine: (values) => values.find((v) => v !== undefined),
62
24
  });
63
25
 
26
+ /**
27
+ * Facet to register the theme
28
+ */
29
+ export const draftlyThemeFacet = Facet.define<ThemeEnum, ThemeEnum>({
30
+ combine: (values) => values.find((v) => v !== undefined) || ThemeEnum.AUTO,
31
+ });
32
+
64
33
  /**
65
34
  * Build decorations for the visible viewport
66
35
  * @param view - The EditorView instance
@@ -70,98 +39,6 @@ function buildDecorations(view: EditorView, plugins: DraftlyPlugin[] = []): Deco
70
39
  const builder = new RangeSetBuilder<Decoration>();
71
40
  const decorations: Range<Decoration>[] = [];
72
41
 
73
- const tree = syntaxTree(view.state);
74
-
75
- // Iterate through the syntax tree
76
- tree.iterate({
77
- enter: (node) => {
78
- const { from, to, name } = node;
79
-
80
- // Skip if cursor is in this range (show raw markdown)
81
- const cursorInNode = selectionOverlapsRange(view, from, to);
82
-
83
- // Handle inline code
84
- if (name === "InlineCode") {
85
- decorations.push(markDecorations["inline-code"].range(from, to));
86
-
87
- // Style the backticks
88
- if (!cursorInNode) {
89
- const marks = node.node.getChildren("CodeMark");
90
- for (const mark of marks) {
91
- decorations.push(markDecorations["code-mark"].range(mark.from, mark.to));
92
- }
93
- }
94
- }
95
-
96
- // Handle links
97
- if (name === "Link") {
98
- decorations.push(markDecorations.link.range(from, to));
99
-
100
- // Find the URL child
101
- const url = node.node.getChild("URL");
102
- if (url) {
103
- decorations.push(markDecorations.url.range(url.from, url.to));
104
- }
105
- }
106
-
107
- // Handle images
108
- if (name === "Image") {
109
- decorations.push(markDecorations.image.range(from, to));
110
- }
111
-
112
- // Handle fenced code blocks
113
- if (name === "FencedCode") {
114
- decorations.push(markDecorations["fenced-code"].range(from, to));
115
-
116
- // Add line decorations for each line in the code block
117
- const startLine = view.state.doc.lineAt(from);
118
- const endLine = view.state.doc.lineAt(to);
119
- for (let i = startLine.number; i <= endLine.number; i++) {
120
- const line = view.state.doc.line(i);
121
- decorations.push(lineDecorations["code-block"].range(line.from));
122
- }
123
-
124
- // Style code info (language identifier)
125
- const codeInfo = node.node.getChild("CodeInfo");
126
- if (codeInfo) {
127
- decorations.push(markDecorations["code-info"].range(codeInfo.from, codeInfo.to));
128
- }
129
-
130
- // Style code marks (```)
131
- const codeMarks = node.node.getChildren("CodeMark");
132
- for (const mark of codeMarks) {
133
- decorations.push(markDecorations["code-mark"].range(mark.from, mark.to));
134
- }
135
- }
136
-
137
- // Handle blockquotes
138
- if (name === "Blockquote") {
139
- decorations.push(markDecorations.blockquote.range(from, to));
140
-
141
- // Add line decorations
142
- const startLine = view.state.doc.lineAt(from);
143
- const endLine = view.state.doc.lineAt(to);
144
- for (let i = startLine.number; i <= endLine.number; i++) {
145
- const line = view.state.doc.line(i);
146
- decorations.push(lineDecorations.blockquote.range(line.from));
147
- }
148
-
149
- // Style quote marks (>)
150
- const quoteMarks = node.node.getChildren("QuoteMark");
151
- for (const mark of quoteMarks) {
152
- decorations.push(markDecorations["quote-mark"].range(mark.from, mark.to));
153
- }
154
- }
155
-
156
- // Handle horizontal rules
157
- if (name === "HorizontalRule") {
158
- const line = view.state.doc.lineAt(from);
159
- decorations.push(lineDecorations.hr.range(line.from));
160
- decorations.push(markDecorations.hr.range(from, to));
161
- }
162
- },
163
- });
164
-
165
42
  // Allow plugins to contribute decorations
166
43
  if (plugins.length > 0) {
167
44
  const ctx: DecorationContext = {
@@ -175,7 +52,12 @@ function buildDecorations(view: EditorView, plugins: DraftlyPlugin[] = []): Deco
175
52
  const sortedPlugins = [...plugins].sort((a, b) => a.decorationPriority - b.decorationPriority);
176
53
 
177
54
  for (const plugin of sortedPlugins) {
178
- plugin.buildDecorations(ctx);
55
+ try {
56
+ plugin.buildDecorations(ctx);
57
+ } catch {
58
+ // Silently ignore errors from partial tree states (e.g., Lezer TreeBuffer
59
+ // "Invalid child in posBefore"). These resolve on the next update cycle.
60
+ }
179
61
  }
180
62
  }
181
63
 
@@ -197,7 +79,7 @@ function buildDecorations(view: EditorView, plugins: DraftlyPlugin[] = []): Deco
197
79
  class draftlyViewPluginClass {
198
80
  decorations: DecorationSet;
199
81
  private plugins: DraftlyPlugin[];
200
- private onNodesChange?: (nodes: DraftlyNode[]) => void;
82
+ private onNodesChange: ((nodes: DraftlyNode[]) => void) | undefined;
201
83
 
202
84
  constructor(view: EditorView) {
203
85
  this.plugins = view.state.facet(DraftlyPluginsFacet);
@@ -210,7 +92,7 @@ class draftlyViewPluginClass {
210
92
  }
211
93
 
212
94
  // Call onNodesChange callback with initial nodes
213
- if (this.onNodesChange) {
95
+ if (this.onNodesChange && typeof this.onNodesChange === "function") {
214
96
  this.onNodesChange(this.buildNodes(view));
215
97
  }
216
98
  }
@@ -276,13 +158,13 @@ class draftlyViewPluginClass {
276
158
  */
277
159
  export const draftlyViewPlugin = ViewPlugin.fromClass(draftlyViewPluginClass, {
278
160
  decorations: (v) => v.decorations,
279
- provide: () => [syntaxHighlighting(highlightStyle)],
161
+ provide: () => [],
280
162
  });
281
163
 
282
164
  /**
283
165
  * Extension to add the cm-draftly-enabled class to the editor
284
166
  */
285
- const draftlyEditorClass = EditorView.editorAttributes.of({ class: "cm-draftly-enabled" });
167
+ const draftlyEditorClass = EditorView.editorAttributes.of({ class: "cm-draftly" });
286
168
 
287
169
  /**
288
170
  * Create draftly view extension bundle with plugin support
@@ -291,14 +173,17 @@ const draftlyEditorClass = EditorView.editorAttributes.of({ class: "cm-draftly-e
291
173
  * @returns Extension array including view plugin, theme, and plugin facet
292
174
  */
293
175
  export function createDraftlyViewExtension(
176
+ theme: ThemeEnum = ThemeEnum.AUTO,
177
+ baseStyles: boolean = true,
294
178
  plugins: DraftlyPlugin[] = [],
295
179
  onNodesChange?: (nodes: DraftlyNode[]) => void
296
180
  ): Extension[] {
297
181
  return [
182
+ draftlyEditorClass,
298
183
  DraftlyPluginsFacet.of(plugins),
299
184
  draftlyOnNodesChangeFacet.of(onNodesChange),
185
+ draftlyThemeFacet.of(theme),
300
186
  draftlyViewPlugin,
301
- draftlyBaseTheme,
302
- draftlyEditorClass,
187
+ ...(baseStyles ? [draftlyBaseTheme] : []),
303
188
  ];
304
189
  }
package/src/index.ts CHANGED
@@ -1,7 +1,4 @@
1
- import { draftly } from "./draftly";
2
-
3
- export default draftly;
4
-
5
- export * from "./draftly";
6
- export * from "./plugin";
7
- export * from "./view-plugin";
1
+ // Re-export everything for backward compatibility
2
+ export * from "./editor";
3
+ export * from "./plugins";
4
+ export * from "./preview";