@lofcz/platejs-markdown 52.3.5

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,1935 @@
1
+ import { ElementApi, KEYS, TextApi, bindFirst, createTSlatePlugin, getPluginKey, getPluginType, isUrl } from "platejs";
2
+ import "mdast-util-mdx";
3
+ import kebabCase from "lodash/kebabCase.js";
4
+ import remarkStringify from "remark-stringify";
5
+ import { unified } from "unified";
6
+ import remarkParse from "remark-parse";
7
+ import { marked } from "marked";
8
+ import baseRemarkMdx from "remark-mdx";
9
+ import { visit } from "unist-util-visit";
10
+
11
+ //#region src/lib/types.ts
12
+ const PLATE_TO_MDAST = {
13
+ a: "link",
14
+ blockquote: "blockquote",
15
+ bold: "strong",
16
+ callout: "callout",
17
+ code: "inlineCode",
18
+ code_block: "code",
19
+ code_line: "code_line",
20
+ column: "column",
21
+ column_group: "column_group",
22
+ comment: "comment",
23
+ date: "date",
24
+ equation: "math",
25
+ heading: "heading",
26
+ hr: "thematicBreak",
27
+ img: "image",
28
+ inline_equation: "inlineMath",
29
+ italic: "emphasis",
30
+ li: "listItem",
31
+ list: "list",
32
+ mention: "mention",
33
+ p: "paragraph",
34
+ strikethrough: "delete",
35
+ subscript: "sub",
36
+ suggestion: "suggestion",
37
+ superscript: "sup",
38
+ table: "table",
39
+ td: "tableCell",
40
+ text: "text",
41
+ th: "tableCell",
42
+ toc: "toc",
43
+ toggle: "toggle",
44
+ tr: "tableRow",
45
+ underline: "u"
46
+ };
47
+ const MDAST_TO_PLATE = {
48
+ backgroundColor: "backgroundColor",
49
+ blockquote: "blockquote",
50
+ break: "break",
51
+ code: "code_block",
52
+ color: "color",
53
+ definition: "definition",
54
+ del: "strikethrough",
55
+ delete: "strikethrough",
56
+ emphasis: "italic",
57
+ fontFamily: "fontFamily",
58
+ fontSize: "fontSize",
59
+ fontWeight: "fontWeight",
60
+ footnoteDefinition: "footnoteDefinition",
61
+ footnoteReference: "footnoteReference",
62
+ heading: "heading",
63
+ html: "html",
64
+ image: "img",
65
+ imageReference: "imageReference",
66
+ inlineCode: "code",
67
+ inlineMath: "inline_equation",
68
+ link: "a",
69
+ linkReference: "linkReference",
70
+ list: "list",
71
+ listItem: "li",
72
+ mark: "highlight",
73
+ math: "equation",
74
+ mdxFlowExpression: "mdxFlowExpression",
75
+ mdxjsEsm: "mdxjsEsm",
76
+ mdxJsxFlowElement: "mdxJsxFlowElement",
77
+ mdxJsxTextElement: "mdxJsxTextElement",
78
+ mdxTextExpression: "mdxTextExpression",
79
+ paragraph: "p",
80
+ strong: "bold",
81
+ sub: "subscript",
82
+ sup: "superscript",
83
+ table: "table",
84
+ tableCell: "td",
85
+ tableRow: "tr",
86
+ text: "text",
87
+ thematicBreak: "hr",
88
+ u: "underline",
89
+ yaml: "yaml"
90
+ };
91
+ /**
92
+ * Get plate node type from mdast node type if the mdast is mdast only return
93
+ * the mdast type itself.
94
+ */
95
+ const mdastToPlate = (editor, mdastType) => {
96
+ const plateKey = MDAST_TO_PLATE[mdastType];
97
+ return getPluginKey(editor, plateKey) ?? plateKey ?? mdastType;
98
+ };
99
+ /**
100
+ * Get mdast node type from plate element type if the plateType is plate only
101
+ * return the plateType itself.
102
+ */
103
+ const plateToMdast = (plateType) => PLATE_TO_MDAST[plateType] ?? plateType;
104
+
105
+ //#endregion
106
+ //#region src/lib/serializer/utils/getCustomMark.ts
107
+ const getCustomMark = (options) => {
108
+ if (!options?.rules) return [];
109
+ return Object.entries(options.rules).filter(([_, parser]) => parser?.mark).map(([key]) => key);
110
+ };
111
+
112
+ //#endregion
113
+ //#region src/lib/rules/utils/parseAttributes.ts
114
+ function parseAttributes(attributes) {
115
+ const props = {};
116
+ if (attributes && attributes.length > 0) attributes.forEach((attr) => {
117
+ if (attr.name && attr.value !== void 0) {
118
+ let value = attr.value;
119
+ try {
120
+ value = JSON.parse(attr.value);
121
+ } catch (_error) {
122
+ value = attr.value;
123
+ }
124
+ props[attr.name] = value;
125
+ }
126
+ });
127
+ return props;
128
+ }
129
+ function propsToAttributes(props) {
130
+ return Object.entries(props).map(([name, value]) => ({
131
+ name,
132
+ type: "mdxJsxAttribute",
133
+ value: typeof value === "string" ? value : JSON.stringify(value)
134
+ }));
135
+ }
136
+
137
+ //#endregion
138
+ //#region src/lib/rules/columnRules.ts
139
+ const columnRules = {
140
+ column: {
141
+ deserialize: (mdastNode, deco, options) => {
142
+ const props = parseAttributes(mdastNode.attributes);
143
+ return {
144
+ children: convertChildrenDeserialize(mdastNode.children, { ...deco }, options),
145
+ type: getPluginType(options.editor, KEYS.column),
146
+ ...props
147
+ };
148
+ },
149
+ serialize: (node, options) => {
150
+ const { id, children, type, ...rest } = node;
151
+ return {
152
+ attributes: propsToAttributes(rest),
153
+ children: convertNodesSerialize(children, options),
154
+ name: type,
155
+ type: "mdxJsxFlowElement"
156
+ };
157
+ }
158
+ },
159
+ column_group: {
160
+ deserialize: (mdastNode, deco, options) => {
161
+ const props = parseAttributes(mdastNode.attributes);
162
+ return {
163
+ children: convertChildrenDeserialize(mdastNode.children, { ...deco }, options),
164
+ type: getPluginType(options.editor, KEYS.columnGroup),
165
+ ...props
166
+ };
167
+ },
168
+ serialize: (node, options) => {
169
+ const { id, children, type, ...rest } = node;
170
+ return {
171
+ attributes: propsToAttributes(rest),
172
+ children: convertNodesSerialize(children, options),
173
+ name: type,
174
+ type: "mdxJsxFlowElement"
175
+ };
176
+ }
177
+ }
178
+ };
179
+
180
+ //#endregion
181
+ //#region src/lib/rules/fontRules.ts
182
+ function createFontRule(propName) {
183
+ const styleName = kebabCase(propName);
184
+ return {
185
+ mark: true,
186
+ serialize: (slateNode) => ({
187
+ attributes: [{
188
+ name: "style",
189
+ type: "mdxJsxAttribute",
190
+ value: `${styleName}: ${slateNode[propName]};`
191
+ }],
192
+ children: [{
193
+ type: "text",
194
+ value: slateNode.text
195
+ }],
196
+ name: "span",
197
+ type: "mdxJsxTextElement"
198
+ })
199
+ };
200
+ }
201
+ const fontRules = {
202
+ backgroundColor: createFontRule("backgroundColor"),
203
+ color: createFontRule("color"),
204
+ fontFamily: createFontRule("fontFamily"),
205
+ fontSize: createFontRule("fontSize"),
206
+ fontWeight: createFontRule("fontWeight"),
207
+ span: {
208
+ mark: true,
209
+ deserialize: (mdastNode, deco, options) => {
210
+ const fontFamily = getStyleValue(mdastNode, "font-family");
211
+ const fontSize = getStyleValue(mdastNode, "font-size");
212
+ const fontWeight = getStyleValue(mdastNode, "font-weight");
213
+ const color = getStyleValue(mdastNode, "color");
214
+ const backgroundColor = getStyleValue(mdastNode, "background-color");
215
+ return convertChildrenDeserialize(mdastNode.children, {
216
+ ...deco,
217
+ backgroundColor,
218
+ color,
219
+ fontFamily,
220
+ fontSize,
221
+ fontWeight
222
+ }, options);
223
+ }
224
+ }
225
+ };
226
+
227
+ //#endregion
228
+ //#region src/lib/rules/mediaRules.ts
229
+ function createMediaRule() {
230
+ return {
231
+ deserialize: (node) => {
232
+ const { src, ...props } = parseAttributes(node.attributes);
233
+ return {
234
+ children: [{ text: "" }],
235
+ type: node.name,
236
+ url: src,
237
+ ...props
238
+ };
239
+ },
240
+ serialize: (node, options) => {
241
+ const { id, children, type, url, ...rest } = node;
242
+ return {
243
+ attributes: propsToAttributes({
244
+ ...rest,
245
+ src: url
246
+ }),
247
+ children: convertNodesSerialize(children, options),
248
+ name: type,
249
+ type: "mdxJsxFlowElement"
250
+ };
251
+ }
252
+ };
253
+ }
254
+ const mediaRules = {
255
+ audio: createMediaRule(),
256
+ file: createMediaRule(),
257
+ video: createMediaRule()
258
+ };
259
+
260
+ //#endregion
261
+ //#region src/lib/serializer/utils/getMergedOptionsSerialize.ts
262
+ /**
263
+ * Merges Markdown configurations, following the principle that options take
264
+ * precedence
265
+ *
266
+ * @param editor Editor instance used to get plugin default configurations
267
+ * @param options User-provided options (higher priority)
268
+ * @returns The final merged configuration
269
+ */
270
+ const getMergedOptionsSerialize = (editor, options) => {
271
+ const { allowedNodes: PluginAllowedNodes, allowNode: PluginAllowNode, disallowedNodes: PluginDisallowedNodes, plainMarks: PluginPlainMarks, remarkPlugins: PluginRemarkPlugins, remarkStringifyOptions: PluginRemarkStringifyOptions, rules: PluginRules } = editor.getOptions(MarkdownPlugin);
272
+ const mergedRules = {
273
+ ...buildRules(editor),
274
+ ...options?.rules ?? PluginRules
275
+ };
276
+ return {
277
+ allowedNodes: options?.allowedNodes ?? PluginAllowedNodes,
278
+ allowNode: options?.allowNode ?? PluginAllowNode,
279
+ disallowedNodes: options?.disallowedNodes ?? PluginDisallowedNodes,
280
+ editor,
281
+ plainMarks: options?.plainMarks ?? PluginPlainMarks,
282
+ preserveEmptyParagraphs: options?.preserveEmptyParagraphs,
283
+ remarkPlugins: options?.remarkPlugins ?? PluginRemarkPlugins ?? [],
284
+ remarkStringifyOptions: options?.remarkStringifyOptions ?? PluginRemarkStringifyOptions,
285
+ rules: mergedRules,
286
+ spread: options?.spread,
287
+ value: options?.value ?? editor.children,
288
+ withBlockId: options?.withBlockId ?? false
289
+ };
290
+ };
291
+
292
+ //#endregion
293
+ //#region src/lib/serializer/utils/getSerializerByKey.ts
294
+ const getSerializerByKey = (key, options) => {
295
+ const nodes = options.rules;
296
+ const rules = buildRules(options.editor);
297
+ return nodes?.[key]?.serialize === void 0 ? rules[key]?.serialize : nodes?.[key]?.serialize;
298
+ };
299
+
300
+ //#endregion
301
+ //#region src/lib/serializer/utils/unreachable.ts
302
+ const unreachable = (value) => {
303
+ console.warn(`Unreachable code: ${JSON.stringify(value)}`);
304
+ };
305
+
306
+ //#endregion
307
+ //#region src/lib/serializer/convertTextsSerialize.ts
308
+ const basicMarkdownMarks = [
309
+ "italic",
310
+ "bold",
311
+ "strikethrough",
312
+ "code"
313
+ ];
314
+ const convertTextsSerialize = (slateTexts, options, _key) => {
315
+ const customLeaf = getCustomMark(options);
316
+ const mdastTexts = [];
317
+ const starts = [];
318
+ let ends = [];
319
+ let textTemp = "";
320
+ for (let j = 0; j < slateTexts.length; j++) {
321
+ const cur = slateTexts[j];
322
+ textTemp += cur.text;
323
+ const prevStarts = starts.slice();
324
+ const prevEnds = ends.slice();
325
+ const prev = slateTexts[j - 1];
326
+ const next = slateTexts[j + 1];
327
+ ends = [];
328
+ [...basicMarkdownMarks, ...customLeaf.filter((k) => !basicMarkdownMarks.includes(k))].forEach((key) => {
329
+ const nodeType = getPluginType(options.editor, key);
330
+ if (cur[nodeType]) {
331
+ if (options.plainMarks?.includes(key)) return;
332
+ if (!prev?.[nodeType]) starts.push(key);
333
+ if (!next?.[nodeType]) ends.push(key);
334
+ }
335
+ });
336
+ const endsToRemove = starts.reduce((acc, k, kIndex) => {
337
+ if (ends.includes(k)) acc.push({
338
+ key: k,
339
+ index: kIndex
340
+ });
341
+ return acc;
342
+ }, []);
343
+ if (starts.length > 0) {
344
+ let bef = "";
345
+ let aft = "";
346
+ if (endsToRemove.length === 1 && (prevStarts.toString() !== starts.toString() || prevEnds.includes("italic") && ends.includes("bold")) && starts.length - endsToRemove.length === 0) {
347
+ while (textTemp.startsWith(" ")) {
348
+ bef += " ";
349
+ textTemp = textTemp.slice(1);
350
+ }
351
+ while (textTemp.endsWith(" ")) {
352
+ aft += " ";
353
+ textTemp = textTemp.slice(0, -1);
354
+ }
355
+ }
356
+ let res = {
357
+ type: "text",
358
+ value: textTemp
359
+ };
360
+ textTemp = "";
361
+ starts.slice().reverse().forEach((k) => {
362
+ const nodeParser = getSerializerByKey(k, options);
363
+ if (nodeParser) res = {
364
+ ...nodeParser(cur, options),
365
+ children: [res]
366
+ };
367
+ switch (k) {
368
+ case "bold":
369
+ res = {
370
+ children: [res],
371
+ type: "strong"
372
+ };
373
+ break;
374
+ case "code": {
375
+ let currentRes = res;
376
+ while (currentRes.type !== "text" && currentRes.type !== "inlineCode") currentRes = currentRes.children[0];
377
+ currentRes.type = "inlineCode";
378
+ break;
379
+ }
380
+ case "italic":
381
+ res = {
382
+ children: [res],
383
+ type: "emphasis"
384
+ };
385
+ break;
386
+ case "strikethrough":
387
+ res = {
388
+ children: [res],
389
+ type: "delete"
390
+ };
391
+ break;
392
+ }
393
+ });
394
+ const arr = [];
395
+ if (bef.length > 0) arr.push({
396
+ type: "text",
397
+ value: bef
398
+ });
399
+ arr.push(res);
400
+ if (aft.length > 0) arr.push({
401
+ type: "text",
402
+ value: aft
403
+ });
404
+ mdastTexts.push(...arr);
405
+ }
406
+ if (endsToRemove.length > 0) endsToRemove.reverse().forEach((e) => {
407
+ starts.splice(e.index, 1);
408
+ });
409
+ else {
410
+ mdastTexts.push({
411
+ type: "text",
412
+ value: textTemp
413
+ });
414
+ textTemp = "";
415
+ }
416
+ }
417
+ if (textTemp) {
418
+ mdastTexts.push({
419
+ type: "text",
420
+ value: textTemp
421
+ });
422
+ textTemp = "";
423
+ }
424
+ return mergeTexts(mdastTexts).map((node) => {
425
+ if (!hasContent(node)) return {
426
+ type: "text",
427
+ value: ""
428
+ };
429
+ return node;
430
+ });
431
+ };
432
+ const hasContent = (node) => {
433
+ if (node.type === "inlineCode") return node.value !== "";
434
+ if (node.type === "text") return node.value !== "";
435
+ if (node.children?.length > 0) for (const child of node.children) {
436
+ if (child.type !== "emphasis" && child.type !== "strong" && child.type !== "inlineCode" && child.type !== "delete" && child.type !== "text") return true;
437
+ if (hasContent(child)) return true;
438
+ }
439
+ return false;
440
+ };
441
+ const mergeTexts = (nodes) => {
442
+ const res = [];
443
+ for (const cur of nodes) {
444
+ const last = res.at(-1);
445
+ if (last && last.type === cur.type) if (last.type === "text") last.value += cur.value;
446
+ else if (last.type === "inlineCode") last.value += cur.value;
447
+ else last.children = mergeTexts(last.children.concat(cur.children));
448
+ else {
449
+ if (cur.type === "text" && cur.value === "") continue;
450
+ res.push(cur);
451
+ }
452
+ }
453
+ return res;
454
+ };
455
+
456
+ //#endregion
457
+ //#region src/lib/serializer/wrapWithBlockId.ts
458
+ /**
459
+ * Wraps an mdast node with a block element containing an ID attribute. Used for
460
+ * preserving block IDs when serializing to markdown.
461
+ *
462
+ * @param mdastNode - The mdast node to wrap
463
+ * @param nodeId - The ID to attach to the block element
464
+ * @returns The wrapped mdast node with block element and ID attribute
465
+ */
466
+ const wrapWithBlockId = (mdastNode, nodeId) => ({
467
+ attributes: [{
468
+ name: "id",
469
+ type: "mdxJsxAttribute",
470
+ value: String(nodeId)
471
+ }],
472
+ children: [mdastNode],
473
+ data: { _mdxExplicitJsx: true },
474
+ name: "block",
475
+ type: "mdxJsxFlowElement"
476
+ });
477
+
478
+ //#endregion
479
+ //#region src/lib/serializer/listToMdastTree.ts
480
+ function listToMdastTree(nodes, options, isBlock = false) {
481
+ if (nodes.length === 0) throw new Error("Cannot create a list from empty nodes");
482
+ if (options.withBlockId && isBlock && nodes.some((node) => node.id)) return processListWithBlockIds(nodes, options);
483
+ const root = {
484
+ children: [],
485
+ ordered: nodes[0].listStyleType === "decimal",
486
+ spread: options.spread ?? false,
487
+ start: nodes[0].listStart,
488
+ type: "list"
489
+ };
490
+ const indentStack = [{
491
+ indent: nodes[0].indent,
492
+ list: root,
493
+ parent: null,
494
+ styleType: nodes[0].listStyleType
495
+ }];
496
+ for (let i = 0; i < nodes.length; i++) {
497
+ const node = nodes[i];
498
+ const currentIndent = node.indent;
499
+ while (indentStack.length > 1 && indentStack.at(-1).indent > currentIndent) indentStack.pop();
500
+ let stackTop = indentStack.at(-1);
501
+ if (!stackTop) throw new Error("Stack should never be empty");
502
+ if (stackTop.indent === currentIndent && stackTop.styleType !== node.listStyleType && !!stackTop.parent) {
503
+ const siblingList = {
504
+ children: [],
505
+ ordered: node.listStyleType === "decimal",
506
+ spread: options.spread ?? false,
507
+ start: node.listStart,
508
+ type: "list"
509
+ };
510
+ stackTop.parent.children.push(siblingList);
511
+ indentStack[indentStack.length - 1] = {
512
+ indent: currentIndent,
513
+ list: siblingList,
514
+ parent: stackTop.parent,
515
+ styleType: node.listStyleType
516
+ };
517
+ stackTop = indentStack.at(-1);
518
+ }
519
+ const listItem = {
520
+ checked: null,
521
+ children: [{
522
+ children: convertNodesSerialize(node.children, options),
523
+ type: "paragraph"
524
+ }],
525
+ type: "listItem"
526
+ };
527
+ if (options.spread) listItem.spread = true;
528
+ if (node.listStyleType === "todo" && node.checked !== void 0) listItem.checked = node.checked;
529
+ stackTop.list.children.push(listItem);
530
+ const nextNode = nodes[i + 1];
531
+ if (nextNode && nextNode.indent > currentIndent) {
532
+ const nestedList = {
533
+ children: [],
534
+ ordered: nextNode.listStyleType === "decimal",
535
+ spread: options.spread ?? false,
536
+ start: nextNode.listStart,
537
+ type: "list"
538
+ };
539
+ listItem.children.push(nestedList);
540
+ indentStack.push({
541
+ indent: nextNode.indent,
542
+ list: nestedList,
543
+ parent: listItem,
544
+ styleType: nextNode.listStyleType
545
+ });
546
+ }
547
+ }
548
+ return root;
549
+ }
550
+ /**
551
+ * Process list nodes with block IDs by wrapping each item separately This
552
+ * preserves list numbering while allowing individual block wrapping
553
+ */
554
+ function processListWithBlockIds(nodes, options) {
555
+ const fragments = [];
556
+ for (let i = 0; i < nodes.length; i++) {
557
+ const node = nodes[i];
558
+ const singleList = {
559
+ children: [],
560
+ ordered: node.listStyleType === "decimal",
561
+ spread: options.spread ?? false,
562
+ start: node.listStyleType === "decimal" ? i + 1 : void 0,
563
+ type: "list"
564
+ };
565
+ const listItem = {
566
+ checked: null,
567
+ children: [{
568
+ children: convertNodesSerialize(node.children, options),
569
+ type: "paragraph"
570
+ }],
571
+ type: "listItem"
572
+ };
573
+ if (options.spread) listItem.spread = true;
574
+ if (node.listStyleType === "todo" && node.checked !== void 0) listItem.checked = node.checked;
575
+ singleList.children.push(listItem);
576
+ if (node.id) fragments.push(wrapWithBlockId(singleList, String(node.id)));
577
+ else fragments.push(singleList);
578
+ }
579
+ return {
580
+ children: fragments,
581
+ type: "fragment"
582
+ };
583
+ }
584
+
585
+ //#endregion
586
+ //#region src/lib/serializer/convertNodesSerialize.ts
587
+ const convertNodesSerialize = (nodes, options, isBlock = false) => {
588
+ const mdastNodes = [];
589
+ let textQueue = [];
590
+ const listBlock = [];
591
+ for (let i = 0; i <= nodes.length; i++) {
592
+ const n = nodes[i];
593
+ if (n && TextApi.isText(n)) {
594
+ if (shouldIncludeText(n, options)) textQueue.push(n);
595
+ } else {
596
+ if (textQueue.length > 0) mdastNodes.push(...convertTextsSerialize(textQueue, options));
597
+ textQueue = [];
598
+ if (!n) continue;
599
+ if (!shouldIncludeNode$1(n, options)) continue;
600
+ const pType = getPluginType(options.editor, KEYS.p) ?? KEYS.p;
601
+ if (n?.type === pType && "listStyleType" in n) {
602
+ listBlock.push(n);
603
+ const next = nodes[i + 1];
604
+ const isNextIndent = next && next.type === pType && "listStyleType" in next;
605
+ const firstList = listBlock.at(0);
606
+ const hasDifferentListStyle = isNextIndent && firstList && next.listStyleType !== firstList.listStyleType && next.indent === firstList.indent;
607
+ if (!isNextIndent || hasDifferentListStyle) {
608
+ const result = listToMdastTree(listBlock, options, isBlock);
609
+ if (result.type === "fragment") mdastNodes.push(...result.children);
610
+ else mdastNodes.push(result);
611
+ listBlock.length = 0;
612
+ }
613
+ } else {
614
+ const node = buildMdastNode(n, options, isBlock);
615
+ if (node) mdastNodes.push(node);
616
+ }
617
+ }
618
+ }
619
+ return mdastNodes;
620
+ };
621
+ const buildMdastNode = (node, options, isBlock = false) => {
622
+ const editor = options.editor;
623
+ let key = getPluginKey(editor, node.type) ?? node.type;
624
+ if (KEYS.heading.includes(key)) key = "heading";
625
+ if (key === KEYS.olClassic || key === KEYS.ulClassic) key = "list";
626
+ const nodeParser = getSerializerByKey(key, options);
627
+ if (nodeParser) {
628
+ const mdastNode = nodeParser(node, options);
629
+ if (options.withBlockId && node.id && isBlock) return wrapWithBlockId(mdastNode, node.id);
630
+ return mdastNode;
631
+ }
632
+ unreachable(node);
633
+ };
634
+ const shouldIncludeText = (text, options) => {
635
+ const { allowedNodes, allowNode, disallowedNodes } = options;
636
+ if (allowedNodes && disallowedNodes && allowedNodes.length > 0 && disallowedNodes.length > 0) throw new Error("Cannot combine allowedNodes with disallowedNodes");
637
+ for (const [key, value] of Object.entries(text)) {
638
+ if (key === "text") continue;
639
+ if (allowedNodes) {
640
+ if (!allowedNodes.includes(key) && value) return false;
641
+ } else if (disallowedNodes?.includes(key) && value) return false;
642
+ }
643
+ if (allowNode?.serialize) return allowNode.serialize(text);
644
+ return true;
645
+ };
646
+ const shouldIncludeNode$1 = (node, options) => {
647
+ const { allowedNodes, allowNode, disallowedNodes } = options;
648
+ if (!node.type) return true;
649
+ if (allowedNodes && disallowedNodes && allowedNodes.length > 0 && disallowedNodes.length > 0) throw new Error("Cannot combine allowedNodes with disallowedNodes");
650
+ if (allowedNodes) {
651
+ if (!allowedNodes.includes(node.type)) return false;
652
+ } else if (disallowedNodes?.includes(node.type)) return false;
653
+ if (allowNode?.serialize) return allowNode.serialize(node);
654
+ return true;
655
+ };
656
+
657
+ //#endregion
658
+ //#region src/lib/serializer/serializeInlineMd.ts
659
+ const serializeInlineMd = (editor, options) => {
660
+ const mergedOptions = getMergedOptionsSerialize(editor, options);
661
+ const toRemarkProcessor = unified().use(mergedOptions.remarkPlugins ?? []).use(remarkStringify, {
662
+ emphasis: "_",
663
+ ...mergedOptions?.remarkStringifyOptions
664
+ });
665
+ if (options?.value?.length === 0) return "";
666
+ const convertedTexts = convertTextsSerialize(mergedOptions.value, { editor });
667
+ return toRemarkProcessor.stringify({
668
+ children: convertedTexts,
669
+ type: "root"
670
+ });
671
+ };
672
+
673
+ //#endregion
674
+ //#region src/lib/serializer/serializeMd.ts
675
+ /** Serialize the editor value to Markdown. */
676
+ const serializeMd = (editor, options) => {
677
+ const mergedOptions = getMergedOptionsSerialize(editor, options);
678
+ const { remarkPlugins, value } = mergedOptions;
679
+ const toRemarkProcessor = unified().use(remarkPlugins ?? []).use(remarkStringify, {
680
+ emphasis: "_",
681
+ ...mergedOptions?.remarkStringifyOptions
682
+ });
683
+ const mdast = slateToMdast({
684
+ children: value,
685
+ options: mergedOptions
686
+ });
687
+ return toRemarkProcessor.stringify(mdast);
688
+ };
689
+ const slateToMdast = ({ children, options }) => {
690
+ return {
691
+ children: convertNodesSerialize(children, options, true),
692
+ type: "root"
693
+ };
694
+ };
695
+
696
+ //#endregion
697
+ //#region src/lib/rules/defaultRules.ts
698
+ const LEADING_NEWLINE_REGEX = /^\n/;
699
+ function isBoolean(value) {
700
+ return value === true || value === false || !!value && typeof value === "object" && Object.prototype.toString.call(value) === "[object Boolean]";
701
+ }
702
+ const defaultRules = {
703
+ a: {
704
+ deserialize: (mdastNode, deco, options) => ({
705
+ children: convertChildrenDeserialize(mdastNode.children, deco, options),
706
+ type: getPluginType(options.editor, KEYS.a),
707
+ url: mdastNode.url
708
+ }),
709
+ serialize: (node, options) => ({
710
+ children: convertNodesSerialize(node.children, options),
711
+ type: "link",
712
+ url: node.url
713
+ })
714
+ },
715
+ blockquote: {
716
+ deserialize: (mdastNode, deco, options) => {
717
+ return {
718
+ children: (mdastNode.children.length > 0 ? mdastNode.children.flatMap((paragraph, index, children) => {
719
+ if (paragraph.type === "paragraph") {
720
+ if (children.length > 1 && children.length - 1 !== index) {
721
+ const paragraphChildren = convertChildrenDeserialize(paragraph.children, deco, options);
722
+ paragraphChildren.push({ text: "\n" }, { text: "\n" });
723
+ return paragraphChildren;
724
+ }
725
+ return convertChildrenDeserialize(paragraph.children, deco, options);
726
+ }
727
+ if ("children" in paragraph) return convertChildrenDeserialize(paragraph.children, deco, options);
728
+ return [{ text: "" }];
729
+ }) : [{ text: "" }]).flatMap((child) => child.type === "blockquote" ? child.children : [child]),
730
+ type: getPluginType(options.editor, KEYS.blockquote)
731
+ };
732
+ },
733
+ serialize: (node, options) => {
734
+ const nodes = [];
735
+ for (const child of node.children) if (child.text === "\n") nodes.push({ type: "break" });
736
+ else nodes.push(child);
737
+ const paragraphChildren = convertNodesSerialize(nodes, options);
738
+ if (paragraphChildren.length > 0 && paragraphChildren.at(-1).type === "break") {
739
+ paragraphChildren.at(-1).type = "html";
740
+ paragraphChildren.at(-1).value = "\n<br />";
741
+ }
742
+ return {
743
+ children: [{
744
+ children: paragraphChildren,
745
+ type: "paragraph"
746
+ }],
747
+ type: "blockquote"
748
+ };
749
+ }
750
+ },
751
+ bold: {
752
+ mark: true,
753
+ deserialize: (mdastNode, deco, options) => convertTextsDeserialize(mdastNode, deco, options)
754
+ },
755
+ br: { deserialize() {
756
+ return [{ text: "\n" }];
757
+ } },
758
+ break: {
759
+ deserialize: (_mdastNode, _deco) => ({ text: "\n" }),
760
+ serialize: () => ({ type: "break" })
761
+ },
762
+ callout: {
763
+ deserialize: (mdastNode, deco, options) => {
764
+ const props = parseAttributes(mdastNode.attributes);
765
+ return {
766
+ children: convertChildrenDeserialize(mdastNode.children, deco, options),
767
+ type: getPluginType(options.editor, KEYS.callout),
768
+ ...props
769
+ };
770
+ },
771
+ serialize(slateNode, options) {
772
+ const { children, type, ...rest } = slateNode;
773
+ return {
774
+ attributes: propsToAttributes(rest),
775
+ children: convertNodesSerialize(children, options),
776
+ name: "callout",
777
+ type: "mdxJsxFlowElement"
778
+ };
779
+ }
780
+ },
781
+ code: {
782
+ mark: true,
783
+ deserialize: (mdastNode, deco, options) => ({
784
+ ...deco,
785
+ [getPluginType(options.editor, KEYS.code)]: true,
786
+ text: mdastNode.value
787
+ })
788
+ },
789
+ code_block: {
790
+ deserialize: (mdastNode, _deco, options) => ({
791
+ children: (mdastNode.value || "").split("\n").map((line) => ({
792
+ children: [{ text: line }],
793
+ type: getPluginType(options.editor, KEYS.codeLine)
794
+ })),
795
+ lang: mdastNode.lang ?? void 0,
796
+ type: getPluginType(options.editor, KEYS.codeBlock)
797
+ }),
798
+ serialize: (node) => ({
799
+ lang: node.lang,
800
+ type: "code",
801
+ value: node.children.map((child) => child?.children === void 0 ? child.text : child.children.map((c) => c.text).join("")).join("\n")
802
+ })
803
+ },
804
+ comment: {
805
+ mark: true,
806
+ deserialize: (mdastNode, deco, options) => {
807
+ return convertChildrenDeserialize(mdastNode.children, {
808
+ [getPluginType(options.editor, KEYS.comment)]: true,
809
+ ...deco
810
+ }, options);
811
+ },
812
+ serialize(slateNode) {
813
+ return {
814
+ attributes: [],
815
+ children: [{
816
+ type: "text",
817
+ value: slateNode.text
818
+ }],
819
+ name: "comment",
820
+ type: "mdxJsxTextElement"
821
+ };
822
+ }
823
+ },
824
+ date: {
825
+ deserialize(mdastNode, _deco, options) {
826
+ return {
827
+ children: [{ text: "" }],
828
+ date: (mdastNode.children?.[0])?.value || "",
829
+ type: getPluginType(options.editor, KEYS.date)
830
+ };
831
+ },
832
+ serialize({ date }) {
833
+ return {
834
+ attributes: [],
835
+ children: [{
836
+ type: "text",
837
+ value: date ?? ""
838
+ }],
839
+ name: "date",
840
+ type: "mdxJsxTextElement"
841
+ };
842
+ }
843
+ },
844
+ del: {
845
+ mark: true,
846
+ deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
847
+ [getPluginType(options.editor, KEYS.strikethrough)]: true,
848
+ ...deco
849
+ }, options)
850
+ },
851
+ equation: {
852
+ deserialize: (mdastNode, _deco, options) => ({
853
+ children: [{ text: "" }],
854
+ texExpression: mdastNode.value,
855
+ type: getPluginType(options.editor, KEYS.equation)
856
+ }),
857
+ serialize: (node) => ({
858
+ type: "math",
859
+ value: node.texExpression
860
+ })
861
+ },
862
+ footnoteDefinition: { deserialize: (mdastNode, deco, options) => {
863
+ return {
864
+ children: convertChildrenDeserialize(mdastNode.children, deco, options).flatMap((child) => child.type === "p" ? child.children : [child]),
865
+ type: getPluginType(options.editor, KEYS.p)
866
+ };
867
+ } },
868
+ footnoteReference: {},
869
+ heading: {
870
+ deserialize: (mdastNode, deco, options) => {
871
+ const defaultType = {
872
+ 1: "h1",
873
+ 2: "h2",
874
+ 3: "h3",
875
+ 4: "h4",
876
+ 5: "h5",
877
+ 6: "h6"
878
+ }[mdastNode.depth];
879
+ const type = getPluginType(options.editor, defaultType);
880
+ return {
881
+ children: convertChildrenDeserialize(mdastNode.children, deco, options),
882
+ type
883
+ };
884
+ },
885
+ serialize: (node, options) => {
886
+ const key = getPluginKey(options.editor, node.type) ?? node.type;
887
+ return {
888
+ children: convertNodesSerialize(node.children, options),
889
+ depth: {
890
+ h1: 1,
891
+ h2: 2,
892
+ h3: 3,
893
+ h4: 4,
894
+ h5: 5,
895
+ h6: 6
896
+ }[key],
897
+ type: "heading"
898
+ };
899
+ }
900
+ },
901
+ highlight: {
902
+ mark: true,
903
+ deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
904
+ [getPluginType(options.editor, KEYS.highlight)]: true,
905
+ ...deco
906
+ }, options),
907
+ serialize(slateNode) {
908
+ return {
909
+ attributes: [],
910
+ children: [{
911
+ type: "text",
912
+ value: slateNode.text
913
+ }],
914
+ name: "mark",
915
+ type: "mdxJsxTextElement"
916
+ };
917
+ }
918
+ },
919
+ hr: {
920
+ deserialize: (_, __, options) => ({
921
+ children: [{ text: "" }],
922
+ type: getPluginType(options.editor, KEYS.hr)
923
+ }),
924
+ serialize: () => ({ type: "thematicBreak" })
925
+ },
926
+ html: { deserialize: (mdastNode, _deco, _options) => ({ text: (mdastNode.value || "").replaceAll("<br />", "\n") }) },
927
+ img: {
928
+ deserialize: (mdastNode, _deco, options) => {
929
+ const { alt, attributes, title, url } = mdastNode;
930
+ const { alt: altAttr, src, ...rest } = attributes ? parseAttributes(attributes) : {};
931
+ return {
932
+ caption: [{ text: altAttr || alt || "" }],
933
+ children: [{ text: "" }],
934
+ ...title && { title },
935
+ type: getPluginType(options.editor, KEYS.img),
936
+ url: src || url,
937
+ ...rest
938
+ };
939
+ },
940
+ serialize: ({ caption, url }) => {
941
+ return {
942
+ children: [{
943
+ alt: caption ? caption.map((c) => c.text).join("") : void 0,
944
+ title: caption ? caption.map((c) => c.text).join("") : void 0,
945
+ type: "image",
946
+ url
947
+ }],
948
+ type: "paragraph"
949
+ };
950
+ }
951
+ },
952
+ inline_equation: {
953
+ deserialize(mdastNode, _, options) {
954
+ return {
955
+ children: [{ text: "" }],
956
+ texExpression: mdastNode.value,
957
+ type: getPluginType(options.editor, KEYS.inlineEquation)
958
+ };
959
+ },
960
+ serialize: (node) => ({
961
+ type: "inlineMath",
962
+ value: node.texExpression
963
+ })
964
+ },
965
+ italic: {
966
+ mark: true,
967
+ deserialize: (mdastNode, deco, options) => convertTextsDeserialize(mdastNode, deco, options)
968
+ },
969
+ kbd: {
970
+ mark: true,
971
+ deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
972
+ [getPluginType(options.editor, KEYS.kbd)]: true,
973
+ ...deco
974
+ }, options),
975
+ serialize(slateNode) {
976
+ return {
977
+ attributes: [],
978
+ children: [{
979
+ type: "text",
980
+ value: slateNode.text
981
+ }],
982
+ name: "kbd",
983
+ type: "mdxJsxTextElement"
984
+ };
985
+ }
986
+ },
987
+ list: {
988
+ deserialize: (mdastNode, deco, options) => {
989
+ if (!!!options.editor?.plugins.list) return {
990
+ children: mdastNode.children.map((child) => {
991
+ if (child.type === "listItem") return {
992
+ children: child.children.map((itemChild) => {
993
+ if (itemChild.type === "paragraph") return {
994
+ children: convertChildrenDeserialize(itemChild.children, deco, options),
995
+ type: getPluginType(options.editor, KEYS.lic)
996
+ };
997
+ return convertChildrenDeserialize([itemChild], deco, options)[0];
998
+ }),
999
+ type: getPluginType(options.editor, KEYS.li)
1000
+ };
1001
+ return convertChildrenDeserialize([child], deco, options)[0];
1002
+ }),
1003
+ type: getPluginType(options.editor, mdastNode.ordered ? KEYS.olClassic : KEYS.ulClassic)
1004
+ };
1005
+ const parseListItems = (listNode, indent = 1, startIndex = 1) => {
1006
+ const items = [];
1007
+ const isOrdered = !!listNode.ordered;
1008
+ let listStyleType = isOrdered ? getPluginType(options.editor, KEYS.ol) : getPluginType(options.editor, KEYS.ul);
1009
+ listNode.children?.forEach((listItem, index) => {
1010
+ if (listItem.type !== "listItem") return;
1011
+ const isTodoList = isBoolean(listItem.checked);
1012
+ if (isTodoList) listStyleType = getPluginType(options.editor, KEYS.listTodo);
1013
+ const [paragraph, ...subLists] = listItem.children || [];
1014
+ const result = paragraph ? buildSlateNode(paragraph, deco, options) : {
1015
+ children: [{ text: "" }],
1016
+ type: getPluginType(options.editor, KEYS.p)
1017
+ };
1018
+ (Array.isArray(result) ? result : [result]).forEach((node) => {
1019
+ const itemContent = {
1020
+ ...node,
1021
+ indent,
1022
+ type: node.type === getPluginType(options.editor, KEYS.img) ? node.type : getPluginType(options.editor, KEYS.p)
1023
+ };
1024
+ itemContent.listStyleType = listStyleType;
1025
+ if (isTodoList) itemContent.checked = listItem.checked;
1026
+ if (isOrdered) itemContent.listStart = startIndex + index;
1027
+ items.push(itemContent);
1028
+ });
1029
+ subLists.forEach((subNode) => {
1030
+ if (subNode.type === "list") {
1031
+ const subListStart = subNode.start || 1;
1032
+ const nestedItems = parseListItems(subNode, indent + 1, subListStart);
1033
+ items.push(...nestedItems);
1034
+ } else {
1035
+ const result$1 = buildSlateNode(subNode, deco, options);
1036
+ if (Array.isArray(result$1)) items.push(...result$1.map((item) => ({
1037
+ ...item,
1038
+ indent: indent + 1
1039
+ })));
1040
+ else items.push({
1041
+ ...result$1,
1042
+ indent: indent + 1
1043
+ });
1044
+ }
1045
+ });
1046
+ });
1047
+ return items;
1048
+ };
1049
+ return parseListItems(mdastNode, 1, mdastNode.start || 1);
1050
+ },
1051
+ serialize: (node, options) => {
1052
+ const editor = options.editor;
1053
+ const isOrdered = getPluginKey(editor, node.type) === KEYS.olClassic;
1054
+ const serializeListItems = (children) => {
1055
+ const items = [];
1056
+ let currentItem = null;
1057
+ for (const child of children) if (getPluginKey(editor, child.type) === "li") {
1058
+ if (currentItem) items.push(currentItem);
1059
+ currentItem = {
1060
+ children: [],
1061
+ spread: false,
1062
+ type: "listItem"
1063
+ };
1064
+ for (const liChild of child.children) if (getPluginKey(editor, liChild.type) === "lic") currentItem.children.push({
1065
+ children: convertNodesSerialize(liChild.children, options),
1066
+ type: "paragraph"
1067
+ });
1068
+ else if (getPluginKey(editor, liChild.type) === "ol" || getPluginKey(editor, liChild.type) === "ul") currentItem.children.push({
1069
+ children: serializeListItems(liChild.children),
1070
+ ordered: getPluginKey(editor, liChild.type) === "ol",
1071
+ spread: false,
1072
+ type: "list"
1073
+ });
1074
+ }
1075
+ if (currentItem) items.push(currentItem);
1076
+ return items;
1077
+ };
1078
+ return {
1079
+ children: serializeListItems(node.children),
1080
+ ordered: isOrdered,
1081
+ spread: false,
1082
+ type: "list"
1083
+ };
1084
+ }
1085
+ },
1086
+ listItem: {
1087
+ deserialize: (mdastNode, deco, options) => {
1088
+ return {
1089
+ children: mdastNode.children.map((child) => {
1090
+ if (child.type === "paragraph") return {
1091
+ children: convertChildrenDeserialize(child.children, deco, options),
1092
+ type: getPluginType(options.editor, KEYS.lic)
1093
+ };
1094
+ return convertChildrenDeserialize([child], deco, options)[0];
1095
+ }),
1096
+ type: getPluginType(options.editor, KEYS.li)
1097
+ };
1098
+ },
1099
+ serialize: (node, options) => ({
1100
+ children: convertNodesSerialize(node.children, options),
1101
+ type: "listItem"
1102
+ })
1103
+ },
1104
+ mention: {
1105
+ deserialize: (node, _deco, options) => ({
1106
+ children: [{ text: "" }],
1107
+ type: getPluginType(options.editor, KEYS.mention),
1108
+ value: node.displayText || node.username,
1109
+ ...node.displayText && { key: node.username }
1110
+ }),
1111
+ serialize: (node) => {
1112
+ const mentionId = node.key || node.value;
1113
+ const displayText = node.value;
1114
+ const encodedId = encodeURIComponent(String(mentionId)).replace(/\(/g, "%28").replace(/\)/g, "%29");
1115
+ return {
1116
+ children: [{
1117
+ type: "text",
1118
+ value: displayText
1119
+ }],
1120
+ type: "link",
1121
+ url: `mention:${encodedId}`
1122
+ };
1123
+ }
1124
+ },
1125
+ p: {
1126
+ deserialize: (node, deco, options) => {
1127
+ const isKeepLineBreak = options.splitLineBreaks;
1128
+ const children = convertChildrenDeserialize(node.children, deco, options);
1129
+ const splitBlockTypes = new Set(["img"]);
1130
+ const elements = [];
1131
+ let inlineNodes = [];
1132
+ const flushInlineNodes = () => {
1133
+ if (inlineNodes.length > 0) {
1134
+ elements.push({
1135
+ children: inlineNodes,
1136
+ type: getPluginType(options.editor, KEYS.p)
1137
+ });
1138
+ inlineNodes = [];
1139
+ }
1140
+ };
1141
+ children.forEach((c) => {
1142
+ if (c.text === "​") c.text = "";
1143
+ });
1144
+ children.forEach((child, index, children$1) => {
1145
+ const { type } = child;
1146
+ if (type && splitBlockTypes.has(type)) {
1147
+ flushInlineNodes();
1148
+ elements.push(child);
1149
+ } else if (isKeepLineBreak && "text" in child && typeof child.text === "string") {
1150
+ const textParts = child.text.split("\n");
1151
+ if (child.text === "\n" && inlineNodes.length === 0) {
1152
+ inlineNodes.push({
1153
+ ...child,
1154
+ text: ""
1155
+ });
1156
+ flushInlineNodes();
1157
+ return;
1158
+ }
1159
+ textParts.forEach((part, index$1, array) => {
1160
+ const isNotFirstPart = index$1 > 0;
1161
+ const isNotLastPart = index$1 < array.length - 1;
1162
+ if (isNotFirstPart) flushInlineNodes();
1163
+ if (part) inlineNodes.push({
1164
+ ...child,
1165
+ text: part
1166
+ });
1167
+ if (isNotLastPart) flushInlineNodes();
1168
+ });
1169
+ } else if (child.text === "\n" && children$1.length > 1 && index === children$1.length - 1) {} else inlineNodes.push(child);
1170
+ });
1171
+ flushInlineNodes();
1172
+ return elements.length === 1 ? elements[0] : elements;
1173
+ },
1174
+ serialize: (node, options) => {
1175
+ let enrichedChildren = node.children;
1176
+ enrichedChildren = enrichedChildren.map((child) => {
1177
+ if (child.text === "\n") return { type: "break" };
1178
+ if (child.text === "" && options.preserveEmptyParagraphs !== false) return {
1179
+ ...child,
1180
+ text: "​"
1181
+ };
1182
+ return child;
1183
+ });
1184
+ const convertedNodes = convertNodesSerialize(enrichedChildren, options);
1185
+ if (convertedNodes.length > 0 && enrichedChildren.at(-1).type === "break") {
1186
+ convertedNodes.at(-1).type = "html";
1187
+ convertedNodes.at(-1).value = "\n<br />";
1188
+ }
1189
+ return {
1190
+ children: convertedNodes,
1191
+ type: "paragraph"
1192
+ };
1193
+ }
1194
+ },
1195
+ strikethrough: {
1196
+ mark: true,
1197
+ deserialize: (mdastNode, deco, options) => convertTextsDeserialize(mdastNode, deco, options)
1198
+ },
1199
+ subscript: {
1200
+ mark: true,
1201
+ deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
1202
+ [getPluginType(options.editor, KEYS.sub)]: true,
1203
+ ...deco
1204
+ }, options),
1205
+ serialize(slateNode, _options) {
1206
+ return {
1207
+ attributes: [],
1208
+ children: [{
1209
+ type: "text",
1210
+ value: slateNode.text
1211
+ }],
1212
+ name: "sub",
1213
+ type: "mdxJsxTextElement"
1214
+ };
1215
+ }
1216
+ },
1217
+ suggestion: {
1218
+ mark: true,
1219
+ deserialize: (mdastNode, deco, options) => {
1220
+ return convertChildrenDeserialize(mdastNode.children, {
1221
+ [getPluginType(options.editor, KEYS.suggestion)]: true,
1222
+ ...deco
1223
+ }, options);
1224
+ },
1225
+ serialize(slateNode) {
1226
+ return {
1227
+ attributes: [],
1228
+ children: [{
1229
+ type: "text",
1230
+ value: slateNode.text
1231
+ }],
1232
+ name: "suggestion",
1233
+ type: "mdxJsxTextElement"
1234
+ };
1235
+ }
1236
+ },
1237
+ superscript: {
1238
+ mark: true,
1239
+ deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
1240
+ [getPluginType(options.editor, KEYS.sup)]: true,
1241
+ ...deco
1242
+ }, options),
1243
+ serialize(slateNode, _options) {
1244
+ return {
1245
+ attributes: [],
1246
+ children: [{
1247
+ type: "text",
1248
+ value: slateNode.text
1249
+ }],
1250
+ name: "sup",
1251
+ type: "mdxJsxTextElement"
1252
+ };
1253
+ }
1254
+ },
1255
+ table: {
1256
+ deserialize: (node, deco, options) => {
1257
+ const paragraphType = getPluginType(options.editor, KEYS.p);
1258
+ return {
1259
+ children: node.children?.map((row, rowIndex) => ({
1260
+ children: row.children?.map((cell) => {
1261
+ const cellType = rowIndex === 0 ? "th" : "td";
1262
+ const cellChildren = convertChildrenDeserialize(cell.children, deco, options);
1263
+ const groupedChildren = [];
1264
+ let currentParagraphChildren = [];
1265
+ for (const child of cellChildren) if (!child.type || child.type === KEYS.inlineEquation) currentParagraphChildren.push(child);
1266
+ else {
1267
+ if (currentParagraphChildren.length > 0) {
1268
+ groupedChildren.push({
1269
+ children: currentParagraphChildren,
1270
+ type: paragraphType
1271
+ });
1272
+ currentParagraphChildren = [];
1273
+ }
1274
+ groupedChildren.push(child);
1275
+ }
1276
+ if (currentParagraphChildren.length > 0) groupedChildren.push({
1277
+ children: currentParagraphChildren,
1278
+ type: paragraphType
1279
+ });
1280
+ return {
1281
+ children: groupedChildren.length > 0 ? groupedChildren : [{
1282
+ children: [{ text: "" }],
1283
+ type: paragraphType
1284
+ }],
1285
+ type: getPluginType(options.editor, cellType)
1286
+ };
1287
+ }) || [],
1288
+ type: getPluginType(options.editor, KEYS.tr)
1289
+ })) || [],
1290
+ type: getPluginType(options.editor, KEYS.table)
1291
+ };
1292
+ },
1293
+ serialize: (node, options) => ({
1294
+ children: convertNodesSerialize(node.children, options),
1295
+ type: "table"
1296
+ })
1297
+ },
1298
+ td: { serialize: (node, options) => {
1299
+ const children = convertNodesSerialize(node.children, options);
1300
+ if (children.length > 1) {
1301
+ const result = [];
1302
+ for (let i = 0; i < children.length; i++) {
1303
+ result.push(children[i]);
1304
+ if (i < children.length - 1) result.push({
1305
+ type: "html",
1306
+ value: "<br/>"
1307
+ });
1308
+ }
1309
+ return {
1310
+ children: result,
1311
+ type: "tableCell"
1312
+ };
1313
+ }
1314
+ return {
1315
+ children,
1316
+ type: "tableCell"
1317
+ };
1318
+ } },
1319
+ text: { deserialize: (mdastNode, deco) => ({
1320
+ ...deco,
1321
+ text: mdastNode.value.replace(LEADING_NEWLINE_REGEX, "")
1322
+ }) },
1323
+ th: { serialize: (node, options) => {
1324
+ const children = convertNodesSerialize(node.children, options);
1325
+ if (children.length > 1) {
1326
+ const result = [];
1327
+ for (let i = 0; i < children.length; i++) {
1328
+ result.push(children[i]);
1329
+ if (i < children.length - 1) result.push({
1330
+ type: "html",
1331
+ value: "<br/>"
1332
+ });
1333
+ }
1334
+ return {
1335
+ children: result,
1336
+ type: "tableCell"
1337
+ };
1338
+ }
1339
+ return {
1340
+ children,
1341
+ type: "tableCell"
1342
+ };
1343
+ } },
1344
+ toc: {
1345
+ deserialize: (mdastNode, deco, options) => ({
1346
+ children: convertChildrenDeserialize(mdastNode.children, deco, options),
1347
+ type: getPluginType(options.editor, KEYS.toc)
1348
+ }),
1349
+ serialize: (node, options) => ({
1350
+ attributes: [],
1351
+ children: convertNodesSerialize(node.children, options),
1352
+ name: "toc",
1353
+ type: "mdxJsxFlowElement"
1354
+ })
1355
+ },
1356
+ tr: { serialize: (node, options) => ({
1357
+ children: convertNodesSerialize(node.children, options),
1358
+ type: "tableRow"
1359
+ }) },
1360
+ underline: {
1361
+ mark: true,
1362
+ deserialize: (mdastNode, deco, options) => convertChildrenDeserialize(mdastNode.children, {
1363
+ [getPluginType(options.editor, KEYS.underline)]: true,
1364
+ ...deco
1365
+ }, options),
1366
+ serialize(slateNode, _options) {
1367
+ return {
1368
+ attributes: [],
1369
+ children: [{
1370
+ type: "text",
1371
+ value: slateNode.text
1372
+ }],
1373
+ name: "u",
1374
+ type: "mdxJsxTextElement"
1375
+ };
1376
+ }
1377
+ },
1378
+ ...fontRules,
1379
+ ...mediaRules,
1380
+ ...columnRules
1381
+ };
1382
+ const buildRules = (editor) => {
1383
+ const keys = Object.keys(defaultRules);
1384
+ const newRules = {};
1385
+ keys.forEach((key) => {
1386
+ const pluginKey = getPluginKey(editor, key);
1387
+ newRules[pluginKey ?? key] = defaultRules[key];
1388
+ });
1389
+ return newRules;
1390
+ };
1391
+
1392
+ //#endregion
1393
+ //#region src/lib/deserializer/utils/getDeserializerByKey.ts
1394
+ const getDeserializerByKey = (key, options) => {
1395
+ const rules = options.rules;
1396
+ return rules?.[key]?.deserialize === void 0 ? buildRules(options.editor)[key]?.deserialize : rules?.[key]?.deserialize;
1397
+ };
1398
+
1399
+ //#endregion
1400
+ //#region src/lib/deserializer/utils/customMdxDeserialize.ts
1401
+ const customMdxDeserialize = (mdastNode, deco, options) => {
1402
+ const customJsxElementKey = mdastNode.name;
1403
+ const key = getPluginKey(options.editor, customJsxElementKey) ?? mdastNode.name;
1404
+ if (key) {
1405
+ const nodeParserDeserialize = getDeserializerByKey(mdastToPlate(options.editor, key), options);
1406
+ if (nodeParserDeserialize) return nodeParserDeserialize(mdastNode, deco, options);
1407
+ } else console.warn("This MDX node does not have a parser for deserialization", mdastNode);
1408
+ if (mdastNode.type === "mdxJsxTextElement") {
1409
+ const tagName = mdastNode.name;
1410
+ let textContent = "";
1411
+ if (mdastNode.children) textContent = mdastNode.children.map((child) => {
1412
+ if ("value" in child) return child.value;
1413
+ return "";
1414
+ }).join("");
1415
+ return [{ text: `<${tagName}>${textContent}</${tagName}>` }];
1416
+ }
1417
+ if (mdastNode.type === "mdxJsxFlowElement") {
1418
+ const tagName = mdastNode.name;
1419
+ return [{
1420
+ children: [
1421
+ { text: `<${tagName}>\n` },
1422
+ ...convertChildrenDeserialize(mdastNode.children, deco, options),
1423
+ { text: `\n</${tagName}>` }
1424
+ ],
1425
+ type: getPluginType(options.editor, KEYS.p)
1426
+ }];
1427
+ }
1428
+ };
1429
+
1430
+ //#endregion
1431
+ //#region src/lib/deserializer/utils/stripMarkdown.ts
1432
+ const stripMarkdownBlocks = (text) => {
1433
+ let result = text;
1434
+ result = result.replaceAll(/^#{1,6}\s+/gm, "");
1435
+ result = result.replaceAll(/^\s*>\s?/gm, "");
1436
+ result = result.replaceAll(/^([*_-]){3,}\s*$/gm, "");
1437
+ result = result.replaceAll(/^(\s*)([*+-]|\d+\.)\s/gm, "$1");
1438
+ result = result.replaceAll(/^```[\S\s]*?^```/gm, "");
1439
+ result = result.replaceAll("<br>", "\n");
1440
+ return result;
1441
+ };
1442
+ const stripMarkdownInline = (text) => {
1443
+ let result = text;
1444
+ result = result.replaceAll(/(\*\*|__)(.*?)\1/g, "$2");
1445
+ result = result.replaceAll(/(\*|_)(.*?)\1/g, "$2");
1446
+ result = result.replaceAll(/\[([^\]]+)]\(([^)]+)\)/g, "$1");
1447
+ result = result.replaceAll(/`(.+?)`/g, "$1");
1448
+ result = result.replaceAll("&nbsp;", " ");
1449
+ result = result.replaceAll("&lt;", "<");
1450
+ result = result.replaceAll("&gt;", ">");
1451
+ result = result.replaceAll("&amp;", "&");
1452
+ return result;
1453
+ };
1454
+ const stripMarkdown = (text) => {
1455
+ let result = stripMarkdownBlocks(text);
1456
+ result = stripMarkdownInline(result);
1457
+ return result;
1458
+ };
1459
+
1460
+ //#endregion
1461
+ //#region src/lib/deserializer/utils/deserializeInlineMd.ts
1462
+ const LEADING_SPACES_REGEX = /^\s*/;
1463
+ const TRAILING_SPACES_REGEX = /\s*$/;
1464
+ const deserializeInlineMd = (editor, text, options) => {
1465
+ const leadingSpaces = LEADING_SPACES_REGEX.exec(text)?.[0] || "";
1466
+ const trailingSpaces = TRAILING_SPACES_REGEX.exec(text)?.[0] || "";
1467
+ const strippedText = stripMarkdownBlocks(text.trim());
1468
+ const fragment = [];
1469
+ if (leadingSpaces) fragment.push({ text: leadingSpaces });
1470
+ if (strippedText) {
1471
+ const result = editor.getApi(MarkdownPlugin).markdown.deserialize(strippedText, options)[0];
1472
+ if (result) {
1473
+ const nodes = ElementApi.isElement(result) ? result.children : [result];
1474
+ fragment.push(...nodes);
1475
+ }
1476
+ }
1477
+ if (trailingSpaces) fragment.push({ text: trailingSpaces });
1478
+ return fragment;
1479
+ };
1480
+
1481
+ //#endregion
1482
+ //#region src/lib/utils/getRemarkPluginsWithoutMdx.ts
1483
+ const REMARK_MDX_TAG = "remarkMdx";
1484
+ const tagRemarkPlugin = (pluginFn, tag) => {
1485
+ const wrapped = function(...args) {
1486
+ return pluginFn.apply(this, args);
1487
+ };
1488
+ wrapped.__pluginTag = tag;
1489
+ return wrapped;
1490
+ };
1491
+ const getRemarkPluginsWithoutMdx = (plugins) => plugins.filter((plugin) => plugin.__pluginTag !== REMARK_MDX_TAG);
1492
+
1493
+ //#endregion
1494
+ //#region src/lib/deserializer/utils/getMergedOptionsDeserialize.ts
1495
+ /**
1496
+ * Merges Markdown configurations, following the principle that options take
1497
+ * precedence
1498
+ *
1499
+ * @param editor Editor instance used to get plugin default configurations
1500
+ * @param options User-provided options (higher priority)
1501
+ * @returns The final merged configuration
1502
+ */
1503
+ const getMergedOptionsDeserialize = (editor, options) => {
1504
+ const { allowedNodes: PluginAllowedNodes, allowNode: PluginAllowNode, disallowedNodes: PluginDisallowedNodes, remarkPlugins: PluginRemarkPlugins, rules: PluginRules } = editor.getOptions(MarkdownPlugin);
1505
+ const mergedRules = {
1506
+ ...buildRules(editor),
1507
+ ...options?.rules ?? PluginRules
1508
+ };
1509
+ const remarkPlugins = options?.remarkPlugins ?? PluginRemarkPlugins ?? [];
1510
+ return {
1511
+ allowedNodes: options?.allowedNodes ?? PluginAllowedNodes,
1512
+ allowNode: options?.allowNode ?? PluginAllowNode,
1513
+ disallowedNodes: options?.disallowedNodes ?? PluginDisallowedNodes,
1514
+ editor,
1515
+ memoize: options?.memoize,
1516
+ parser: options?.parser,
1517
+ remarkPlugins: options?.withoutMdx ? getRemarkPluginsWithoutMdx(remarkPlugins) : remarkPlugins,
1518
+ rules: mergedRules,
1519
+ splitLineBreaks: options?.splitLineBreaks
1520
+ };
1521
+ };
1522
+
1523
+ //#endregion
1524
+ //#region src/lib/deserializer/utils/getStyleValue.ts
1525
+ const getStyleValue = (mdastNode, styleName) => {
1526
+ const styleAttribute = mdastNode.attributes.find((attr) => "name" in attr && attr.name === "style");
1527
+ if (!styleAttribute?.value) return;
1528
+ const styles = styleAttribute.value.split(";");
1529
+ for (const style of styles) {
1530
+ const [name, value] = style.split(":").map((s) => s.trim());
1531
+ if (name === styleName) return value;
1532
+ }
1533
+ };
1534
+
1535
+ //#endregion
1536
+ //#region src/lib/deserializer/utils/htmlToJsx.ts
1537
+ const VOID_ELEMENTS = new Set([
1538
+ "area",
1539
+ "base",
1540
+ "br",
1541
+ "col",
1542
+ "embed",
1543
+ "hr",
1544
+ "img",
1545
+ "input",
1546
+ "link",
1547
+ "meta",
1548
+ "param",
1549
+ "source",
1550
+ "track",
1551
+ "wbr"
1552
+ ]);
1553
+ const BOOL_ATTRS = new Map([
1554
+ ["checked", "checked"],
1555
+ ["disabled", "disabled"],
1556
+ ["readonly", "readOnly"],
1557
+ ["required", "required"],
1558
+ ["multiple", "multiple"],
1559
+ ["hidden", "hidden"]
1560
+ ]);
1561
+ const BOOL_ATTR_REGEXES = Array.from(BOOL_ATTRS.entries()).map(([htmlAttr, jsxAttr]) => ({
1562
+ jsxAttr,
1563
+ reg: new RegExp(`(\\s|^)${htmlAttr}(\\s|/?>|$)`, "gi")
1564
+ }));
1565
+ const ATTR_RENAMES = [[/(\s)class=/g, "$1className="], [/(\s)for=/g, "$1htmlFor="]];
1566
+ const htmlToJsx = (html) => {
1567
+ if (!html || typeof html !== "string") return html;
1568
+ return html.replace(/<!--([\s\S]*?)-->/g, "{/*$1*/}").replace(/<([a-zA-Z0-9]+)\b([^>]*?)(\/?)>/gi, (_match, tagName, attrs, selfClosing) => {
1569
+ let a = attrs;
1570
+ ATTR_RENAMES.forEach(([pattern, replacement]) => {
1571
+ a = a.replace(pattern, replacement);
1572
+ });
1573
+ a = a.replace(/(^|\s)([a-zA-Z0-9_-]+)=([^{ \t\n\r"'>]+?)(?=\s|\/?>|$)/g, "$1$2=\"$3\"");
1574
+ for (const { reg, jsxAttr } of BOOL_ATTR_REGEXES) a = a.replace(reg, `$1${jsxAttr}="true"$2`);
1575
+ const closing = VOID_ELEMENTS.has(tagName.toLowerCase()) ? " /" : selfClosing;
1576
+ return `<${tagName}${a.trimEnd()}${closing}>`;
1577
+ });
1578
+ };
1579
+
1580
+ //#endregion
1581
+ //#region src/lib/deserializer/mdastToSlate.ts
1582
+ const mdastToSlate = (node, options) => buildSlateRoot(node, options);
1583
+ const buildSlateRoot = (root, options) => {
1584
+ if (!options.splitLineBreaks) {
1585
+ root.children = root.children.map((child) => {
1586
+ if (child.type === "html" && child.value === "<br />") return {
1587
+ children: [{
1588
+ type: "text",
1589
+ value: "\n"
1590
+ }],
1591
+ type: "paragraph"
1592
+ };
1593
+ return child;
1594
+ });
1595
+ return convertNodesDeserialize(root.children, {}, options);
1596
+ }
1597
+ const results = [];
1598
+ let startLine = root.position?.start.line ?? 1;
1599
+ const addEmptyParagraphs = (count) => {
1600
+ if (count > 0) results.push(...Array.from({ length: count }).map(() => ({
1601
+ children: [{ text: "" }],
1602
+ type: options.editor ? getPluginKey(options.editor, KEYS.p) ?? KEYS.p : KEYS.p
1603
+ })));
1604
+ };
1605
+ root.children?.forEach((child, index) => {
1606
+ const isFirstChild = index === 0;
1607
+ const isLastChild = index === root.children.length - 1;
1608
+ if (child.position) {
1609
+ addEmptyParagraphs(child.position.start.line - (isFirstChild ? startLine : startLine + 1));
1610
+ const transformValue = convertNodesDeserialize([child], {}, options);
1611
+ results.push(...transformValue);
1612
+ if (isLastChild) addEmptyParagraphs(root.position.end.line - child.position.end.line - 1);
1613
+ startLine = child.position.end.line;
1614
+ } else {
1615
+ const transformValue = convertNodesDeserialize([child], {}, options);
1616
+ results.push(...transformValue);
1617
+ }
1618
+ });
1619
+ return results;
1620
+ };
1621
+
1622
+ //#endregion
1623
+ //#region src/lib/deserializer/deserializeMd.ts
1624
+ const markdownToAstProcessor = (editor, data, options) => {
1625
+ const mergedOptions = getMergedOptionsDeserialize(editor, options);
1626
+ return unified().use(remarkParse).use(mergedOptions.remarkPlugins ?? []).parse(data);
1627
+ };
1628
+ const markdownToSlateNodes = (editor, data, options) => {
1629
+ const processedData = options?.withoutMdx ? data : htmlToJsx(data);
1630
+ const mergedOptions = getMergedOptionsDeserialize(editor, options);
1631
+ const toSlateProcessor = unified().use(remarkParse).use(mergedOptions.remarkPlugins ?? []).use(remarkToSlate, mergedOptions);
1632
+ if (options?.memoize) return parseMarkdownBlocks(processedData, options.parser).flatMap((token) => {
1633
+ if (token.type === "space") return {
1634
+ ...editor.api.create.block(),
1635
+ _memo: token.raw
1636
+ };
1637
+ return toSlateProcessor.processSync(token.raw).result.map((result) => ({
1638
+ _memo: token.raw,
1639
+ ...result
1640
+ }));
1641
+ });
1642
+ return toSlateProcessor.processSync(processedData).result;
1643
+ };
1644
+ const deserializeMd = (editor, data, options) => {
1645
+ let output = null;
1646
+ try {
1647
+ output = markdownToSlateNodes(editor, data, options);
1648
+ } catch (error) {
1649
+ options?.onError?.(error);
1650
+ if (!options?.withoutMdx) output = markdownToSlateNodesSafely(editor, data, options);
1651
+ }
1652
+ if (!output) return [];
1653
+ return output.map((item) => TextApi.isText(item) ? {
1654
+ children: [item],
1655
+ type: getPluginKey(editor, KEYS.p) ?? KEYS.p
1656
+ } : item);
1657
+ };
1658
+ const remarkToSlate = function(options = {}) {
1659
+ this.compiler = (node) => mdastToSlate(node, options);
1660
+ };
1661
+
1662
+ //#endregion
1663
+ //#region src/lib/deserializer/utils/splitIncompleteMdx.ts
1664
+ /** Check if character is valid for tag name: A-Z / a-z / 0-9 / - _ : */
1665
+ const isNameChar = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 45 || c === 95 || c === 58;
1666
+ const splitIncompleteMdx = (data) => {
1667
+ const stack = [];
1668
+ const len = data.length;
1669
+ let i = 0;
1670
+ let cutPos = -1;
1671
+ while (i < len) {
1672
+ if (data.codePointAt(i) !== 60) {
1673
+ i++;
1674
+ continue;
1675
+ }
1676
+ const tagStart = i;
1677
+ i++;
1678
+ if (i >= len) {
1679
+ cutPos = tagStart;
1680
+ break;
1681
+ }
1682
+ let closing = false;
1683
+ if (data[i] === "/") {
1684
+ closing = true;
1685
+ i++;
1686
+ }
1687
+ const nameStart = i;
1688
+ while (i < len && isNameChar(data.codePointAt(i))) i++;
1689
+ if (nameStart === i) {
1690
+ cutPos = tagStart;
1691
+ break;
1692
+ }
1693
+ const tagName = data.slice(nameStart, i).toLowerCase();
1694
+ let inQuote = null;
1695
+ let selfClosing = false;
1696
+ while (i < len) {
1697
+ const ch = data[i];
1698
+ if (inQuote) {
1699
+ if (ch === inQuote) inQuote = null;
1700
+ } else if (ch === "\"" || ch === "'") inQuote = ch;
1701
+ else if (ch === ">") {
1702
+ selfClosing = data[i - 1] === "/";
1703
+ i++;
1704
+ break;
1705
+ }
1706
+ i++;
1707
+ }
1708
+ if (i >= len) {
1709
+ cutPos = tagStart;
1710
+ break;
1711
+ }
1712
+ if (selfClosing) continue;
1713
+ if (closing) {
1714
+ for (let j = stack.length - 1; j >= 0; j--) if (stack[j].name === tagName) {
1715
+ stack.splice(j, 1);
1716
+ break;
1717
+ }
1718
+ } else stack.push({
1719
+ name: tagName,
1720
+ pos: tagStart
1721
+ });
1722
+ }
1723
+ if (stack.length > 0) {
1724
+ const firstUnmatched = stack[0].pos;
1725
+ cutPos = cutPos === -1 ? firstUnmatched : Math.min(cutPos, firstUnmatched);
1726
+ }
1727
+ return cutPos === -1 ? data : [data.slice(0, cutPos), data.slice(cutPos)];
1728
+ };
1729
+
1730
+ //#endregion
1731
+ //#region src/lib/deserializer/utils/markdownToSlateNodesSafely.ts
1732
+ const markdownToSlateNodesSafely = (editor, data, options) => {
1733
+ const result = splitIncompleteMdx(data);
1734
+ if (!Array.isArray(result)) return markdownToSlateNodes(editor, data, {
1735
+ ...options,
1736
+ withoutMdx: true
1737
+ });
1738
+ const [completeString, incompleteString] = result;
1739
+ const incompleteNodes = deserializeInlineMd(editor, incompleteString, {
1740
+ ...options,
1741
+ withoutMdx: true
1742
+ });
1743
+ const completeNodes = markdownToSlateNodes(editor, completeString, options);
1744
+ if (incompleteNodes.length === 0) return completeNodes;
1745
+ const newBlock = {
1746
+ children: incompleteNodes,
1747
+ type: getPluginType(editor, KEYS.p)
1748
+ };
1749
+ if (completeNodes.length === 0) return [newBlock];
1750
+ const lastBlock = completeNodes.at(-1);
1751
+ if (ElementApi.isElement(lastBlock) && editor.api.isVoid(lastBlock)) return [...completeNodes, newBlock];
1752
+ if (ElementApi.isElement(lastBlock) && lastBlock?.children) {
1753
+ lastBlock.children.push(...incompleteNodes);
1754
+ return completeNodes;
1755
+ }
1756
+ return completeNodes;
1757
+ };
1758
+
1759
+ //#endregion
1760
+ //#region src/lib/deserializer/utils/parseMarkdownBlocks.ts
1761
+ const parseMarkdownBlocks = (content, { exclude = ["space"], trim = true } = {}) => {
1762
+ let tokens = [...marked.lexer(content)];
1763
+ if (exclude.length > 0) tokens = tokens.filter((token) => !exclude.includes(token.type));
1764
+ if (trim) tokens = tokens.map((token) => ({
1765
+ ...token,
1766
+ raw: token.raw.trimEnd()
1767
+ }));
1768
+ return tokens;
1769
+ };
1770
+
1771
+ //#endregion
1772
+ //#region src/lib/deserializer/convertNodesDeserialize.ts
1773
+ const convertNodesDeserialize = (nodes, deco, options) => {
1774
+ return nodes.reduce((acc, node) => {
1775
+ if (shouldIncludeNode(node, options)) acc.push(...buildSlateNode(node, deco, options));
1776
+ return acc;
1777
+ }, []);
1778
+ };
1779
+ const buildSlateNode = (mdastNode, deco, options) => {
1780
+ /** Handle custom mdx nodes */
1781
+ if (mdastNode.type === "mdxJsxTextElement" || mdastNode.type === "mdxJsxFlowElement") {
1782
+ const result = customMdxDeserialize(mdastNode, deco, options);
1783
+ return Array.isArray(result) ? result : [result];
1784
+ }
1785
+ const nodeParser = getDeserializerByKey(mdastToPlate(options.editor, mdastNode.type), options);
1786
+ if (nodeParser) {
1787
+ const result = nodeParser(mdastNode, deco, options);
1788
+ return Array.isArray(result) ? result : [result];
1789
+ }
1790
+ return [];
1791
+ };
1792
+ const shouldIncludeNode = (node, options) => {
1793
+ const { allowedNodes, allowNode, disallowedNodes } = options;
1794
+ if (!node.type) return true;
1795
+ const type = mdastToPlate(options.editor, node.type);
1796
+ if (allowedNodes && disallowedNodes && allowedNodes.length > 0 && disallowedNodes.length > 0) throw new Error("Cannot combine allowedNodes with disallowedNodes");
1797
+ if (allowedNodes) {
1798
+ if (!allowedNodes.includes(type)) return false;
1799
+ } else if (disallowedNodes?.includes(type)) return false;
1800
+ if (allowNode?.deserialize) return allowNode.deserialize({
1801
+ ...node,
1802
+ type
1803
+ });
1804
+ return true;
1805
+ };
1806
+
1807
+ //#endregion
1808
+ //#region src/lib/deserializer/convertChildrenDeserialize.ts
1809
+ const convertChildrenDeserialize = (children, deco, options) => {
1810
+ if (children.length === 0) return [{ text: "" }];
1811
+ return convertNodesDeserialize(children, deco, options);
1812
+ };
1813
+
1814
+ //#endregion
1815
+ //#region src/lib/deserializer/convertTextsDeserialize.ts
1816
+ const convertTextsDeserialize = (mdastNode, deco, options) => mdastNode.children.reduce((acc, n) => {
1817
+ const key = mdastToPlate(options.editor, mdastNode.type);
1818
+ const type = getPluginType(options.editor, key);
1819
+ acc.push(...buildSlateNode(n, {
1820
+ ...deco,
1821
+ [type]: true
1822
+ }, options));
1823
+ return acc;
1824
+ }, []);
1825
+
1826
+ //#endregion
1827
+ //#region src/lib/MarkdownPlugin.ts
1828
+ const MarkdownPlugin = createTSlatePlugin({
1829
+ key: KEYS.markdown,
1830
+ options: {
1831
+ allowedNodes: null,
1832
+ disallowedNodes: null,
1833
+ plainMarks: null,
1834
+ remarkPlugins: [],
1835
+ remarkStringifyOptions: null,
1836
+ rules: null
1837
+ }
1838
+ }).extendApi(({ editor }) => ({
1839
+ deserialize: bindFirst(deserializeMd, editor),
1840
+ deserializeInline: bindFirst(deserializeInlineMd, editor),
1841
+ serialize: bindFirst(serializeMd, editor)
1842
+ })).extend(({ api }) => ({ parser: {
1843
+ format: "text/plain",
1844
+ deserialize: ({ data }) => api.markdown.deserialize(data),
1845
+ query: ({ data, dataTransfer }) => {
1846
+ if (dataTransfer.getData("text/html")) return false;
1847
+ const { files } = dataTransfer;
1848
+ if (!files?.length && isUrl(data)) return false;
1849
+ return true;
1850
+ }
1851
+ } }));
1852
+
1853
+ //#endregion
1854
+ //#region src/lib/plugins/remarkMdx.ts
1855
+ const remarkMdx = tagRemarkPlugin(baseRemarkMdx, REMARK_MDX_TAG);
1856
+
1857
+ //#endregion
1858
+ //#region src/lib/plugins/remarkMention.ts
1859
+ /**
1860
+ * A remark plugin that converts @username patterns and [display
1861
+ * text](mention:id) patterns in text nodes into mention nodes. This plugin runs
1862
+ * after remark-gfm and transforms mention patterns into special mention nodes
1863
+ * that can be later converted into Plate mention elements.
1864
+ *
1865
+ * Supports two formats:
1866
+ *
1867
+ * - @username - Simple mention format (no spaces allowed)
1868
+ * - [display text](mention:id) - Markdown link-style format (supports spaces)
1869
+ */
1870
+ const remarkMention = () => (tree) => {
1871
+ visit(tree, "link", (node, index, parent) => {
1872
+ if (!parent || typeof index !== "number") return;
1873
+ if (node.url?.startsWith("mention:")) {
1874
+ let username = node.url.slice(8);
1875
+ username = decodeURIComponent(username);
1876
+ const displayText = node.children?.[0]?.value || username;
1877
+ const mentionNode = {
1878
+ children: [{
1879
+ type: "text",
1880
+ value: displayText
1881
+ }],
1882
+ displayText,
1883
+ type: "mention",
1884
+ username
1885
+ };
1886
+ parent.children[index] = mentionNode;
1887
+ }
1888
+ });
1889
+ visit(tree, "text", (node, index, parent) => {
1890
+ if (!parent || typeof index !== "number") return;
1891
+ if (parent.type === "link") return;
1892
+ const atMentionPattern = /(?:^|\s)@([a-zA-Z0-9_-]+)(?=[\s.,;:!?)]|$)/g;
1893
+ const parts = [];
1894
+ let lastIndex = 0;
1895
+ const text = node.value;
1896
+ const allMatches = [];
1897
+ let match;
1898
+ while (true) {
1899
+ match = atMentionPattern.exec(text);
1900
+ if (!match) break;
1901
+ const mentionStart = match[0].startsWith(" ") ? match.index + 1 : match.index;
1902
+ const mentionEnd = mentionStart + match[0].length - (match[0].startsWith(" ") ? 1 : 0);
1903
+ allMatches.push({
1904
+ end: mentionEnd,
1905
+ node: {
1906
+ children: [{
1907
+ type: "text",
1908
+ value: `@${match[1]}`
1909
+ }],
1910
+ type: "mention",
1911
+ username: match[1]
1912
+ },
1913
+ start: mentionStart
1914
+ });
1915
+ }
1916
+ allMatches.sort((a, b) => a.start - b.start);
1917
+ for (const matchInfo of allMatches) {
1918
+ if (matchInfo.start > lastIndex) parts.push({
1919
+ type: "text",
1920
+ value: text.slice(lastIndex, matchInfo.start)
1921
+ });
1922
+ parts.push(matchInfo.node);
1923
+ lastIndex = matchInfo.end;
1924
+ }
1925
+ if (lastIndex < text.length) parts.push({
1926
+ type: "text",
1927
+ value: text.slice(lastIndex)
1928
+ });
1929
+ if (parts.length > 0) parent.children.splice(index, 1, ...parts);
1930
+ });
1931
+ };
1932
+
1933
+ //#endregion
1934
+ export { MarkdownPlugin, REMARK_MDX_TAG, basicMarkdownMarks, buildMdastNode, buildRules, buildSlateNode, columnRules, convertChildrenDeserialize, convertNodesDeserialize, convertNodesSerialize, convertTextsDeserialize, convertTextsSerialize, customMdxDeserialize, defaultRules, deserializeInlineMd, deserializeMd, fontRules, getCustomMark, getDeserializerByKey, getMergedOptionsDeserialize, getMergedOptionsSerialize, getRemarkPluginsWithoutMdx, getSerializerByKey, getStyleValue, htmlToJsx, listToMdastTree, markdownToAstProcessor, markdownToSlateNodes, markdownToSlateNodesSafely, mdastToPlate, mdastToSlate, mediaRules, parseAttributes, parseMarkdownBlocks, plateToMdast, propsToAttributes, remarkMdx, remarkMention, serializeInlineMd, serializeMd, splitIncompleteMdx, stripMarkdown, stripMarkdownBlocks, stripMarkdownInline, tagRemarkPlugin, unreachable, wrapWithBlockId };
1935
+ //# sourceMappingURL=index.js.map