draftly 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1128 @@
1
+ import { Facet, RangeSetBuilder, Prec } from '@codemirror/state';
2
+ import { EditorView, Decoration, ViewPlugin, keymap, drawSelection, highlightActiveLine, rectangularSelection, WidgetType } from '@codemirror/view';
3
+ import { markdown, markdownLanguage, markdownKeymap } from '@codemirror/lang-markdown';
4
+ import { HighlightStyle, syntaxHighlighting, syntaxTree, indentOnInput } from '@codemirror/language';
5
+ import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
6
+ import { languages } from '@codemirror/language-data';
7
+ import DOMPurify from 'dompurify';
8
+
9
+ // src/draftly.ts
10
+
11
+ // src/utils.ts
12
+ function cursorInRange(view, from, to) {
13
+ const selection = view.state.selection.main;
14
+ return selection.from <= to && selection.to >= from;
15
+ }
16
+ function selectionOverlapsRange(view, from, to) {
17
+ for (const range of view.state.selection.ranges) {
18
+ if (range.from <= to && range.to >= from) {
19
+ return true;
20
+ }
21
+ }
22
+ return false;
23
+ }
24
+ var highlightStyle = HighlightStyle.define([]);
25
+ var draftlyBaseTheme = EditorView.baseTheme({
26
+ // Container styles - only apply when view plugin is enabled
27
+ "&.cm-draftly-enabled": {
28
+ fontSize: "16px",
29
+ lineHeight: "1.6"
30
+ },
31
+ "&.cm-draftly-enabled .cm-content": {
32
+ maxWidth: "48rem",
33
+ margin: "0 auto",
34
+ fontFamily: "var(--font-sans, sans-serif)",
35
+ fontSize: "16px",
36
+ lineHeight: "1.6"
37
+ },
38
+ // Inline code
39
+ ".cm-draftly-inline-code": {
40
+ fontFamily: "var(--font-mono, monospace)",
41
+ fontSize: "0.9em",
42
+ backgroundColor: "rgba(175, 184, 193, 0.2)",
43
+ padding: "0.1em 0.3em",
44
+ borderRadius: "4px"
45
+ },
46
+ ".cm-draftly-code-mark": {
47
+ opacity: "0.4"
48
+ },
49
+ // Links
50
+ ".cm-draftly-link": {
51
+ color: "#0969da",
52
+ textDecoration: "none"
53
+ },
54
+ ".cm-draftly-link:hover": {
55
+ textDecoration: "underline"
56
+ },
57
+ ".cm-draftly-url": {
58
+ opacity: "0.6",
59
+ fontSize: "0.9em"
60
+ },
61
+ // Images (placeholder styling)
62
+ ".cm-draftly-image": {
63
+ color: "#8250df"
64
+ },
65
+ // Code blocks
66
+ ".cm-draftly-fenced-code": {
67
+ fontFamily: "var(--font-mono, monospace)",
68
+ fontSize: "0.9em"
69
+ },
70
+ ".cm-draftly-line-code": {
71
+ backgroundColor: "rgba(175, 184, 193, 0.15)",
72
+ borderRadius: "0"
73
+ },
74
+ ".cm-draftly-code-info": {
75
+ color: "#6e7781",
76
+ fontStyle: "italic"
77
+ },
78
+ // Blockquote
79
+ ".cm-draftly-line-blockquote": {
80
+ borderLeft: "3px solid #d0d7de",
81
+ paddingLeft: "1em",
82
+ color: "#656d76"
83
+ },
84
+ ".cm-draftly-quote-mark": {
85
+ opacity: "0.4"
86
+ },
87
+ // Horizontal rule
88
+ ".cm-draftly-line-hr": {
89
+ textAlign: "center"
90
+ },
91
+ ".cm-draftly-hr": {
92
+ opacity: "0.4"
93
+ }
94
+ });
95
+
96
+ // src/view-plugin.ts
97
+ var markDecorations = {
98
+ // Inline styles
99
+ "inline-code": Decoration.mark({ class: "cm-draftly-inline-code" }),
100
+ // Links and images
101
+ link: Decoration.mark({ class: "cm-draftly-link" }),
102
+ "link-text": Decoration.mark({ class: "cm-draftly-link-text" }),
103
+ url: Decoration.mark({ class: "cm-draftly-url" }),
104
+ image: Decoration.mark({ class: "cm-draftly-image" }),
105
+ // Emphasis markers (* _ ~~ `)
106
+ "emphasis-mark": Decoration.mark({ class: "cm-draftly-emphasis-mark" }),
107
+ // Code blocks
108
+ "fenced-code": Decoration.mark({ class: "cm-draftly-fenced-code" }),
109
+ "code-mark": Decoration.mark({ class: "cm-draftly-code-mark" }),
110
+ "code-info": Decoration.mark({ class: "cm-draftly-code-info" }),
111
+ // Blockquote
112
+ blockquote: Decoration.mark({ class: "cm-draftly-blockquote" }),
113
+ "quote-mark": Decoration.mark({ class: "cm-draftly-quote-mark" }),
114
+ // Horizontal rule
115
+ hr: Decoration.mark({ class: "cm-draftly-hr" })
116
+ };
117
+ var lineDecorations = {
118
+ blockquote: Decoration.line({ class: "cm-draftly-line-blockquote" }),
119
+ "code-block": Decoration.line({ class: "cm-draftly-line-code" }),
120
+ hr: Decoration.line({ class: "cm-draftly-line-hr" })
121
+ };
122
+ var DraftlyPluginsFacet = Facet.define({
123
+ combine: (values) => values.flat()
124
+ });
125
+ var draftlyOnNodesChangeFacet = Facet.define({
126
+ combine: (values) => values.find((v) => v !== void 0)
127
+ });
128
+ function buildDecorations(view, plugins = []) {
129
+ const builder = new RangeSetBuilder();
130
+ const decorations = [];
131
+ const tree = syntaxTree(view.state);
132
+ tree.iterate({
133
+ enter: (node) => {
134
+ const { from, to, name } = node;
135
+ const cursorInNode = selectionOverlapsRange(view, from, to);
136
+ if (name === "InlineCode") {
137
+ decorations.push(markDecorations["inline-code"].range(from, to));
138
+ if (!cursorInNode) {
139
+ const marks = node.node.getChildren("CodeMark");
140
+ for (const mark of marks) {
141
+ decorations.push(markDecorations["code-mark"].range(mark.from, mark.to));
142
+ }
143
+ }
144
+ }
145
+ if (name === "Link") {
146
+ decorations.push(markDecorations.link.range(from, to));
147
+ const url = node.node.getChild("URL");
148
+ if (url) {
149
+ decorations.push(markDecorations.url.range(url.from, url.to));
150
+ }
151
+ }
152
+ if (name === "Image") {
153
+ decorations.push(markDecorations.image.range(from, to));
154
+ }
155
+ if (name === "FencedCode") {
156
+ decorations.push(markDecorations["fenced-code"].range(from, to));
157
+ const startLine = view.state.doc.lineAt(from);
158
+ const endLine = view.state.doc.lineAt(to);
159
+ for (let i = startLine.number; i <= endLine.number; i++) {
160
+ const line = view.state.doc.line(i);
161
+ decorations.push(lineDecorations["code-block"].range(line.from));
162
+ }
163
+ const codeInfo = node.node.getChild("CodeInfo");
164
+ if (codeInfo) {
165
+ decorations.push(markDecorations["code-info"].range(codeInfo.from, codeInfo.to));
166
+ }
167
+ const codeMarks = node.node.getChildren("CodeMark");
168
+ for (const mark of codeMarks) {
169
+ decorations.push(markDecorations["code-mark"].range(mark.from, mark.to));
170
+ }
171
+ }
172
+ if (name === "Blockquote") {
173
+ decorations.push(markDecorations.blockquote.range(from, to));
174
+ const startLine = view.state.doc.lineAt(from);
175
+ const endLine = view.state.doc.lineAt(to);
176
+ for (let i = startLine.number; i <= endLine.number; i++) {
177
+ const line = view.state.doc.line(i);
178
+ decorations.push(lineDecorations.blockquote.range(line.from));
179
+ }
180
+ const quoteMarks = node.node.getChildren("QuoteMark");
181
+ for (const mark of quoteMarks) {
182
+ decorations.push(markDecorations["quote-mark"].range(mark.from, mark.to));
183
+ }
184
+ }
185
+ if (name === "HorizontalRule") {
186
+ const line = view.state.doc.lineAt(from);
187
+ decorations.push(lineDecorations.hr.range(line.from));
188
+ decorations.push(markDecorations.hr.range(from, to));
189
+ }
190
+ }
191
+ });
192
+ if (plugins.length > 0) {
193
+ const ctx = {
194
+ view,
195
+ decorations,
196
+ selectionOverlapsRange: (from, to) => selectionOverlapsRange(view, from, to),
197
+ cursorInRange: (from, to) => cursorInRange(view, from, to)
198
+ };
199
+ const sortedPlugins = [...plugins].sort((a, b) => a.decorationPriority - b.decorationPriority);
200
+ for (const plugin of sortedPlugins) {
201
+ plugin.buildDecorations(ctx);
202
+ }
203
+ }
204
+ decorations.sort((a, b) => a.from - b.from || a.value.startSide - b.value.startSide);
205
+ for (const decoration of decorations) {
206
+ builder.add(decoration.from, decoration.to, decoration.value);
207
+ }
208
+ return builder.finish();
209
+ }
210
+ var draftlyViewPluginClass = class {
211
+ decorations;
212
+ plugins;
213
+ onNodesChange;
214
+ constructor(view) {
215
+ this.plugins = view.state.facet(DraftlyPluginsFacet);
216
+ this.onNodesChange = view.state.facet(draftlyOnNodesChangeFacet);
217
+ this.decorations = buildDecorations(view, this.plugins);
218
+ for (const plugin of this.plugins) {
219
+ plugin.onViewReady(view);
220
+ }
221
+ if (this.onNodesChange) {
222
+ this.onNodesChange(this.buildNodes(view));
223
+ }
224
+ }
225
+ update(update) {
226
+ this.plugins = update.view.state.facet(DraftlyPluginsFacet);
227
+ this.onNodesChange = update.view.state.facet(draftlyOnNodesChangeFacet);
228
+ for (const plugin of this.plugins) {
229
+ plugin.onViewUpdate(update);
230
+ }
231
+ if (update.docChanged || update.selectionSet || update.viewportChanged) {
232
+ this.decorations = buildDecorations(update.view, this.plugins);
233
+ if (this.onNodesChange) {
234
+ this.onNodesChange(this.buildNodes(update.view));
235
+ }
236
+ }
237
+ }
238
+ buildNodes(view) {
239
+ const tree = syntaxTree(view.state);
240
+ const roots = [];
241
+ const stack = [];
242
+ tree.iterate({
243
+ enter: (nodeRef) => {
244
+ const node = {
245
+ from: nodeRef.from,
246
+ to: nodeRef.to,
247
+ name: nodeRef.name,
248
+ children: [],
249
+ isSelected: selectionOverlapsRange(view, nodeRef.from, nodeRef.to)
250
+ };
251
+ if (stack.length > 0) {
252
+ stack[stack.length - 1].children.push(node);
253
+ } else {
254
+ roots.push(node);
255
+ }
256
+ stack.push(node);
257
+ },
258
+ leave: () => {
259
+ stack.pop();
260
+ }
261
+ });
262
+ return roots;
263
+ }
264
+ };
265
+ var draftlyViewPlugin = ViewPlugin.fromClass(draftlyViewPluginClass, {
266
+ decorations: (v) => v.decorations,
267
+ provide: () => [syntaxHighlighting(highlightStyle)]
268
+ });
269
+ var draftlyEditorClass = EditorView.editorAttributes.of({ class: "cm-draftly-enabled" });
270
+ function createDraftlyViewExtension(plugins = [], onNodesChange) {
271
+ return [
272
+ DraftlyPluginsFacet.of(plugins),
273
+ draftlyOnNodesChangeFacet.of(onNodesChange),
274
+ draftlyViewPlugin,
275
+ draftlyBaseTheme,
276
+ draftlyEditorClass
277
+ ];
278
+ }
279
+
280
+ // src/plugin.ts
281
+ var DraftlyPlugin = class {
282
+ /** Plugin dependencies - names of required plugins */
283
+ dependencies = [];
284
+ /** Private configuration storage */
285
+ _config = {};
286
+ /** Protected context - accessible to subclasses */
287
+ _context = null;
288
+ /** Get plugin configuration */
289
+ get config() {
290
+ return this._config;
291
+ }
292
+ /** Set plugin configuration */
293
+ set config(value) {
294
+ this._config = value;
295
+ }
296
+ /** Get plugin context */
297
+ get context() {
298
+ return this._context;
299
+ }
300
+ // ============================================
301
+ // EXTENSION METHODS (overridable by subclasses)
302
+ // ============================================
303
+ /**
304
+ * Return CodeMirror extensions for this plugin
305
+ * Override to provide custom extensions
306
+ */
307
+ getExtensions() {
308
+ return [];
309
+ }
310
+ /**
311
+ * Return markdown parser extensions
312
+ * Override to extend markdown parsing
313
+ */
314
+ getMarkdownConfig() {
315
+ return null;
316
+ }
317
+ /**
318
+ * Return keybindings for this plugin
319
+ * Override to add custom keyboard shortcuts
320
+ */
321
+ getKeymap() {
322
+ return [];
323
+ }
324
+ // ============================================
325
+ // DECORATION METHODS (overridable by subclasses)
326
+ // ============================================
327
+ /**
328
+ * Decoration priority (higher = applied later)
329
+ * Override to customize priority. Default: 100
330
+ */
331
+ get decorationPriority() {
332
+ return 100;
333
+ }
334
+ /**
335
+ * Build decorations for the current view state
336
+ * Override to contribute decorations to the editor
337
+ *
338
+ * @param ctx - Decoration context with view and decoration array
339
+ */
340
+ buildDecorations(_ctx) {
341
+ }
342
+ // ============================================
343
+ // LIFECYCLE HOOKS (overridable by subclasses)
344
+ // ============================================
345
+ /**
346
+ * Called when plugin is registered with draftly
347
+ * Override to perform initialization
348
+ *
349
+ * @param context - Plugin context with configuration
350
+ */
351
+ onRegister(context) {
352
+ this._context = context;
353
+ }
354
+ /**
355
+ * Called when plugin is unregistered
356
+ * Override to perform cleanup
357
+ */
358
+ onUnregister() {
359
+ this._context = null;
360
+ }
361
+ /**
362
+ * Called when EditorView is created and ready
363
+ * Override to perform view-specific initialization
364
+ *
365
+ * @param view - The EditorView instance
366
+ */
367
+ onViewReady(_view) {
368
+ }
369
+ /**
370
+ * Called on view updates (document changes, selection changes, etc.)
371
+ * Override to react to editor changes
372
+ *
373
+ * @param update - The ViewUpdate with change information
374
+ */
375
+ onViewUpdate(_update) {
376
+ }
377
+ // ============================================
378
+ // ACCESSIBILITY METHODS (overridable by subclasses)
379
+ // ============================================
380
+ /**
381
+ * Return ARIA attributes to add to the editor
382
+ * Override to improve accessibility
383
+ */
384
+ getAriaAttributes() {
385
+ return {};
386
+ }
387
+ // ============================================
388
+ // VIEW CONTRIBUTIONS (overridable by subclasses)
389
+ // ============================================
390
+ /**
391
+ * Return widget types this plugin provides
392
+ * Override to contribute custom widgets
393
+ */
394
+ getWidgets() {
395
+ return [];
396
+ }
397
+ // ============================================
398
+ // PROTECTED UTILITIES (for subclasses)
399
+ // ============================================
400
+ /**
401
+ * Helper to get current editor state
402
+ * @param view - The EditorView instance
403
+ */
404
+ getState(view) {
405
+ return view.state;
406
+ }
407
+ /**
408
+ * Helper to get current document
409
+ * @param view - The EditorView instance
410
+ */
411
+ getDocument(view) {
412
+ return view.state.doc;
413
+ }
414
+ };
415
+ var DecorationPlugin = class extends DraftlyPlugin {
416
+ /**
417
+ * Decoration priority - lower than default for decoration plugins
418
+ * Override to customize
419
+ */
420
+ get decorationPriority() {
421
+ return 50;
422
+ }
423
+ };
424
+ var SyntaxPlugin = class extends DraftlyPlugin {
425
+ };
426
+
427
+ // src/plugins/heading-plugin.ts
428
+ var HEADING_TYPES = ["ATXHeading1", "ATXHeading2", "ATXHeading3", "ATXHeading4", "ATXHeading5", "ATXHeading6"];
429
+ var headingMarkDecorations = {
430
+ "heading-1": Decoration.mark({ class: "cm-draftly-h1" }),
431
+ "heading-2": Decoration.mark({ class: "cm-draftly-h2" }),
432
+ "heading-3": Decoration.mark({ class: "cm-draftly-h3" }),
433
+ "heading-4": Decoration.mark({ class: "cm-draftly-h4" }),
434
+ "heading-5": Decoration.mark({ class: "cm-draftly-h5" }),
435
+ "heading-6": Decoration.mark({ class: "cm-draftly-h6" }),
436
+ "heading-mark": Decoration.mark({ class: "cm-draftly-heading-mark" })
437
+ };
438
+ var headingLineDecorations = {
439
+ "heading-1": Decoration.line({ class: "cm-draftly-line-h1" }),
440
+ "heading-2": Decoration.line({ class: "cm-draftly-line-h2" }),
441
+ "heading-3": Decoration.line({ class: "cm-draftly-line-h3" }),
442
+ "heading-4": Decoration.line({ class: "cm-draftly-line-h4" }),
443
+ "heading-5": Decoration.line({ class: "cm-draftly-line-h5" }),
444
+ "heading-6": Decoration.line({ class: "cm-draftly-line-h6" })
445
+ };
446
+ var HeadingPlugin = class extends DecorationPlugin {
447
+ name = "heading";
448
+ version = "1.0.0";
449
+ /**
450
+ * Higher priority to ensure headings are styled first
451
+ */
452
+ get decorationPriority() {
453
+ return 10;
454
+ }
455
+ /**
456
+ * Get the extensions for this plugin
457
+ */
458
+ getExtensions() {
459
+ return [headingTheme];
460
+ }
461
+ /**
462
+ * Build heading decorations by iterating the syntax tree
463
+ */
464
+ buildDecorations(ctx) {
465
+ const { view, decorations } = ctx;
466
+ const tree = syntaxTree(view.state);
467
+ tree.iterate({
468
+ enter: (node) => {
469
+ const { from, to, name } = node;
470
+ if (!HEADING_TYPES.includes(name)) {
471
+ return;
472
+ }
473
+ const level = parseInt(name.slice(-1), 10);
474
+ const headingClass = `heading-${level}`;
475
+ const lineClass = `heading-${level}`;
476
+ const line = view.state.doc.lineAt(from);
477
+ decorations.push(headingLineDecorations[lineClass].range(line.from));
478
+ decorations.push(headingMarkDecorations[headingClass].range(from, to + 1));
479
+ const cursorInNode = ctx.selectionOverlapsRange(from, to);
480
+ if (!cursorInNode) {
481
+ const headingMark = node.node.getChild("HeaderMark");
482
+ if (headingMark) {
483
+ decorations.push(headingMarkDecorations["heading-mark"].range(headingMark.from, headingMark.to + 1));
484
+ }
485
+ }
486
+ }
487
+ });
488
+ }
489
+ };
490
+ var headingTheme = EditorView.theme({
491
+ ".cm-draftly-h1": {
492
+ fontSize: "2em",
493
+ fontWeight: "bold",
494
+ fontFamily: "sans-serif",
495
+ textDecoration: "none"
496
+ },
497
+ ".cm-draftly-h2": {
498
+ fontSize: "1.75em",
499
+ fontWeight: "bold",
500
+ fontFamily: "sans-serif",
501
+ textDecoration: "none"
502
+ },
503
+ ".cm-draftly-h3": {
504
+ fontSize: "1.5em",
505
+ fontWeight: "bold",
506
+ fontFamily: "sans-serif",
507
+ textDecoration: "none"
508
+ },
509
+ ".cm-draftly-h4": {
510
+ fontSize: "1.25em",
511
+ fontWeight: "bold",
512
+ fontFamily: "sans-serif",
513
+ textDecoration: "none"
514
+ },
515
+ ".cm-draftly-h5": {
516
+ fontSize: "1em",
517
+ fontWeight: "bold",
518
+ fontFamily: "sans-serif",
519
+ textDecoration: "none"
520
+ },
521
+ ".cm-draftly-h6": {
522
+ fontSize: "0.75em",
523
+ fontWeight: "bold",
524
+ fontFamily: "sans-serif",
525
+ textDecoration: "none"
526
+ },
527
+ // Heading line styles
528
+ ".cm-draftly-line-h1": {
529
+ paddingTop: "1.5em",
530
+ paddingBottom: "0.5em"
531
+ },
532
+ ".cm-draftly-line-h2": {
533
+ paddingTop: "1.25em",
534
+ paddingBottom: "0.5em"
535
+ },
536
+ ".cm-draftly-line-h3, .cm-draftly-line-h4, .cm-draftly-line-h5, .cm-draftly-line-h6": {
537
+ paddingTop: "1em",
538
+ paddingBottom: "0.5em"
539
+ },
540
+ // Heading mark (# symbols)
541
+ ".cm-draftly-heading-mark": {
542
+ display: "none"
543
+ }
544
+ });
545
+ var INLINE_TYPES = {
546
+ Emphasis: "emphasis",
547
+ StrongEmphasis: "strong",
548
+ Strikethrough: "strikethrough",
549
+ Subscript: "subscript",
550
+ Superscript: "superscript"
551
+ };
552
+ var inlineMarkDecorations = {
553
+ emphasis: Decoration.mark({ class: "cm-draftly-emphasis" }),
554
+ strong: Decoration.mark({ class: "cm-draftly-strong" }),
555
+ strikethrough: Decoration.mark({ class: "cm-draftly-strikethrough" }),
556
+ subscript: Decoration.mark({ class: "cm-draftly-subscript" }),
557
+ superscript: Decoration.mark({ class: "cm-draftly-superscript" }),
558
+ // Markers (* _ ~~ ^ ~)
559
+ "inline-mark": Decoration.mark({ class: "cm-draftly-inline-mark" })
560
+ };
561
+ var InlinePlugin = class extends DecorationPlugin {
562
+ name = "inline";
563
+ version = "1.0.0";
564
+ /**
565
+ * Moderate priority for inline styling
566
+ */
567
+ get decorationPriority() {
568
+ return 20;
569
+ }
570
+ /**
571
+ * Get the extensions for this plugin (theme)
572
+ */
573
+ getExtensions() {
574
+ return [inlineTheme];
575
+ }
576
+ /**
577
+ * Build inline decorations by iterating the syntax tree
578
+ */
579
+ buildDecorations(ctx) {
580
+ const { view, decorations } = ctx;
581
+ const tree = syntaxTree(view.state);
582
+ tree.iterate({
583
+ enter: (node) => {
584
+ const { from, to, name } = node;
585
+ const inlineType = INLINE_TYPES[name];
586
+ if (!inlineType) {
587
+ return;
588
+ }
589
+ decorations.push(inlineMarkDecorations[inlineType].range(from, to));
590
+ const cursorInNode = ctx.selectionOverlapsRange(from, to);
591
+ if (!cursorInNode) {
592
+ const markerNames = this.getMarkerNames(name);
593
+ for (const markerName of markerNames) {
594
+ const marks = node.node.getChildren(markerName);
595
+ for (const mark of marks) {
596
+ decorations.push(inlineMarkDecorations["inline-mark"].range(mark.from, mark.to));
597
+ }
598
+ }
599
+ }
600
+ }
601
+ });
602
+ }
603
+ /**
604
+ * Get the marker node names for a given inline type
605
+ */
606
+ getMarkerNames(nodeType) {
607
+ switch (nodeType) {
608
+ case "Emphasis":
609
+ case "StrongEmphasis":
610
+ return ["EmphasisMark"];
611
+ case "Strikethrough":
612
+ return ["StrikethroughMark"];
613
+ case "Subscript":
614
+ return ["SubscriptMark"];
615
+ case "Superscript":
616
+ return ["SuperscriptMark"];
617
+ default:
618
+ return [];
619
+ }
620
+ }
621
+ };
622
+ var inlineTheme = EditorView.theme({
623
+ // Emphasis (italic)
624
+ ".cm-draftly-emphasis": {
625
+ fontStyle: "italic"
626
+ },
627
+ // Strong (bold)
628
+ ".cm-draftly-strong": {
629
+ fontWeight: "bold"
630
+ },
631
+ // Strikethrough
632
+ ".cm-draftly-strikethrough": {
633
+ textDecoration: "line-through",
634
+ opacity: "0.7"
635
+ },
636
+ // Subscript
637
+ ".cm-draftly-subscript": {
638
+ fontSize: "0.75em",
639
+ verticalAlign: "sub"
640
+ },
641
+ // Superscript
642
+ ".cm-draftly-superscript": {
643
+ fontSize: "0.75em",
644
+ verticalAlign: "super"
645
+ },
646
+ // Inline markers (* _ ~~ ^ ~) - hidden when not focused
647
+ ".cm-draftly-inline-mark": {
648
+ display: "none"
649
+ }
650
+ });
651
+ var listMarkDecorations = {
652
+ "list-mark-ul": Decoration.mark({ class: "cm-draftly-list-mark-ul" }),
653
+ "list-mark-ol": Decoration.mark({ class: "cm-draftly-list-mark-ol" }),
654
+ "task-marker": Decoration.mark({ class: "cm-draftly-task-marker" })
655
+ };
656
+ var TaskCheckboxWidget = class extends WidgetType {
657
+ constructor(checked) {
658
+ super();
659
+ this.checked = checked;
660
+ }
661
+ eq(other) {
662
+ return other.checked === this.checked;
663
+ }
664
+ toDOM(view) {
665
+ const wrap = document.createElement("span");
666
+ wrap.className = `cm-draftly-task-checkbox ${this.checked ? "checked" : ""}`;
667
+ wrap.setAttribute("aria-hidden", "true");
668
+ const checkbox = document.createElement("input");
669
+ checkbox.type = "checkbox";
670
+ checkbox.checked = this.checked;
671
+ checkbox.tabIndex = -1;
672
+ checkbox.addEventListener("mousedown", (e) => {
673
+ e.preventDefault();
674
+ const pos = view.posAtDOM(wrap);
675
+ const line = view.state.doc.lineAt(pos);
676
+ const match = line.text.match(/^(\s*[-*+]\s*)\[([ xX])\]/);
677
+ if (match) {
678
+ const markerStart = line.from + match[1].length + 1;
679
+ const newChar = this.checked ? " " : "x";
680
+ view.dispatch({
681
+ changes: { from: markerStart, to: markerStart + 1, insert: newChar }
682
+ });
683
+ }
684
+ });
685
+ wrap.appendChild(checkbox);
686
+ return wrap;
687
+ }
688
+ ignoreEvent() {
689
+ return false;
690
+ }
691
+ };
692
+ var ListPlugin = class extends DecorationPlugin {
693
+ name = "list";
694
+ version = "1.0.0";
695
+ /**
696
+ * Moderate priority
697
+ */
698
+ get decorationPriority() {
699
+ return 20;
700
+ }
701
+ /**
702
+ * Get the extensions for this plugin (theme)
703
+ */
704
+ getExtensions() {
705
+ return [listTheme];
706
+ }
707
+ /**
708
+ * Build list decorations by iterating the syntax tree
709
+ */
710
+ buildDecorations(ctx) {
711
+ const { view, decorations } = ctx;
712
+ const tree = syntaxTree(view.state);
713
+ tree.iterate({
714
+ enter: (node) => {
715
+ const { from, to, name } = node;
716
+ const line = view.state.doc.lineAt(from);
717
+ const cursorInLine = ctx.cursorInRange(line.from, line.to);
718
+ if (name === "ListMark") {
719
+ const parent = node.node.parent;
720
+ const grandparent = parent?.parent;
721
+ const listType = grandparent?.name;
722
+ if (!cursorInLine) {
723
+ if (listType === "OrderedList") {
724
+ decorations.push(listMarkDecorations["list-mark-ol"].range(from, to));
725
+ } else {
726
+ decorations.push(listMarkDecorations["list-mark-ul"].range(from, to));
727
+ }
728
+ }
729
+ }
730
+ if (name === "TaskMarker") {
731
+ const text = view.state.sliceDoc(from, to);
732
+ const isChecked = text.includes("x") || text.includes("X");
733
+ if (cursorInLine) {
734
+ decorations.push(listMarkDecorations["task-marker"].range(from, to));
735
+ } else {
736
+ decorations.push(
737
+ Decoration.replace({
738
+ widget: new TaskCheckboxWidget(isChecked)
739
+ }).range(from, to)
740
+ );
741
+ }
742
+ }
743
+ }
744
+ });
745
+ }
746
+ };
747
+ var listTheme = EditorView.theme({
748
+ // Unordered List markers (*, -, +)
749
+ ".cm-draftly-list-mark-ul": {
750
+ position: "relative"
751
+ },
752
+ ".cm-draftly-list-mark-ul > span": {
753
+ visibility: "hidden"
754
+ },
755
+ ".cm-draftly-list-mark-ul::after": {
756
+ content: '"\u2022"',
757
+ position: "absolute",
758
+ left: "50%",
759
+ top: "50%",
760
+ transform: "translate(-50%, -50%)",
761
+ color: "var(--color-link)",
762
+ fontWeight: "bold",
763
+ pointerEvents: "none"
764
+ },
765
+ // Ordered List markers (1., 2.)
766
+ ".cm-draftly-list-mark-ol": {
767
+ color: "var(--draftly-highlight, #a4a4a4)",
768
+ fontFamily: "monospace",
769
+ marginRight: "2px"
770
+ },
771
+ // Task markers text ([ ] or [x]) - visible only when editing
772
+ ".cm-draftly-task-marker": {
773
+ color: "var(--draftly-highlight, #a4a4a4)",
774
+ fontFamily: "monospace"
775
+ },
776
+ // Task Checkbox Widget
777
+ ".cm-draftly-task-checkbox": {
778
+ display: "inline-flex",
779
+ verticalAlign: "middle",
780
+ marginRight: "0.3em",
781
+ cursor: "pointer",
782
+ userSelect: "none",
783
+ alignItems: "center",
784
+ height: "1.2em"
785
+ },
786
+ ".cm-draftly-task-checkbox input": {
787
+ cursor: "pointer",
788
+ margin: 0,
789
+ width: "1.1em",
790
+ height: "1.1em",
791
+ appearance: "none",
792
+ border: "1px solid",
793
+ borderRadius: "0.25em",
794
+ backgroundColor: "transparent",
795
+ position: "relative"
796
+ },
797
+ ".cm-draftly-task-checkbox.checked input::after": {
798
+ content: '"\u2713"',
799
+ position: "absolute",
800
+ left: "1px",
801
+ top: "-3px"
802
+ }
803
+ });
804
+ var htmlMarkDecorations = {
805
+ "html-tag": Decoration.mark({ class: "cm-draftly-html-tag" }),
806
+ "html-comment": Decoration.mark({ class: "cm-draftly-html-comment" })
807
+ };
808
+ var htmlLineDecorations = {
809
+ "html-block": Decoration.line({ class: "cm-draftly-line-html-block" }),
810
+ "hidden-line": Decoration.line({ class: "cm-draftly-hidden-line" })
811
+ };
812
+ var HTMLPreviewWidget = class extends WidgetType {
813
+ constructor(html) {
814
+ super();
815
+ this.html = html;
816
+ }
817
+ eq(other) {
818
+ return other.html === this.html;
819
+ }
820
+ toDOM() {
821
+ const div = document.createElement("div");
822
+ div.className = "cm-draftly-html-preview";
823
+ div.innerHTML = DOMPurify.sanitize(this.html);
824
+ return div;
825
+ }
826
+ ignoreEvent() {
827
+ return false;
828
+ }
829
+ };
830
+ var InlineHTMLPreviewWidget = class extends WidgetType {
831
+ constructor(html) {
832
+ super();
833
+ this.html = html;
834
+ }
835
+ eq(other) {
836
+ return other.html === this.html;
837
+ }
838
+ toDOM() {
839
+ const span = document.createElement("span");
840
+ span.className = "cm-draftly-inline-html-preview";
841
+ span.innerHTML = DOMPurify.sanitize(this.html);
842
+ return span;
843
+ }
844
+ ignoreEvent() {
845
+ return false;
846
+ }
847
+ };
848
+ function parseHTMLTag(content) {
849
+ const match = content.match(/^<\s*(\/?)([a-zA-Z][a-zA-Z0-9-]*)[^>]*(\/?)>$/);
850
+ if (!match) return null;
851
+ return {
852
+ tagName: match[2].toLowerCase(),
853
+ isClosing: match[1] === "/",
854
+ isSelfClosing: match[3] === "/" || ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"].includes(
855
+ match[2].toLowerCase()
856
+ )
857
+ };
858
+ }
859
+ var HTMLPlugin = class extends DecorationPlugin {
860
+ name = "html";
861
+ version = "1.0.0";
862
+ get decorationPriority() {
863
+ return 30;
864
+ }
865
+ getExtensions() {
866
+ return [htmlTheme];
867
+ }
868
+ buildDecorations(ctx) {
869
+ const { view, decorations } = ctx;
870
+ const tree = syntaxTree(view.state);
871
+ const htmlGroups = [];
872
+ const htmlTags = [];
873
+ tree.iterate({
874
+ enter: (node) => {
875
+ const { from, to, name } = node;
876
+ if (name === "Comment") {
877
+ decorations.push(htmlMarkDecorations["html-comment"].range(from, to));
878
+ return;
879
+ }
880
+ if (name === "HTMLTag") {
881
+ const content = view.state.sliceDoc(from, to);
882
+ const parsed = parseHTMLTag(content);
883
+ if (parsed) {
884
+ htmlTags.push({
885
+ from,
886
+ to,
887
+ tagName: parsed.tagName,
888
+ isClosing: parsed.isClosing,
889
+ isSelfClosing: parsed.isSelfClosing
890
+ });
891
+ }
892
+ }
893
+ if (name === "HTMLBlock") {
894
+ const last = htmlGroups[htmlGroups.length - 1];
895
+ if (last) {
896
+ const gap = view.state.sliceDoc(last.to, from);
897
+ if (!gap.trim()) {
898
+ last.to = to;
899
+ return;
900
+ }
901
+ }
902
+ htmlGroups.push({ from, to });
903
+ }
904
+ }
905
+ });
906
+ const inlineElements = [];
907
+ const usedTags = /* @__PURE__ */ new Set();
908
+ for (let i = 0; i < htmlTags.length; i++) {
909
+ if (usedTags.has(i)) continue;
910
+ const openTag = htmlTags[i];
911
+ if (openTag.isClosing) continue;
912
+ if (openTag.isSelfClosing) {
913
+ inlineElements.push({
914
+ from: openTag.from,
915
+ to: openTag.to,
916
+ content: view.state.sliceDoc(openTag.from, openTag.to)
917
+ });
918
+ usedTags.add(i);
919
+ continue;
920
+ }
921
+ const openLine = view.state.doc.lineAt(openTag.from);
922
+ let depth = 1;
923
+ let closeTagIndex = null;
924
+ for (let j = i + 1; j < htmlTags.length && depth > 0; j++) {
925
+ const tag = htmlTags[j];
926
+ if (tag.from > openLine.to) break;
927
+ if (tag.tagName === openTag.tagName) {
928
+ if (tag.isClosing) {
929
+ depth--;
930
+ if (depth === 0) {
931
+ closeTagIndex = j;
932
+ }
933
+ } else if (!tag.isSelfClosing) {
934
+ depth++;
935
+ }
936
+ }
937
+ }
938
+ if (closeTagIndex !== null) {
939
+ const closeTag = htmlTags[closeTagIndex];
940
+ inlineElements.push({
941
+ from: openTag.from,
942
+ to: closeTag.to,
943
+ content: view.state.sliceDoc(openTag.from, closeTag.to)
944
+ });
945
+ for (let k = i; k <= closeTagIndex; k++) {
946
+ usedTags.add(k);
947
+ }
948
+ }
949
+ }
950
+ inlineElements.sort((a, b) => a.from - b.from);
951
+ const filteredElements = [];
952
+ let lastEnd = -1;
953
+ for (const elem of inlineElements) {
954
+ if (elem.from >= lastEnd) {
955
+ filteredElements.push(elem);
956
+ lastEnd = elem.to;
957
+ }
958
+ }
959
+ for (const elem of filteredElements) {
960
+ const cursorInRange2 = ctx.cursorInRange(elem.from, elem.to);
961
+ if (cursorInRange2) {
962
+ for (const tag of htmlTags) {
963
+ if (tag.from >= elem.from && tag.to <= elem.to) {
964
+ decorations.push(htmlMarkDecorations["html-tag"].range(tag.from, tag.to));
965
+ }
966
+ }
967
+ } else {
968
+ decorations.push(
969
+ Decoration.replace({
970
+ widget: new InlineHTMLPreviewWidget(elem.content)
971
+ }).range(elem.from, elem.to)
972
+ );
973
+ }
974
+ }
975
+ for (let i = 0; i < htmlTags.length; i++) {
976
+ if (!usedTags.has(i)) {
977
+ const tag = htmlTags[i];
978
+ decorations.push(htmlMarkDecorations["html-tag"].range(tag.from, tag.to));
979
+ }
980
+ }
981
+ for (const group of htmlGroups) {
982
+ const { from, to } = group;
983
+ const nodeLineStart = view.state.doc.lineAt(from);
984
+ const nodeLineEnd = view.state.doc.lineAt(to);
985
+ const cursorInRange2 = ctx.cursorInRange(nodeLineStart.from, nodeLineEnd.to);
986
+ if (cursorInRange2) {
987
+ for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
988
+ const line = view.state.doc.line(i);
989
+ decorations.push(htmlLineDecorations["html-block"].range(line.from));
990
+ }
991
+ } else {
992
+ const htmlContent = view.state.sliceDoc(from, to);
993
+ decorations.push(
994
+ Decoration.replace({
995
+ widget: new HTMLPreviewWidget(htmlContent.trim())
996
+ }).range(from, nodeLineStart.to)
997
+ );
998
+ for (let i = nodeLineStart.number + 1; i <= nodeLineEnd.number; i++) {
999
+ const line = view.state.doc.line(i);
1000
+ decorations.push(htmlLineDecorations["hidden-line"].range(line.from));
1001
+ }
1002
+ }
1003
+ }
1004
+ }
1005
+ };
1006
+ var htmlTheme = EditorView.theme({
1007
+ ".cm-draftly-html-tag": {
1008
+ color: "#6a737d",
1009
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
1010
+ fontSize: "0.85em"
1011
+ },
1012
+ ".cm-draftly-html-comment": {
1013
+ color: "#6a737d",
1014
+ fontStyle: "italic",
1015
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
1016
+ fontSize: "0.85em",
1017
+ opacity: 0.5
1018
+ },
1019
+ ".cm-draftly-line-html-block": {
1020
+ backgroundColor: "rgba(0, 0, 0, 0.02)"
1021
+ },
1022
+ ".cm-draftly-hidden-line": {
1023
+ display: "none"
1024
+ },
1025
+ ".cm-draftly-html-preview": {
1026
+ display: "inline-block",
1027
+ width: "100%",
1028
+ verticalAlign: "top",
1029
+ margin: "0",
1030
+ whiteSpace: "normal",
1031
+ lineHeight: "1.4"
1032
+ },
1033
+ ".cm-draftly-html-preview > *:first-child": {
1034
+ marginTop: "0"
1035
+ },
1036
+ ".cm-draftly-html-preview > *:last-child": {
1037
+ marginBottom: "0"
1038
+ },
1039
+ ".cm-draftly-inline-html-preview": {
1040
+ display: "inline",
1041
+ whiteSpace: "normal"
1042
+ }
1043
+ });
1044
+
1045
+ // src/plugins/plugins.ts
1046
+ var defaultPlugins = [new HeadingPlugin(), new InlinePlugin(), new ListPlugin(), new HTMLPlugin()];
1047
+
1048
+ // src/draftly.ts
1049
+ function draftly(config = {}) {
1050
+ const {
1051
+ plugins = [],
1052
+ extensions = [],
1053
+ keymap: configKeymap = [],
1054
+ disableViewPlugin = false,
1055
+ defaultKeybindings = true,
1056
+ history: configHistory = true,
1057
+ indentWithTab: configIndentWithTab = true,
1058
+ drawSelection: configDrawSelection = true,
1059
+ highlightActiveLine: configHighlightActiveLine = true,
1060
+ rectangularSelection: configRectangularSelection = true,
1061
+ lineWrapping: configLineWrapping = true,
1062
+ onNodesChange: configOnNodesChange = void 0
1063
+ } = config;
1064
+ const allPlugins = [...defaultPlugins, ...plugins];
1065
+ const pluginExtensions = [];
1066
+ const pluginKeymaps = [];
1067
+ const markdownExtensions = [];
1068
+ const pluginContext = { config };
1069
+ for (const plugin of allPlugins) {
1070
+ plugin.onRegister(pluginContext);
1071
+ const exts = plugin.getExtensions();
1072
+ if (exts.length > 0) {
1073
+ pluginExtensions.push(...exts);
1074
+ }
1075
+ const keys = plugin.getKeymap();
1076
+ if (keys.length > 0) {
1077
+ pluginKeymaps.push(...keys);
1078
+ }
1079
+ const md = plugin.getMarkdownConfig();
1080
+ if (md) {
1081
+ markdownExtensions.push(md);
1082
+ }
1083
+ }
1084
+ if (config.markdown) {
1085
+ markdownExtensions.push(...config.markdown);
1086
+ }
1087
+ const markdownSupport = markdown({
1088
+ base: markdownLanguage,
1089
+ codeLanguages: languages,
1090
+ extensions: markdownExtensions,
1091
+ addKeymap: true,
1092
+ completeHTMLTags: true
1093
+ });
1094
+ const baseExtensions = [
1095
+ ...defaultKeybindings ? [keymap.of(defaultKeymap)] : [],
1096
+ ...configHistory ? [history(), keymap.of(historyKeymap)] : [],
1097
+ ...configIndentWithTab ? [indentOnInput(), keymap.of([indentWithTab])] : [],
1098
+ ...configDrawSelection ? [drawSelection()] : [],
1099
+ ...configHighlightActiveLine && disableViewPlugin ? [highlightActiveLine()] : [],
1100
+ ...configRectangularSelection ? [rectangularSelection()] : []
1101
+ ];
1102
+ const draftlyExtensions = [];
1103
+ if (!disableViewPlugin) draftlyExtensions.push(createDraftlyViewExtension(allPlugins, configOnNodesChange));
1104
+ if (!disableViewPlugin || configLineWrapping) draftlyExtensions.push(EditorView.lineWrapping);
1105
+ const composedExtensions = [
1106
+ // Core markdown support (highest priority)
1107
+ Prec.high(markdownSupport),
1108
+ Prec.high(keymap.of(markdownKeymap)),
1109
+ // Core CodeMirror extensions
1110
+ ...baseExtensions,
1111
+ // draftly view plugin for rich rendering
1112
+ ...draftlyExtensions,
1113
+ // Plugin extensions & keymaps
1114
+ ...pluginExtensions,
1115
+ pluginKeymaps.length > 0 ? keymap.of(pluginKeymaps) : [],
1116
+ // Config keymaps & extensions
1117
+ configKeymap.length > 0 ? keymap.of(configKeymap) : [],
1118
+ ...extensions
1119
+ ];
1120
+ return composedExtensions;
1121
+ }
1122
+
1123
+ // src/index.ts
1124
+ var index_default = draftly;
1125
+
1126
+ export { DecorationPlugin, DraftlyPlugin, DraftlyPluginsFacet, SyntaxPlugin, createDraftlyViewExtension, index_default as default, draftly, draftlyOnNodesChangeFacet, draftlyViewPlugin };
1127
+ //# sourceMappingURL=index.js.map
1128
+ //# sourceMappingURL=index.js.map