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