@tiptap/markdown 3.7.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,761 @@
1
+ // src/Extension.ts
2
+ import {
3
+ commands,
4
+ Extension
5
+ } from "@tiptap/core";
6
+
7
+ // src/MarkdownManager.ts
8
+ import {
9
+ flattenExtensions,
10
+ generateJSON,
11
+ getExtensionField
12
+ } from "@tiptap/core";
13
+ import { marked } from "marked";
14
+
15
+ // src/utils.ts
16
+ function wrapInMarkdownBlock(prefix, content) {
17
+ const lines = content.split("\n");
18
+ const output = lines.flatMap((line) => [line, ""]).map((line) => `${prefix}${line}`).join("\n");
19
+ return output.slice(0, output.length - 1);
20
+ }
21
+ function findMarksToClose(activeMarks, currentMarks) {
22
+ const marksToClose = [];
23
+ Array.from(activeMarks.keys()).forEach((markType) => {
24
+ if (!currentMarks.has(markType)) {
25
+ marksToClose.push(markType);
26
+ }
27
+ });
28
+ return marksToClose.reverse();
29
+ }
30
+ function findMarksToOpen(activeMarks, currentMarks) {
31
+ const marksToOpen = [];
32
+ Array.from(currentMarks.entries()).forEach(([markType, mark]) => {
33
+ if (!activeMarks.has(markType)) {
34
+ marksToOpen.push({ type: markType, mark });
35
+ }
36
+ });
37
+ return marksToOpen;
38
+ }
39
+ function findMarksToCloseAtEnd(activeMarks, currentMarks, nextNode, markSetsEqual) {
40
+ const isLastNode = !nextNode;
41
+ const nextNodeHasNoMarks = nextNode && nextNode.type === "text" && (!nextNode.marks || nextNode.marks.length === 0);
42
+ const nextNodeHasDifferentMarks = nextNode && nextNode.type === "text" && nextNode.marks && !markSetsEqual(currentMarks, new Map(nextNode.marks.map((mark) => [mark.type, mark])));
43
+ const marksToCloseAtEnd = [];
44
+ if (isLastNode || nextNodeHasNoMarks || nextNodeHasDifferentMarks) {
45
+ if (nextNode && nextNode.type === "text" && nextNode.marks) {
46
+ const nextMarks = new Map(nextNode.marks.map((mark) => [mark.type, mark]));
47
+ Array.from(activeMarks.keys()).forEach((markType) => {
48
+ if (!nextMarks.has(markType)) {
49
+ marksToCloseAtEnd.push(markType);
50
+ }
51
+ });
52
+ } else if (isLastNode || nextNodeHasNoMarks) {
53
+ marksToCloseAtEnd.push(...Array.from(activeMarks.keys()));
54
+ }
55
+ }
56
+ return marksToCloseAtEnd.reverse();
57
+ }
58
+ function closeMarksBeforeNode(activeMarks, getMarkClosing) {
59
+ let beforeMarkdown = "";
60
+ Array.from(activeMarks.keys()).reverse().forEach((markType) => {
61
+ const mark = activeMarks.get(markType);
62
+ const closeMarkdown = getMarkClosing(markType, mark);
63
+ if (closeMarkdown) {
64
+ beforeMarkdown = closeMarkdown + beforeMarkdown;
65
+ }
66
+ });
67
+ activeMarks.clear();
68
+ return beforeMarkdown;
69
+ }
70
+ function reopenMarksAfterNode(marksToReopen, activeMarks, getMarkOpening) {
71
+ let afterMarkdown = "";
72
+ Array.from(marksToReopen.entries()).forEach(([markType, mark]) => {
73
+ const openMarkdown = getMarkOpening(markType, mark);
74
+ if (openMarkdown) {
75
+ afterMarkdown += openMarkdown;
76
+ }
77
+ activeMarks.set(markType, mark);
78
+ });
79
+ return afterMarkdown;
80
+ }
81
+ function assumeContentType(content, contentType) {
82
+ if (typeof content !== "string") {
83
+ return "json";
84
+ }
85
+ return contentType;
86
+ }
87
+
88
+ // src/MarkdownManager.ts
89
+ var MarkdownManager = class {
90
+ /**
91
+ * Create a MarkdownManager.
92
+ * @param options.marked Optional marked instance to use (injected).
93
+ * @param options.markedOptions Optional options to pass to marked.setOptions
94
+ * @param options.indentation Indentation settings (style and size).
95
+ * @param options.extensions An array of Tiptap extensions to register for markdown parsing and rendering.
96
+ */
97
+ constructor(options) {
98
+ this.baseExtensions = [];
99
+ this.extensions = [];
100
+ this.lastParseResult = null;
101
+ var _a, _b, _c, _d, _e;
102
+ this.markedInstance = (_a = options == null ? void 0 : options.marked) != null ? _a : marked;
103
+ this.lexer = new this.markedInstance.Lexer();
104
+ this.indentStyle = (_c = (_b = options == null ? void 0 : options.indentation) == null ? void 0 : _b.style) != null ? _c : "space";
105
+ this.indentSize = (_e = (_d = options == null ? void 0 : options.indentation) == null ? void 0 : _d.size) != null ? _e : 2;
106
+ this.baseExtensions = (options == null ? void 0 : options.extensions) || [];
107
+ if ((options == null ? void 0 : options.markedOptions) && typeof this.markedInstance.setOptions === "function") {
108
+ this.markedInstance.setOptions(options.markedOptions);
109
+ }
110
+ this.registry = /* @__PURE__ */ new Map();
111
+ this.nodeTypeRegistry = /* @__PURE__ */ new Map();
112
+ if (options == null ? void 0 : options.extensions) {
113
+ this.baseExtensions = options.extensions;
114
+ const flattened = flattenExtensions(options.extensions);
115
+ flattened.forEach((ext) => this.registerExtension(ext, false));
116
+ }
117
+ this.lexer = new this.markedInstance.Lexer();
118
+ }
119
+ /** Returns the underlying marked instance. */
120
+ get instance() {
121
+ return this.markedInstance;
122
+ }
123
+ /** Returns the correct indentCharacter (space or tab) */
124
+ get indentCharacter() {
125
+ return this.indentStyle === "space" ? " " : " ";
126
+ }
127
+ /** Returns the correct indentString repeated X times */
128
+ get indentString() {
129
+ return this.indentCharacter.repeat(this.indentSize);
130
+ }
131
+ /** Helper to quickly check whether a marked instance is available. */
132
+ hasMarked() {
133
+ return !!this.markedInstance;
134
+ }
135
+ /**
136
+ * Register a Tiptap extension (Node/Mark/Extension). This will read
137
+ * `markdownName`, `parseMarkdown`, `renderMarkdown` and `priority` from the
138
+ * extension config (using the same resolution used across the codebase).
139
+ */
140
+ registerExtension(extension, recreateLexer = true) {
141
+ var _a, _b;
142
+ this.extensions.push(extension);
143
+ const name = extension.name;
144
+ const tokenName = getExtensionField(extension, "markdownTokenName") || name;
145
+ const parseMarkdown = getExtensionField(extension, "parseMarkdown");
146
+ const renderMarkdown = getExtensionField(extension, "renderMarkdown");
147
+ const tokenizer = getExtensionField(extension, "markdownTokenizer");
148
+ const markdownCfg = (_a = getExtensionField(extension, "markdownOptions")) != null ? _a : null;
149
+ const isIndenting = (_b = markdownCfg == null ? void 0 : markdownCfg.indentsContent) != null ? _b : false;
150
+ const spec = {
151
+ tokenName,
152
+ nodeName: name,
153
+ parseMarkdown,
154
+ renderMarkdown,
155
+ isIndenting,
156
+ tokenizer
157
+ };
158
+ if (tokenName && parseMarkdown) {
159
+ const parseExisting = this.registry.get(tokenName) || [];
160
+ parseExisting.push(spec);
161
+ this.registry.set(tokenName, parseExisting);
162
+ }
163
+ if (renderMarkdown) {
164
+ const renderExisting = this.nodeTypeRegistry.get(name) || [];
165
+ renderExisting.push(spec);
166
+ this.nodeTypeRegistry.set(name, renderExisting);
167
+ }
168
+ if (tokenizer && this.hasMarked()) {
169
+ this.registerTokenizer(tokenizer);
170
+ if (recreateLexer) {
171
+ this.lexer = new this.markedInstance.Lexer();
172
+ }
173
+ }
174
+ }
175
+ /**
176
+ * Register a custom tokenizer with marked.js for parsing non-standard markdown syntax.
177
+ */
178
+ registerTokenizer(tokenizer) {
179
+ if (!this.hasMarked()) {
180
+ return;
181
+ }
182
+ const { name, start, level = "inline", tokenize } = tokenizer;
183
+ const tokenizeInline = (src) => {
184
+ return this.lexer.inlineTokens(src);
185
+ };
186
+ const tokenizeBlock = (src) => {
187
+ return this.lexer.blockTokens(src);
188
+ };
189
+ const helper = {
190
+ inlineTokens: tokenizeInline,
191
+ blockTokens: tokenizeBlock
192
+ };
193
+ let startCb;
194
+ if (!start) {
195
+ startCb = (src) => {
196
+ const result = tokenize(src, [], helper);
197
+ if (result && result.raw) {
198
+ const index = src.indexOf(result.raw);
199
+ return index;
200
+ }
201
+ return -1;
202
+ };
203
+ } else {
204
+ startCb = typeof start === "function" ? start : (src) => src.indexOf(start);
205
+ }
206
+ const markedExtension = {
207
+ name,
208
+ level,
209
+ start: startCb,
210
+ tokenizer: (src, tokens) => {
211
+ const result = tokenize(src, tokens, helper);
212
+ if (result && result.type) {
213
+ return {
214
+ ...result,
215
+ type: result.type || name,
216
+ raw: result.raw || "",
217
+ tokens: result.tokens || []
218
+ };
219
+ }
220
+ return void 0;
221
+ },
222
+ childTokens: []
223
+ };
224
+ this.markedInstance.use({
225
+ extensions: [markedExtension]
226
+ });
227
+ }
228
+ /** Get registered handlers for a token type and try each until one succeeds. */
229
+ getHandlersForToken(type) {
230
+ try {
231
+ return this.registry.get(type) || [];
232
+ } catch {
233
+ return [];
234
+ }
235
+ }
236
+ /** Get the first handler for a token type (for backwards compatibility). */
237
+ getHandlerForToken(type) {
238
+ const markdownHandlers = this.getHandlersForToken(type);
239
+ if (markdownHandlers.length > 0) {
240
+ return markdownHandlers[0];
241
+ }
242
+ const nodeTypeHandlers = this.getHandlersForNodeType(type);
243
+ return nodeTypeHandlers.length > 0 ? nodeTypeHandlers[0] : void 0;
244
+ }
245
+ /** Get registered handlers for a node type (for rendering). */
246
+ getHandlersForNodeType(type) {
247
+ try {
248
+ return this.nodeTypeRegistry.get(type) || [];
249
+ } catch {
250
+ return [];
251
+ }
252
+ }
253
+ /**
254
+ * Serialize a ProseMirror-like JSON document (or node array) to a Markdown string
255
+ * using registered renderers and fallback renderers.
256
+ */
257
+ serialize(docOrContent) {
258
+ if (!docOrContent) {
259
+ return "";
260
+ }
261
+ if (Array.isArray(docOrContent)) {
262
+ return this.renderNodes(docOrContent, docOrContent);
263
+ }
264
+ return this.renderNodes(docOrContent, docOrContent);
265
+ }
266
+ /**
267
+ * Parse markdown string into Tiptap JSON document using registered extension handlers.
268
+ */
269
+ parse(markdown) {
270
+ if (!this.hasMarked()) {
271
+ throw new Error("No marked instance available for parsing");
272
+ }
273
+ const tokens = this.markedInstance.lexer(markdown);
274
+ const content = this.parseTokens(tokens);
275
+ return {
276
+ type: "doc",
277
+ content
278
+ };
279
+ }
280
+ /**
281
+ * Convert an array of marked tokens into Tiptap JSON nodes using registered extension handlers.
282
+ */
283
+ parseTokens(tokens) {
284
+ return tokens.map((token) => this.parseToken(token)).filter((parsed) => parsed !== null).flatMap((parsed) => Array.isArray(parsed) ? parsed : [parsed]);
285
+ }
286
+ /**
287
+ * Parse a single token into Tiptap JSON using the appropriate registered handler.
288
+ */
289
+ parseToken(token) {
290
+ if (!token.type) {
291
+ return null;
292
+ }
293
+ const handlers = this.getHandlersForToken(token.type);
294
+ const helpers = this.createParseHelpers();
295
+ const result = handlers.find((handler) => {
296
+ if (!handler.parseMarkdown) {
297
+ return false;
298
+ }
299
+ const parseResult = handler.parseMarkdown(token, helpers);
300
+ const normalized = this.normalizeParseResult(parseResult);
301
+ if (normalized && (!Array.isArray(normalized) || normalized.length > 0)) {
302
+ this.lastParseResult = normalized;
303
+ return true;
304
+ }
305
+ return false;
306
+ });
307
+ if (result && this.lastParseResult) {
308
+ const toReturn = this.lastParseResult;
309
+ this.lastParseResult = null;
310
+ return toReturn;
311
+ }
312
+ return this.parseFallbackToken(token);
313
+ }
314
+ /**
315
+ * Create the helper functions that are passed to parse handlers.
316
+ */
317
+ createParseHelpers() {
318
+ return {
319
+ parseInline: (tokens) => this.parseInlineTokens(tokens),
320
+ parseChildren: (tokens) => this.parseTokens(tokens),
321
+ createTextNode: (text, marks) => {
322
+ const node = {
323
+ type: "text",
324
+ text,
325
+ marks: marks || void 0
326
+ };
327
+ return node;
328
+ },
329
+ createNode: (type, attrs, content) => {
330
+ const node = {
331
+ type,
332
+ attrs: attrs || void 0,
333
+ content: content || void 0
334
+ };
335
+ if (!attrs || Object.keys(attrs).length === 0) {
336
+ delete node.attrs;
337
+ }
338
+ return node;
339
+ },
340
+ applyMark: (markType, content, attrs) => ({
341
+ mark: markType,
342
+ content,
343
+ attrs: attrs && Object.keys(attrs).length > 0 ? attrs : void 0
344
+ })
345
+ };
346
+ }
347
+ /**
348
+ * Parse inline tokens (bold, italic, links, etc.) into text nodes with marks.
349
+ * This is the complex part that handles mark nesting and boundaries.
350
+ */
351
+ parseInlineTokens(tokens) {
352
+ const result = [];
353
+ tokens.forEach((token) => {
354
+ if (token.type === "text") {
355
+ result.push({
356
+ type: "text",
357
+ text: token.text || ""
358
+ });
359
+ } else if (token.type) {
360
+ const markHandler = this.getHandlerForToken(token.type);
361
+ if (markHandler && markHandler.parseMarkdown) {
362
+ const helpers = this.createParseHelpers();
363
+ const parsed = markHandler.parseMarkdown(token, helpers);
364
+ if (this.isMarkResult(parsed)) {
365
+ const markedContent = this.applyMarkToContent(parsed.mark, parsed.content, parsed.attrs);
366
+ result.push(...markedContent);
367
+ } else {
368
+ const normalized = this.normalizeParseResult(parsed);
369
+ if (Array.isArray(normalized)) {
370
+ result.push(...normalized);
371
+ } else if (normalized) {
372
+ result.push(normalized);
373
+ }
374
+ }
375
+ } else if (token.tokens) {
376
+ result.push(...this.parseInlineTokens(token.tokens));
377
+ }
378
+ }
379
+ });
380
+ return result;
381
+ }
382
+ /**
383
+ * Apply a mark to content nodes.
384
+ */
385
+ applyMarkToContent(markType, content, attrs) {
386
+ return content.map((node) => {
387
+ if (node.type === "text") {
388
+ const existingMarks = node.marks || [];
389
+ const newMark = attrs ? { type: markType, attrs } : { type: markType };
390
+ return {
391
+ ...node,
392
+ marks: [...existingMarks, newMark]
393
+ };
394
+ }
395
+ return {
396
+ ...node,
397
+ content: node.content ? this.applyMarkToContent(markType, node.content, attrs) : void 0
398
+ };
399
+ });
400
+ }
401
+ /**
402
+ * Check if a parse result represents a mark to be applied.
403
+ */
404
+ isMarkResult(result) {
405
+ return result && typeof result === "object" && "mark" in result;
406
+ }
407
+ /**
408
+ * Normalize parse results to ensure they're valid JSONContent.
409
+ */
410
+ normalizeParseResult(result) {
411
+ if (!result) {
412
+ return null;
413
+ }
414
+ if (this.isMarkResult(result)) {
415
+ return result.content;
416
+ }
417
+ return result;
418
+ }
419
+ /**
420
+ * Fallback parsing for common tokens when no specific handler is registered.
421
+ */
422
+ parseFallbackToken(token) {
423
+ switch (token.type) {
424
+ case "paragraph":
425
+ return {
426
+ type: "paragraph",
427
+ content: token.tokens ? this.parseInlineTokens(token.tokens) : []
428
+ };
429
+ case "heading":
430
+ return {
431
+ type: "heading",
432
+ attrs: { level: token.depth || 1 },
433
+ content: token.tokens ? this.parseInlineTokens(token.tokens) : []
434
+ };
435
+ case "text":
436
+ return {
437
+ type: "text",
438
+ text: token.text || ""
439
+ };
440
+ case "html":
441
+ return this.parseHTMLToken(token);
442
+ case "space":
443
+ return null;
444
+ default:
445
+ if (token.tokens) {
446
+ return this.parseTokens(token.tokens);
447
+ }
448
+ return null;
449
+ }
450
+ }
451
+ /**
452
+ * Parse HTML tokens using extensions' parseHTML methods.
453
+ * This allows HTML within markdown to be parsed according to extension rules.
454
+ */
455
+ parseHTMLToken(token) {
456
+ const html = token.text || token.raw || "";
457
+ if (!html.trim()) {
458
+ return null;
459
+ }
460
+ try {
461
+ const parsed = generateJSON(html, this.baseExtensions);
462
+ if (parsed.type === "doc" && parsed.content) {
463
+ if (token.block) {
464
+ return parsed.content;
465
+ }
466
+ if (parsed.content.length === 1 && parsed.content[0].type === "paragraph" && parsed.content[0].content) {
467
+ return parsed.content[0].content;
468
+ }
469
+ return parsed.content;
470
+ }
471
+ return parsed;
472
+ } catch (error) {
473
+ throw new Error(`Failed to parse HTML in markdown: ${error}`);
474
+ }
475
+ }
476
+ renderNodeToMarkdown(node, parentNode, index = 0, level = 0) {
477
+ var _a;
478
+ if (node.type === "text") {
479
+ return node.text || "";
480
+ }
481
+ if (!node.type) {
482
+ return "";
483
+ }
484
+ const handler = this.getHandlerForToken(node.type);
485
+ if (!handler) {
486
+ return "";
487
+ }
488
+ const helpers = {
489
+ renderChildren: (nodes, separator) => {
490
+ const childLevel = handler.isIndenting ? level + 1 : level;
491
+ if (!Array.isArray(nodes) && nodes.content) {
492
+ return this.renderNodes(nodes.content, node, separator || "", index, childLevel);
493
+ }
494
+ return this.renderNodes(nodes, node, separator || "", index, childLevel);
495
+ },
496
+ indent: (content) => {
497
+ return this.indentString + content;
498
+ },
499
+ wrapInBlock: wrapInMarkdownBlock
500
+ };
501
+ const context = {
502
+ index,
503
+ level,
504
+ parentType: parentNode == null ? void 0 : parentNode.type,
505
+ meta: {}
506
+ };
507
+ const rendered = ((_a = handler.renderMarkdown) == null ? void 0 : _a.call(handler, node, helpers, context)) || "";
508
+ return rendered;
509
+ }
510
+ /**
511
+ * Render a node or an array of nodes. Parent type controls how children
512
+ * are joined (which determines newline insertion between children).
513
+ */
514
+ renderNodes(nodeOrNodes, parentNode, separator = "", index = 0, level = 0) {
515
+ if (!Array.isArray(nodeOrNodes)) {
516
+ if (!nodeOrNodes.type) {
517
+ return "";
518
+ }
519
+ return this.renderNodeToMarkdown(nodeOrNodes, parentNode, index, level);
520
+ }
521
+ return this.renderNodesWithMarkBoundaries(nodeOrNodes, parentNode, separator, level);
522
+ }
523
+ /**
524
+ * Render an array of nodes while properly tracking mark boundaries.
525
+ * This handles cases where marks span across multiple text nodes.
526
+ */
527
+ renderNodesWithMarkBoundaries(nodes, parentNode, separator = "", level = 0) {
528
+ const result = [];
529
+ const activeMarks = /* @__PURE__ */ new Map();
530
+ nodes.forEach((node, i) => {
531
+ const nextNode = i < nodes.length - 1 ? nodes[i + 1] : null;
532
+ if (!node.type) {
533
+ return;
534
+ }
535
+ if (node.type === "text") {
536
+ let textContent = node.text || "";
537
+ const currentMarks = new Map((node.marks || []).map((mark) => [mark.type, mark]));
538
+ const marksToClose = findMarksToClose(activeMarks, currentMarks);
539
+ const marksToOpen = findMarksToOpen(activeMarks, currentMarks);
540
+ marksToClose.forEach((markType) => {
541
+ const mark = activeMarks.get(markType);
542
+ const closeMarkdown = this.getMarkClosing(markType, mark);
543
+ if (closeMarkdown) {
544
+ textContent += closeMarkdown;
545
+ }
546
+ activeMarks.delete(markType);
547
+ });
548
+ marksToOpen.forEach(({ type, mark }) => {
549
+ const openMarkdown = this.getMarkOpening(type, mark);
550
+ if (openMarkdown) {
551
+ textContent = openMarkdown + textContent;
552
+ }
553
+ activeMarks.set(type, mark);
554
+ });
555
+ const marksToCloseAtEnd = findMarksToCloseAtEnd(
556
+ activeMarks,
557
+ currentMarks,
558
+ nextNode,
559
+ this.markSetsEqual.bind(this)
560
+ );
561
+ marksToCloseAtEnd.forEach((markType) => {
562
+ const mark = activeMarks.get(markType);
563
+ const closeMarkdown = this.getMarkClosing(markType, mark);
564
+ if (closeMarkdown) {
565
+ textContent += closeMarkdown;
566
+ }
567
+ activeMarks.delete(markType);
568
+ });
569
+ result.push(textContent);
570
+ } else {
571
+ const marksToReopen = new Map(activeMarks);
572
+ const beforeMarkdown = closeMarksBeforeNode(activeMarks, this.getMarkClosing.bind(this));
573
+ const nodeContent = this.renderNodeToMarkdown(node, parentNode, i, level);
574
+ const afterMarkdown = reopenMarksAfterNode(marksToReopen, activeMarks, this.getMarkOpening.bind(this));
575
+ result.push(beforeMarkdown + nodeContent + afterMarkdown);
576
+ }
577
+ });
578
+ return result.join(separator);
579
+ }
580
+ /**
581
+ * Get the opening markdown syntax for a mark type.
582
+ */
583
+ getMarkOpening(markType, mark) {
584
+ const handlers = this.getHandlersForNodeType(markType);
585
+ const handler = handlers.length > 0 ? handlers[0] : void 0;
586
+ if (!handler || !handler.renderMarkdown) {
587
+ return "";
588
+ }
589
+ const placeholder = "\uE000__TIPTAP_MARKDOWN_PLACEHOLDER__\uE001";
590
+ const syntheticNode = {
591
+ type: markType,
592
+ attrs: mark.attrs || {},
593
+ content: [{ type: "text", text: placeholder }]
594
+ };
595
+ try {
596
+ const rendered = handler.renderMarkdown(
597
+ syntheticNode,
598
+ {
599
+ renderChildren: () => placeholder,
600
+ indent: (content) => content,
601
+ wrapInBlock: (prefix, content) => prefix + content
602
+ },
603
+ { index: 0, level: 0, parentType: "text", meta: {} }
604
+ );
605
+ const placeholderIndex = rendered.indexOf(placeholder);
606
+ return placeholderIndex >= 0 ? rendered.substring(0, placeholderIndex) : "";
607
+ } catch (err) {
608
+ throw new Error(`Failed to get mark opening for ${markType}: ${err}`);
609
+ }
610
+ }
611
+ /**
612
+ * Get the closing markdown syntax for a mark type.
613
+ */
614
+ getMarkClosing(markType, mark) {
615
+ const handlers = this.getHandlersForNodeType(markType);
616
+ const handler = handlers.length > 0 ? handlers[0] : void 0;
617
+ if (!handler || !handler.renderMarkdown) {
618
+ return "";
619
+ }
620
+ const placeholder = "\uE000__TIPTAP_MARKDOWN_PLACEHOLDER__\uE001";
621
+ const syntheticNode = {
622
+ type: markType,
623
+ attrs: mark.attrs || {},
624
+ content: [{ type: "text", text: placeholder }]
625
+ };
626
+ try {
627
+ const rendered = handler.renderMarkdown(
628
+ syntheticNode,
629
+ {
630
+ renderChildren: () => placeholder,
631
+ indent: (content) => content,
632
+ wrapInBlock: (prefix, content) => prefix + content
633
+ },
634
+ { index: 0, level: 0, parentType: "text", meta: {} }
635
+ );
636
+ const placeholderIndex = rendered.indexOf(placeholder);
637
+ const placeholderEnd = placeholderIndex + placeholder.length;
638
+ return placeholderIndex >= 0 ? rendered.substring(placeholderEnd) : "";
639
+ } catch (err) {
640
+ throw new Error(`Failed to get mark closing for ${markType}: ${err}`);
641
+ }
642
+ }
643
+ /**
644
+ * Check if two mark sets are equal.
645
+ */
646
+ markSetsEqual(marks1, marks2) {
647
+ if (marks1.size !== marks2.size) {
648
+ return false;
649
+ }
650
+ return Array.from(marks1.keys()).every((type) => marks2.has(type));
651
+ }
652
+ };
653
+ var MarkdownManager_default = MarkdownManager;
654
+
655
+ // src/Extension.ts
656
+ var Markdown = Extension.create({
657
+ name: "markdown",
658
+ addOptions() {
659
+ return {
660
+ indentation: { style: "space", size: 2 },
661
+ marked: void 0,
662
+ markedOptions: {}
663
+ };
664
+ },
665
+ addCommands() {
666
+ return {
667
+ setContent: (content, options) => {
668
+ if (!(options == null ? void 0 : options.contentType)) {
669
+ return commands.setContent(content, options);
670
+ }
671
+ const actualContentType = assumeContentType(content, options == null ? void 0 : options.contentType);
672
+ if (actualContentType !== "markdown" || !this.editor.markdown) {
673
+ return commands.setContent(content, options);
674
+ }
675
+ const mdContent = this.editor.markdown.parse(content);
676
+ return commands.setContent(mdContent, options);
677
+ },
678
+ insertContent: (value, options) => {
679
+ if (!(options == null ? void 0 : options.contentType)) {
680
+ return commands.insertContent(value, options);
681
+ }
682
+ const actualContentType = assumeContentType(value, options == null ? void 0 : options.contentType);
683
+ if (actualContentType !== "markdown" || !this.editor.markdown) {
684
+ return commands.insertContent(value, options);
685
+ }
686
+ const mdContent = this.editor.markdown.parse(value);
687
+ return commands.insertContent(mdContent, options);
688
+ },
689
+ insertContentAt: (position, value, options) => {
690
+ if (!(options == null ? void 0 : options.contentType)) {
691
+ return commands.insertContentAt(position, value, options);
692
+ }
693
+ const actualContentType = assumeContentType(value, options == null ? void 0 : options.contentType);
694
+ if (actualContentType !== "markdown" || !this.editor.markdown) {
695
+ return commands.insertContentAt(position, value, options);
696
+ }
697
+ const mdContent = this.editor.markdown.parse(value);
698
+ return commands.insertContentAt(position, mdContent, options);
699
+ }
700
+ };
701
+ },
702
+ addStorage() {
703
+ return {
704
+ manager: new MarkdownManager_default({
705
+ indentation: this.options.indentation,
706
+ marked: this.options.marked,
707
+ markedOptions: this.options.markedOptions,
708
+ extensions: []
709
+ })
710
+ };
711
+ },
712
+ onBeforeCreate() {
713
+ if (this.editor.markdown) {
714
+ console.error(
715
+ "[tiptap][markdown]: There is already a `markdown` property on the editor instance. This might lead to unexpected behavior."
716
+ );
717
+ return;
718
+ }
719
+ this.storage.manager = new MarkdownManager_default({
720
+ indentation: this.options.indentation,
721
+ marked: this.options.marked,
722
+ markedOptions: this.options.markedOptions,
723
+ extensions: this.editor.extensionManager.baseExtensions
724
+ });
725
+ this.editor.markdown = this.storage.manager;
726
+ this.editor.getMarkdown = () => {
727
+ return this.storage.manager.serialize(this.editor.getJSON());
728
+ };
729
+ if (!this.editor.options.contentType) {
730
+ return;
731
+ }
732
+ const assumedType = assumeContentType(this.editor.options.content, this.editor.options.contentType);
733
+ if (assumedType !== "markdown") {
734
+ return;
735
+ }
736
+ if (!this.editor.markdown) {
737
+ throw new Error(
738
+ '[tiptap][markdown]: The `contentType` option is set to "markdown", but the Markdown extension is not added to the editor. Please add the Markdown extension to use this feature.'
739
+ );
740
+ }
741
+ if (!this.editor.options.content || typeof this.editor.options.content !== "string") {
742
+ throw new Error(
743
+ '[tiptap][markdown]: The `contentType` option is set to "markdown", but the initial content is not a string. Please provide the initial content as a markdown string.'
744
+ );
745
+ }
746
+ const json = this.editor.markdown.parse(this.editor.options.content);
747
+ this.editor.options.content = json;
748
+ }
749
+ });
750
+ export {
751
+ Markdown,
752
+ MarkdownManager,
753
+ assumeContentType,
754
+ closeMarksBeforeNode,
755
+ findMarksToClose,
756
+ findMarksToCloseAtEnd,
757
+ findMarksToOpen,
758
+ reopenMarksAfterNode,
759
+ wrapInMarkdownBlock
760
+ };
761
+ //# sourceMappingURL=index.js.map