@lofcz/platejs-markdown 52.3.8 → 53.0.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.d.ts +27 -5
- package/dist/index.js +446 -120
- package/package.json +7 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as platejs2 from "platejs";
|
|
2
2
|
import { Descendant, NodeKey, Nullable, OmitFirst, PluginConfig, SlateEditor, TElement, TListElement, TNodeMap, TText, Value } from "platejs";
|
|
3
3
|
import { MdxJsxFlowElement, MdxJsxFlowElement as MdMdxJsxFlowElement, MdxJsxTextElement, MdxJsxTextElement as MdMdxJsxTextElement } from "mdast-util-mdx";
|
|
4
|
-
import { Options } from "remark-stringify";
|
|
5
4
|
import { Plugin } from "unified";
|
|
5
|
+
import { Options } from "remark-stringify";
|
|
6
6
|
import { Token } from "marked";
|
|
7
7
|
import baseRemarkMdx from "remark-mdx";
|
|
8
8
|
import { Blockquote as MdBlockquote, Break as MdBreak, Code as MdCode, Content as MdContent, Definition as MdDefinition, Delete as MdDelete, Emphasis as MdEmphasis, FootnoteDefinition as MdFootnoteDefinition, FootnoteReference as MdFootnoteReference, Heading as MdHeading, Html as MdHtml, Image as MdImage, ImageReference as MdImageReference, InlineCode as MdInlineCode, Link as MdLink, LinkReference as MdLinkReference, List as MdList, ListItem as MdListItem, Paragraph as MdParagraph, PhrasingContent as MDPhrasingContent, Root, Root as MdRoot, RootContent as MdRootContent, Strong as MdStrong, Table as MdTable, TableCell as MdTableCell, TableRow as MdTableRow, Text as MdText, ThematicBreak as MdThematicBreak, Yaml as MdYaml } from "mdast";
|
|
@@ -40,9 +40,6 @@ declare const convertTextsSerialize: (slateTexts: readonly TText[], options: Ser
|
|
|
40
40
|
//#region src/lib/serializer/listToMdastTree.d.ts
|
|
41
41
|
declare function listToMdastTree(nodes: TListElement[], options: SerializeMdOptions, isBlock?: boolean): any;
|
|
42
42
|
//#endregion
|
|
43
|
-
//#region src/lib/serializer/serializeInlineMd.d.ts
|
|
44
|
-
declare const serializeInlineMd: (editor: SlateEditor, options?: SerializeMdOptions) => string;
|
|
45
|
-
//#endregion
|
|
46
43
|
//#region src/lib/serializer/serializeMdWithSourceMap.d.ts
|
|
47
44
|
type SelectionPath = number[];
|
|
48
45
|
type MarkdownSourceMapKind = 'block' | 'blockquote' | 'code_block' | 'heading' | 'list_item' | 'media' | 'paragraph' | 'table_cell';
|
|
@@ -58,11 +55,36 @@ type MarkdownSourceMapSegment = {
|
|
|
58
55
|
text: string;
|
|
59
56
|
};
|
|
60
57
|
type SerializeMdSourceMapResult = {
|
|
58
|
+
/** All segments including container-level (parent MDX) segments, sorted by path. */
|
|
59
|
+
allSegments: MarkdownSourceMapSegment[];
|
|
61
60
|
markdown: string;
|
|
61
|
+
/** Leaf-level segments only (most precise per path). */
|
|
62
62
|
segments: MarkdownSourceMapSegment[];
|
|
63
63
|
};
|
|
64
64
|
declare const serializeMdWithSourceMap: (editor: SlateEditor, options?: Omit<SerializeMdOptions, "editor">) => SerializeMdSourceMapResult;
|
|
65
65
|
//#endregion
|
|
66
|
+
//#region src/lib/serializer/resolveSelectionLines.d.ts
|
|
67
|
+
type SelectionPoint = {
|
|
68
|
+
offset: number;
|
|
69
|
+
path: number[];
|
|
70
|
+
};
|
|
71
|
+
type SelectionRange = {
|
|
72
|
+
anchor: SelectionPoint;
|
|
73
|
+
focus: SelectionPoint;
|
|
74
|
+
};
|
|
75
|
+
type ResolveSelectionByPathResult = {
|
|
76
|
+
containsMdx: boolean;
|
|
77
|
+
endLine: number;
|
|
78
|
+
extractedMarkdown: string;
|
|
79
|
+
markdown: string;
|
|
80
|
+
segments: MarkdownSourceMapSegment[];
|
|
81
|
+
startLine: number;
|
|
82
|
+
};
|
|
83
|
+
declare const resolveSelectionByPath: (editor: SlateEditor, selection: SelectionRange, options?: Omit<SerializeMdOptions, "editor">) => ResolveSelectionByPathResult;
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/lib/serializer/serializeInlineMd.d.ts
|
|
86
|
+
declare const serializeInlineMd: (editor: SlateEditor, options?: SerializeMdOptions) => string;
|
|
87
|
+
//#endregion
|
|
66
88
|
//#region src/lib/serializer/wrapWithBlockId.d.ts
|
|
67
89
|
/**
|
|
68
90
|
* Wraps an mdast node with a block element containing an ID attribute. Used for
|
|
@@ -514,4 +536,4 @@ declare const tagRemarkPlugin: (pluginFn: any, tag: string) => {
|
|
|
514
536
|
};
|
|
515
537
|
declare const getRemarkPluginsWithoutMdx: (plugins: Plugin[]) => Plugin[];
|
|
516
538
|
//#endregion
|
|
517
|
-
export { AllowNodeConfig, DeserializeMdOptions, type MDPhrasingContent, MarkdownConfig, MarkdownPlugin, MarkdownSourceMapKind, MarkdownSourceMapSegment, type MdBlockquote, type MdBreak, type MdCode, type MdContent, MdDecoration, type MdDefinition, type MdDelete, type MdEmphasis, type MdFootnoteDefinition, type MdFootnoteReference, type MdHeading, type MdHtml, type MdImage, type MdImageReference, type MdInlineCode, type MdInlineMath, type MdLink, type MdLinkReference, type MdList, type MdListItem, MdMark, type MdMath, type MdMdxJsxFlowElement, type MdMdxJsxTextElement, MdNodeParser, type MdParagraph, type MdRoot, type MdRootContent, MdRules, type MdStrong, type MdTable, type MdTableCell, type MdTableRow, type MdText, type MdThematicBreak, MdType, type MdYaml, MentionNode, ParseMarkdownBlocksOptions, PlateType, REMARK_MDX_TAG, SerializeMdOptions, SerializeMdSourceMapResult, StrictPlateType, 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, serializeMdWithSourceMap, splitIncompleteMdx, stripMarkdown, stripMarkdownBlocks, stripMarkdownInline, tagRemarkPlugin, type unistLib, unreachable, wrapWithBlockId };
|
|
539
|
+
export { AllowNodeConfig, DeserializeMdOptions, type MDPhrasingContent, MarkdownConfig, MarkdownPlugin, MarkdownSourceMapKind, MarkdownSourceMapSegment, type MdBlockquote, type MdBreak, type MdCode, type MdContent, MdDecoration, type MdDefinition, type MdDelete, type MdEmphasis, type MdFootnoteDefinition, type MdFootnoteReference, type MdHeading, type MdHtml, type MdImage, type MdImageReference, type MdInlineCode, type MdInlineMath, type MdLink, type MdLinkReference, type MdList, type MdListItem, MdMark, type MdMath, type MdMdxJsxFlowElement, type MdMdxJsxTextElement, MdNodeParser, type MdParagraph, type MdRoot, type MdRootContent, MdRules, type MdStrong, type MdTable, type MdTableCell, type MdTableRow, type MdText, type MdThematicBreak, MdType, type MdYaml, MentionNode, ParseMarkdownBlocksOptions, PlateType, REMARK_MDX_TAG, ResolveSelectionByPathResult, SelectionRange, SerializeMdOptions, SerializeMdSourceMapResult, StrictPlateType, 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, resolveSelectionByPath, serializeInlineMd, serializeMd, serializeMdWithSourceMap, splitIncompleteMdx, stripMarkdown, stripMarkdownBlocks, stripMarkdownInline, tagRemarkPlugin, type unistLib, unreachable, wrapWithBlockId };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ElementApi, KEYS, TextApi, bindFirst, createTSlatePlugin, getPluginKey, getPluginType, isUrl } from "platejs";
|
|
2
2
|
import "mdast-util-mdx";
|
|
3
|
+
import { normalizeDateValue } from "@platejs/date";
|
|
3
4
|
import kebabCase from "lodash/kebabCase.js";
|
|
4
|
-
import remarkStringify from "remark-stringify";
|
|
5
|
-
import { unified } from "unified";
|
|
6
5
|
import { defaultHandlers, toMarkdown } from "mdast-util-to-markdown";
|
|
6
|
+
import { unified } from "unified";
|
|
7
|
+
import remarkStringify from "remark-stringify";
|
|
7
8
|
import remarkParse from "remark-parse";
|
|
8
9
|
import { marked } from "marked";
|
|
9
10
|
import baseRemarkMdx from "remark-mdx";
|
|
@@ -131,7 +132,7 @@ function propsToAttributes(props) {
|
|
|
131
132
|
return Object.entries(props).map(([name, value]) => ({
|
|
132
133
|
name,
|
|
133
134
|
type: "mdxJsxAttribute",
|
|
134
|
-
value: typeof value === "string" ? value : JSON.stringify(value)
|
|
135
|
+
value: typeof value === "string" || value?.type === "mdxJsxAttributeValueExpression" ? value : JSON.stringify(value)
|
|
135
136
|
}));
|
|
136
137
|
}
|
|
137
138
|
|
|
@@ -239,7 +240,7 @@ function createMediaRule() {
|
|
|
239
240
|
};
|
|
240
241
|
},
|
|
241
242
|
serialize: (node, options) => {
|
|
242
|
-
const {
|
|
243
|
+
const { children, type, url, ...rest } = node;
|
|
243
244
|
return {
|
|
244
245
|
attributes: propsToAttributes({
|
|
245
246
|
...rest,
|
|
@@ -255,6 +256,7 @@ function createMediaRule() {
|
|
|
255
256
|
const mediaRules = {
|
|
256
257
|
audio: createMediaRule(),
|
|
257
258
|
file: createMediaRule(),
|
|
259
|
+
media_embed: createMediaRule(),
|
|
258
260
|
video: createMediaRule()
|
|
259
261
|
};
|
|
260
262
|
|
|
@@ -655,45 +657,6 @@ const shouldIncludeNode$2 = (node, options) => {
|
|
|
655
657
|
return true;
|
|
656
658
|
};
|
|
657
659
|
|
|
658
|
-
//#endregion
|
|
659
|
-
//#region src/lib/serializer/serializeInlineMd.ts
|
|
660
|
-
const serializeInlineMd = (editor, options) => {
|
|
661
|
-
const mergedOptions = getMergedOptionsSerialize(editor, options);
|
|
662
|
-
const toRemarkProcessor = unified().use(mergedOptions.remarkPlugins ?? []).use(remarkStringify, {
|
|
663
|
-
emphasis: "_",
|
|
664
|
-
...mergedOptions?.remarkStringifyOptions
|
|
665
|
-
});
|
|
666
|
-
if (options?.value?.length === 0) return "";
|
|
667
|
-
const convertedTexts = convertTextsSerialize(mergedOptions.value, { editor });
|
|
668
|
-
return toRemarkProcessor.stringify({
|
|
669
|
-
children: convertedTexts,
|
|
670
|
-
type: "root"
|
|
671
|
-
});
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
//#endregion
|
|
675
|
-
//#region src/lib/serializer/serializeMd.ts
|
|
676
|
-
/** Serialize the editor value to Markdown. */
|
|
677
|
-
const serializeMd = (editor, options) => {
|
|
678
|
-
const mergedOptions = getMergedOptionsSerialize(editor, options);
|
|
679
|
-
const { remarkPlugins, value } = mergedOptions;
|
|
680
|
-
const toRemarkProcessor = unified().use(remarkPlugins ?? []).use(remarkStringify, {
|
|
681
|
-
emphasis: "_",
|
|
682
|
-
...mergedOptions?.remarkStringifyOptions
|
|
683
|
-
});
|
|
684
|
-
const mdast = slateToMdast({
|
|
685
|
-
children: value,
|
|
686
|
-
options: mergedOptions
|
|
687
|
-
});
|
|
688
|
-
return toRemarkProcessor.stringify(mdast);
|
|
689
|
-
};
|
|
690
|
-
const slateToMdast = ({ children, options }) => {
|
|
691
|
-
return {
|
|
692
|
-
children: convertNodesSerialize(children, options, true),
|
|
693
|
-
type: "root"
|
|
694
|
-
};
|
|
695
|
-
};
|
|
696
|
-
|
|
697
660
|
//#endregion
|
|
698
661
|
//#region src/lib/serializer/serializeMdWithSourceMap.ts
|
|
699
662
|
const collectSlateText = (node) => {
|
|
@@ -770,7 +733,6 @@ const buildSource = (node, editor) => {
|
|
|
770
733
|
const path = Array.isArray(node.__sourceMapPath) ? node.__sourceMapPath : null;
|
|
771
734
|
if (!path) return null;
|
|
772
735
|
const text = collectSlateText(node);
|
|
773
|
-
if (!text.trim() && kind !== "media" && kind !== "block") return null;
|
|
774
736
|
return {
|
|
775
737
|
containsMdx: false,
|
|
776
738
|
kind,
|
|
@@ -826,6 +788,72 @@ const attachTableCellSources = (mdastTable, slateTable, editor) => {
|
|
|
826
788
|
}
|
|
827
789
|
}
|
|
828
790
|
};
|
|
791
|
+
/**
|
|
792
|
+
* Custom serializers (e.g. MDX components) build their own mdast subtree,
|
|
793
|
+
* bypassing convertNodesWithSource. Walk Slate and mdast element children
|
|
794
|
+
* in parallel (by position) and attach sourceMap data so the handler
|
|
795
|
+
* wrappers capture per-child segments during serialization.
|
|
796
|
+
*
|
|
797
|
+
* Position-based matching works because custom serializers preserve child
|
|
798
|
+
* order. We filter to element children only (skip text/leaf nodes) in both
|
|
799
|
+
* trees before zipping.
|
|
800
|
+
*/
|
|
801
|
+
/**
|
|
802
|
+
* Walk the mdast list tree and attach sources from the flat Slate list
|
|
803
|
+
* paragraphs. Slate uses flat paragraphs with `listStyleType` while mdast
|
|
804
|
+
* nests them as list > listItem > paragraph. We walk both in document order
|
|
805
|
+
* to zip them correctly.
|
|
806
|
+
*/
|
|
807
|
+
const attachListSources = (mdastList, slateListItems, editor, insideMdx) => {
|
|
808
|
+
let slateIdx = 0;
|
|
809
|
+
const walkList = (list) => {
|
|
810
|
+
if (!Array.isArray(list.children)) return;
|
|
811
|
+
for (const listItem of list.children) {
|
|
812
|
+
if (listItem.type !== "listItem" || !Array.isArray(listItem.children)) continue;
|
|
813
|
+
for (const child of listItem.children) if (child.type === "list") walkList(child);
|
|
814
|
+
else if (slateIdx < slateListItems.length) {
|
|
815
|
+
const slateChild = slateListItems[slateIdx++];
|
|
816
|
+
const source = buildSource(slateChild, editor);
|
|
817
|
+
if (source) {
|
|
818
|
+
if (insideMdx) source.containsMdx = true;
|
|
819
|
+
attachSource(child, source);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
walkList(mdastList);
|
|
825
|
+
};
|
|
826
|
+
const attachDescendantSources = (mdastNode, slateNode, editor, insideMdx) => {
|
|
827
|
+
if (!insideMdx && !MDX_MDAST_TYPES.has(mdastNode?.type ?? "")) return;
|
|
828
|
+
const slateChildren = slateNode?.children;
|
|
829
|
+
const mdastChildren = mdastNode?.children;
|
|
830
|
+
if (!Array.isArray(slateChildren) || !Array.isArray(mdastChildren)) return;
|
|
831
|
+
const slateElements = slateChildren.filter((c) => c?.type && Array.isArray(c.children));
|
|
832
|
+
const mdastElements = mdastChildren.filter((c) => c.type && c.type !== "text");
|
|
833
|
+
let si = 0;
|
|
834
|
+
for (const mdastChild of mdastElements) {
|
|
835
|
+
if (mdastChild.type === "list") {
|
|
836
|
+
const listRun = [];
|
|
837
|
+
while (si < slateElements.length && slateElements[si].listStyleType) {
|
|
838
|
+
listRun.push(slateElements[si]);
|
|
839
|
+
si++;
|
|
840
|
+
}
|
|
841
|
+
attachListSources(mdastChild, listRun, editor, insideMdx);
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (si >= slateElements.length) break;
|
|
845
|
+
const slateChild = slateElements[si];
|
|
846
|
+
si++;
|
|
847
|
+
const isMdx = insideMdx || MDX_MDAST_TYPES.has(mdastChild.type ?? "");
|
|
848
|
+
const source = buildSource(slateChild, editor);
|
|
849
|
+
if (source) {
|
|
850
|
+
if (isMdx) source.containsMdx = true;
|
|
851
|
+
attachSource(mdastChild, source);
|
|
852
|
+
}
|
|
853
|
+
if (mdastChild.type === "table") attachTableCellSources(mdastChild, slateChild, editor);
|
|
854
|
+
else if (insideMdx || MDX_MDAST_TYPES.has(mdastChild.type ?? "")) attachDescendantSources(mdastChild, slateChild, editor, isMdx);
|
|
855
|
+
}
|
|
856
|
+
};
|
|
829
857
|
const buildMdastNodeWithSource = (node, options, isBlock = false) => {
|
|
830
858
|
let mdastNode = buildMdastNode(node, options, isBlock);
|
|
831
859
|
if (!mdastNode) {
|
|
@@ -847,19 +875,20 @@ const buildMdastNodeWithSource = (node, options, isBlock = false) => {
|
|
|
847
875
|
return mdastNode;
|
|
848
876
|
}
|
|
849
877
|
const source = buildSource(node, options.editor);
|
|
878
|
+
const isMdxOutput = MDX_MDAST_TYPES.has(mdastNode?.type ?? "");
|
|
850
879
|
if (!source) {
|
|
851
|
-
if (
|
|
880
|
+
if (isMdxOutput) propagateMdxFlag(mdastNode);
|
|
881
|
+
attachDescendantSources(mdastNode, node, options.editor, isMdxOutput);
|
|
852
882
|
return mdastNode;
|
|
853
883
|
}
|
|
854
|
-
if (
|
|
855
|
-
source.containsMdx = true;
|
|
856
|
-
if (Array.isArray(mdastNode?.children)) for (const child of mdastNode.children) propagateMdxFlag(child);
|
|
857
|
-
}
|
|
884
|
+
if (isMdxOutput) source.containsMdx = true;
|
|
858
885
|
if (source.kind === "blockquote" && Array.isArray(mdastNode?.children) && mdastNode.children[0]) {
|
|
859
886
|
attachSource(mdastNode.children[0], source);
|
|
860
887
|
return mdastNode;
|
|
861
888
|
}
|
|
862
|
-
|
|
889
|
+
attachSource(mdastNode, source);
|
|
890
|
+
attachDescendantSources(mdastNode, node, options.editor, isMdxOutput);
|
|
891
|
+
return mdastNode;
|
|
863
892
|
};
|
|
864
893
|
const convertNodesWithSource = (nodes, options, isBlock = false) => {
|
|
865
894
|
const mdastNodes = [];
|
|
@@ -1046,13 +1075,248 @@ const serializeMdWithSourceMap = (editor, options) => {
|
|
|
1046
1075
|
}
|
|
1047
1076
|
const ordered = Array.from(deduped.values()).sort((a, b) => comparePaths(a.path, b.path));
|
|
1048
1077
|
return {
|
|
1078
|
+
allSegments: ordered,
|
|
1049
1079
|
markdown,
|
|
1050
1080
|
segments: ordered.filter((seg) => !ordered.some((other) => other !== seg && isPathPrefix(seg.path, other.path)))
|
|
1051
1081
|
};
|
|
1052
1082
|
};
|
|
1053
1083
|
|
|
1084
|
+
//#endregion
|
|
1085
|
+
//#region src/lib/serializer/resolveSelectionLines.ts
|
|
1086
|
+
const MDX_TAG_RE = /<[a-zA-Z_][\w.-]*/;
|
|
1087
|
+
/** `a` is an ancestor-or-self of `b`: a's path is a prefix of (or equal to) b's. */
|
|
1088
|
+
const isAncestorOrSelf = (a, b) => a.length <= b.length && a.every((v, i) => b[i] === v);
|
|
1089
|
+
const comparePoints = (a, b) => {
|
|
1090
|
+
const len = Math.max(a.path.length, b.path.length);
|
|
1091
|
+
for (let i = 0; i < len; i++) {
|
|
1092
|
+
const av = a.path[i] ?? 0;
|
|
1093
|
+
const bv = b.path[i] ?? 0;
|
|
1094
|
+
if (av !== bv) return av - bv;
|
|
1095
|
+
}
|
|
1096
|
+
return a.offset - b.offset;
|
|
1097
|
+
};
|
|
1098
|
+
/**
|
|
1099
|
+
* Find the segment covering the start of a selection at `path`.
|
|
1100
|
+
*
|
|
1101
|
+
* 1. Ancestor-or-self: segment whose path is a prefix of `path` (most specific wins)
|
|
1102
|
+
* 2. First descendant: `path` is a prefix of segment's path (container selection)
|
|
1103
|
+
*/
|
|
1104
|
+
const findStartSegment = (segments, path) => {
|
|
1105
|
+
let best = null;
|
|
1106
|
+
let bestLen = -1;
|
|
1107
|
+
for (const seg of segments) if (isAncestorOrSelf(seg.path, path) && seg.path.length > bestLen) {
|
|
1108
|
+
best = seg;
|
|
1109
|
+
bestLen = seg.path.length;
|
|
1110
|
+
}
|
|
1111
|
+
if (best) return best;
|
|
1112
|
+
for (const seg of segments) if (isAncestorOrSelf(path, seg.path)) return seg;
|
|
1113
|
+
return null;
|
|
1114
|
+
};
|
|
1115
|
+
/**
|
|
1116
|
+
* Find the segment covering the end of a selection at `path`.
|
|
1117
|
+
*
|
|
1118
|
+
* Same as findStartSegment but for descendant matches picks the LAST one
|
|
1119
|
+
* (the container's last child segment).
|
|
1120
|
+
*/
|
|
1121
|
+
const findEndSegment = (segments, path) => {
|
|
1122
|
+
let best = null;
|
|
1123
|
+
let bestLen = -1;
|
|
1124
|
+
for (const seg of segments) if (isAncestorOrSelf(seg.path, path) && seg.path.length > bestLen) {
|
|
1125
|
+
best = seg;
|
|
1126
|
+
bestLen = seg.path.length;
|
|
1127
|
+
}
|
|
1128
|
+
if (best) return best;
|
|
1129
|
+
let last = null;
|
|
1130
|
+
for (const seg of segments) if (isAncestorOrSelf(path, seg.path)) last = seg;
|
|
1131
|
+
return last;
|
|
1132
|
+
};
|
|
1133
|
+
/** Recursively compute the total text length of a Slate node. */
|
|
1134
|
+
const getTextLength = (node) => {
|
|
1135
|
+
if (typeof node?.text === "string") return node.text.length;
|
|
1136
|
+
if (Array.isArray(node?.children)) return node.children.reduce((sum, child) => sum + getTextLength(child), 0);
|
|
1137
|
+
return 0;
|
|
1138
|
+
};
|
|
1139
|
+
/** Walk the editor tree to reach the node at `path`. */
|
|
1140
|
+
const getNodeAtPath = (editor, path) => {
|
|
1141
|
+
let node = { children: editor.children };
|
|
1142
|
+
for (const idx of path) {
|
|
1143
|
+
if (!node?.children?.[idx]) return null;
|
|
1144
|
+
node = node.children[idx];
|
|
1145
|
+
}
|
|
1146
|
+
return node;
|
|
1147
|
+
};
|
|
1148
|
+
/**
|
|
1149
|
+
* Compute the absolute character offset within a segment's text.
|
|
1150
|
+
*
|
|
1151
|
+
* Given a segment at `segPath` (e.g. [1,3,0] = a paragraph) and a selection
|
|
1152
|
+
* point at `selPath` (e.g. [1,3,0,1] = second text node) with `selOffset`,
|
|
1153
|
+
* sum up text lengths of all Slate nodes preceding the selection point within
|
|
1154
|
+
* the segment, then add `selOffset`.
|
|
1155
|
+
*/
|
|
1156
|
+
const computeAbsoluteOffset = (editor, segPath, selPath, selOffset) => {
|
|
1157
|
+
const segNode = getNodeAtPath(editor, segPath);
|
|
1158
|
+
if (!segNode) return null;
|
|
1159
|
+
const remainingPath = selPath.slice(segPath.length);
|
|
1160
|
+
if (remainingPath.length === 0) {
|
|
1161
|
+
let textOffset = 0;
|
|
1162
|
+
const children = segNode.children ?? [];
|
|
1163
|
+
for (let i = 0; i < Math.min(selOffset, children.length); i++) textOffset += getTextLength(children[i]);
|
|
1164
|
+
return textOffset;
|
|
1165
|
+
}
|
|
1166
|
+
let current = segNode;
|
|
1167
|
+
let accumulated = 0;
|
|
1168
|
+
for (const childIdx of remainingPath) {
|
|
1169
|
+
const children = current.children ?? [];
|
|
1170
|
+
for (let i = 0; i < childIdx && i < children.length; i++) accumulated += getTextLength(children[i]);
|
|
1171
|
+
if (!children[childIdx]) return null;
|
|
1172
|
+
current = children[childIdx];
|
|
1173
|
+
}
|
|
1174
|
+
return accumulated + selOffset;
|
|
1175
|
+
};
|
|
1176
|
+
/**
|
|
1177
|
+
* Given a segment and an absolute text offset, determine which line within
|
|
1178
|
+
* the segment the offset falls on.
|
|
1179
|
+
*/
|
|
1180
|
+
const offsetToLine = (segText, segStartLine, absOffset) => {
|
|
1181
|
+
const clamped = Math.max(0, Math.min(absOffset, segText.length));
|
|
1182
|
+
return segStartLine + (segText.slice(0, clamped).match(/\n/g) ?? []).length;
|
|
1183
|
+
};
|
|
1184
|
+
/**
|
|
1185
|
+
* Whether a segment supports sub-line narrowing. Only for kinds where text
|
|
1186
|
+
* content lines map 1:1 to markdown output lines (no structural wrappers
|
|
1187
|
+
* like code fences, MDX tags, or table delimiters).
|
|
1188
|
+
*/
|
|
1189
|
+
const NARROWABLE_KINDS = new Set([
|
|
1190
|
+
"paragraph",
|
|
1191
|
+
"heading",
|
|
1192
|
+
"list_item",
|
|
1193
|
+
"blockquote"
|
|
1194
|
+
]);
|
|
1195
|
+
const canNarrow = (seg) => NARROWABLE_KINDS.has(seg.kind);
|
|
1196
|
+
const extractLines = (md, start, end) => md.split("\n").slice(start - 1, end).join("\n");
|
|
1197
|
+
/**
|
|
1198
|
+
* Resolve which markdown lines correspond to a Plate editor selection
|
|
1199
|
+
* using the source-map path mapping (no text matching).
|
|
1200
|
+
*
|
|
1201
|
+
* 1. Serializes editor to markdown with source-map segments
|
|
1202
|
+
* 2. Finds the segment matching the anchor path (longest prefix)
|
|
1203
|
+
* 3. Finds the segment matching the focus path
|
|
1204
|
+
* 4. Narrows to sub-line precision using selection offsets
|
|
1205
|
+
* 5. Returns the line range and extracted markdown
|
|
1206
|
+
*/
|
|
1207
|
+
/**
|
|
1208
|
+
* When the selection lands on a container path (e.g. clicking an MDX label),
|
|
1209
|
+
* look up the container segment in `allSegments` to include the MDX wrapper
|
|
1210
|
+
* lines (opening/closing tags) rather than just the inner content.
|
|
1211
|
+
*/
|
|
1212
|
+
const tryContainerExpand = ({ allSegments, leafSeg, selPath, currentLine, direction }) => {
|
|
1213
|
+
if (!leafSeg) return currentLine;
|
|
1214
|
+
if (selPath.length >= leafSeg.path.length) return currentLine;
|
|
1215
|
+
let containerSeg = null;
|
|
1216
|
+
let containerLen = -1;
|
|
1217
|
+
for (const seg of allSegments) if (isAncestorOrSelf(seg.path, selPath) && seg.path.length > containerLen && seg !== leafSeg) {
|
|
1218
|
+
containerSeg = seg;
|
|
1219
|
+
containerLen = seg.path.length;
|
|
1220
|
+
}
|
|
1221
|
+
for (const seg of allSegments) if (seg.path.length === selPath.length && seg.path.every((v, i) => selPath[i] === v)) {
|
|
1222
|
+
containerSeg = seg;
|
|
1223
|
+
break;
|
|
1224
|
+
}
|
|
1225
|
+
if (!containerSeg) return currentLine;
|
|
1226
|
+
return direction === "start" ? Math.min(currentLine, containerSeg.startLine) : Math.max(currentLine, containerSeg.endLine);
|
|
1227
|
+
};
|
|
1228
|
+
const resolveSelectionByPath = (editor, selection, options) => {
|
|
1229
|
+
const { allSegments, markdown, segments } = serializeMdWithSourceMap(editor, options);
|
|
1230
|
+
const [start, end] = comparePoints(selection.anchor, selection.focus) <= 0 ? [selection.anchor, selection.focus] : [selection.focus, selection.anchor];
|
|
1231
|
+
const startSeg = findStartSegment(segments, start.path);
|
|
1232
|
+
const endSeg = findEndSegment(segments, end.path);
|
|
1233
|
+
if (!startSeg && !endSeg) return {
|
|
1234
|
+
containsMdx: false,
|
|
1235
|
+
endLine: 1,
|
|
1236
|
+
extractedMarkdown: "",
|
|
1237
|
+
markdown,
|
|
1238
|
+
segments,
|
|
1239
|
+
startLine: 1
|
|
1240
|
+
};
|
|
1241
|
+
let startLine = startSeg?.startLine ?? endSeg.startLine;
|
|
1242
|
+
let endLine = endSeg?.endLine ?? startSeg.endLine;
|
|
1243
|
+
if (startSeg && startSeg.endLine > startSeg.startLine && canNarrow(startSeg)) {
|
|
1244
|
+
const absOffset = computeAbsoluteOffset(editor, startSeg.path, start.path, start.offset);
|
|
1245
|
+
if (absOffset !== null) startLine = offsetToLine(startSeg.text, startSeg.startLine, absOffset);
|
|
1246
|
+
}
|
|
1247
|
+
if (endSeg && endSeg.endLine > endSeg.startLine && canNarrow(endSeg)) {
|
|
1248
|
+
const absOffset = computeAbsoluteOffset(editor, endSeg.path, end.path, end.offset);
|
|
1249
|
+
if (absOffset !== null) endLine = offsetToLine(endSeg.text, endSeg.startLine, absOffset);
|
|
1250
|
+
}
|
|
1251
|
+
startLine = tryContainerExpand({
|
|
1252
|
+
allSegments,
|
|
1253
|
+
currentLine: startLine,
|
|
1254
|
+
direction: "start",
|
|
1255
|
+
leafSeg: startSeg,
|
|
1256
|
+
selPath: start.path
|
|
1257
|
+
});
|
|
1258
|
+
endLine = tryContainerExpand({
|
|
1259
|
+
allSegments,
|
|
1260
|
+
currentLine: endLine,
|
|
1261
|
+
direction: "end",
|
|
1262
|
+
leafSeg: endSeg,
|
|
1263
|
+
selPath: end.path
|
|
1264
|
+
});
|
|
1265
|
+
if (startLine > endLine) [startLine, endLine] = [endLine, startLine];
|
|
1266
|
+
const extractedMarkdown = extractLines(markdown, startLine, endLine);
|
|
1267
|
+
return {
|
|
1268
|
+
containsMdx: extractedMarkdown.includes("<") && MDX_TAG_RE.test(extractedMarkdown),
|
|
1269
|
+
endLine,
|
|
1270
|
+
extractedMarkdown,
|
|
1271
|
+
markdown,
|
|
1272
|
+
segments,
|
|
1273
|
+
startLine
|
|
1274
|
+
};
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
//#endregion
|
|
1278
|
+
//#region src/lib/serializer/serializeInlineMd.ts
|
|
1279
|
+
const serializeInlineMd = (editor, options) => {
|
|
1280
|
+
const mergedOptions = getMergedOptionsSerialize(editor, options);
|
|
1281
|
+
const toRemarkProcessor = unified().use(mergedOptions.remarkPlugins ?? []).use(remarkStringify, {
|
|
1282
|
+
emphasis: "_",
|
|
1283
|
+
...mergedOptions?.remarkStringifyOptions
|
|
1284
|
+
});
|
|
1285
|
+
if (options?.value?.length === 0) return "";
|
|
1286
|
+
const convertedTexts = convertTextsSerialize(mergedOptions.value, { editor });
|
|
1287
|
+
return toRemarkProcessor.stringify({
|
|
1288
|
+
children: convertedTexts,
|
|
1289
|
+
type: "root"
|
|
1290
|
+
});
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
//#endregion
|
|
1294
|
+
//#region src/lib/serializer/serializeMd.ts
|
|
1295
|
+
/** Serialize the editor value to Markdown. */
|
|
1296
|
+
const serializeMd = (editor, options) => {
|
|
1297
|
+
const mergedOptions = getMergedOptionsSerialize(editor, options);
|
|
1298
|
+
const { remarkPlugins, value } = mergedOptions;
|
|
1299
|
+
const toRemarkProcessor = unified().use(remarkPlugins ?? []).use(remarkStringify, {
|
|
1300
|
+
emphasis: "_",
|
|
1301
|
+
resourceLink: false,
|
|
1302
|
+
...mergedOptions?.remarkStringifyOptions
|
|
1303
|
+
});
|
|
1304
|
+
const mdast = slateToMdast({
|
|
1305
|
+
children: value,
|
|
1306
|
+
options: mergedOptions
|
|
1307
|
+
});
|
|
1308
|
+
return toRemarkProcessor.stringify(mdast);
|
|
1309
|
+
};
|
|
1310
|
+
const slateToMdast = ({ children, options }) => {
|
|
1311
|
+
return {
|
|
1312
|
+
children: convertNodesSerialize(children, options, true),
|
|
1313
|
+
type: "root"
|
|
1314
|
+
};
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1054
1317
|
//#endregion
|
|
1055
1318
|
//#region src/lib/rules/defaultRules.ts
|
|
1319
|
+
const BARE_AUTOLINK_PROTOCOL_REGEX = /^https?:\/\//i;
|
|
1056
1320
|
const LEADING_NEWLINE_REGEX = /^\n/;
|
|
1057
1321
|
function isBoolean(value) {
|
|
1058
1322
|
return value === true || value === false || !!value && typeof value === "object" && Object.prototype.toString.call(value) === "[object Boolean]";
|
|
@@ -1070,6 +1334,33 @@ const deserializeClassicListItemChildren = (mdastChildren, deco, options) => {
|
|
|
1070
1334
|
if (!children.some((child) => child.type === licType)) children.unshift(createClassicListItemContent(options.editor));
|
|
1071
1335
|
return children;
|
|
1072
1336
|
};
|
|
1337
|
+
const groupInlineChildrenIntoParagraphs = (editor, children = []) => {
|
|
1338
|
+
const paragraphType = getPluginType(editor, KEYS.p);
|
|
1339
|
+
const elements = [];
|
|
1340
|
+
let inlineNodes = [];
|
|
1341
|
+
const flushInlineNodes = () => {
|
|
1342
|
+
if (inlineNodes.length === 0) return;
|
|
1343
|
+
elements.push({
|
|
1344
|
+
children: inlineNodes,
|
|
1345
|
+
type: paragraphType
|
|
1346
|
+
});
|
|
1347
|
+
inlineNodes = [];
|
|
1348
|
+
};
|
|
1349
|
+
children.forEach((child) => {
|
|
1350
|
+
if (ElementApi.isElement(child) && !editor.api.isInline(child) && editor.api.isBlock(child)) {
|
|
1351
|
+
flushInlineNodes();
|
|
1352
|
+
elements.push(child);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
inlineNodes.push(child);
|
|
1356
|
+
});
|
|
1357
|
+
flushInlineNodes();
|
|
1358
|
+
if (elements.length > 0) return elements;
|
|
1359
|
+
return [{
|
|
1360
|
+
children: [{ text: "" }],
|
|
1361
|
+
type: paragraphType
|
|
1362
|
+
}];
|
|
1363
|
+
};
|
|
1073
1364
|
const defaultRules = {
|
|
1074
1365
|
a: {
|
|
1075
1366
|
deserialize: (mdastNode, deco, options) => ({
|
|
@@ -1077,48 +1368,29 @@ const defaultRules = {
|
|
|
1077
1368
|
type: getPluginType(options.editor, KEYS.a),
|
|
1078
1369
|
url: mdastNode.url
|
|
1079
1370
|
}),
|
|
1080
|
-
serialize: (node, options) => ({
|
|
1081
|
-
children: convertNodesSerialize(node.children, options),
|
|
1082
|
-
type: "link",
|
|
1083
|
-
url: node.url
|
|
1084
|
-
})
|
|
1085
|
-
},
|
|
1086
|
-
blockquote: {
|
|
1087
|
-
deserialize: (mdastNode, deco, options) => {
|
|
1088
|
-
return {
|
|
1089
|
-
children: (mdastNode.children.length > 0 ? mdastNode.children.flatMap((paragraph, index, children) => {
|
|
1090
|
-
if (paragraph.type === "paragraph") {
|
|
1091
|
-
if (children.length > 1 && children.length - 1 !== index) {
|
|
1092
|
-
const paragraphChildren = convertChildrenDeserialize(paragraph.children, deco, options);
|
|
1093
|
-
paragraphChildren.push({ text: "\n" }, { text: "\n" });
|
|
1094
|
-
return paragraphChildren;
|
|
1095
|
-
}
|
|
1096
|
-
return convertChildrenDeserialize(paragraph.children, deco, options);
|
|
1097
|
-
}
|
|
1098
|
-
if ("children" in paragraph) return convertChildrenDeserialize(paragraph.children, deco, options);
|
|
1099
|
-
return [{ text: "" }];
|
|
1100
|
-
}) : [{ text: "" }]).flatMap((child) => child.type === "blockquote" ? child.children : [child]),
|
|
1101
|
-
type: getPluginType(options.editor, KEYS.blockquote)
|
|
1102
|
-
};
|
|
1103
|
-
},
|
|
1104
1371
|
serialize: (node, options) => {
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
paragraphChildren.at(-1).type = "html";
|
|
1111
|
-
paragraphChildren.at(-1).value = "\n<br />";
|
|
1112
|
-
}
|
|
1372
|
+
const children = convertNodesSerialize(node.children, options);
|
|
1373
|
+
if (children.length === 1 && children[0]?.type === "text" && children[0].value === node.url && BARE_AUTOLINK_PROTOCOL_REGEX.test(node.url ?? "")) return {
|
|
1374
|
+
type: "html",
|
|
1375
|
+
value: node.url
|
|
1376
|
+
};
|
|
1113
1377
|
return {
|
|
1114
|
-
children
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
}],
|
|
1118
|
-
type: "blockquote"
|
|
1378
|
+
children,
|
|
1379
|
+
type: "link",
|
|
1380
|
+
url: node.url
|
|
1119
1381
|
};
|
|
1120
1382
|
}
|
|
1121
1383
|
},
|
|
1384
|
+
blockquote: {
|
|
1385
|
+
deserialize: (mdastNode, deco, options) => ({
|
|
1386
|
+
children: groupInlineChildrenIntoParagraphs(options.editor, convertNodesDeserialize(mdastNode.children, deco, options)),
|
|
1387
|
+
type: getPluginType(options.editor, KEYS.blockquote)
|
|
1388
|
+
}),
|
|
1389
|
+
serialize: (node, options) => ({
|
|
1390
|
+
children: convertNodesSerialize(groupInlineChildrenIntoParagraphs(options.editor, node.children), options),
|
|
1391
|
+
type: "blockquote"
|
|
1392
|
+
})
|
|
1393
|
+
},
|
|
1122
1394
|
bold: {
|
|
1123
1395
|
mark: true,
|
|
1124
1396
|
deserialize: (mdastNode, deco, options) => convertTextsDeserialize(mdastNode, deco, options)
|
|
@@ -1194,18 +1466,25 @@ const defaultRules = {
|
|
|
1194
1466
|
},
|
|
1195
1467
|
date: {
|
|
1196
1468
|
deserialize(mdastNode, _deco, options) {
|
|
1469
|
+
const props = parseAttributes(mdastNode.attributes);
|
|
1197
1470
|
return {
|
|
1198
1471
|
children: [{ text: "" }],
|
|
1199
|
-
|
|
1472
|
+
...normalizeDateValue(typeof props.value === "string" ? props.value : (mdastNode.children?.[0])?.value || ""),
|
|
1200
1473
|
type: getPluginType(options.editor, KEYS.date)
|
|
1201
1474
|
};
|
|
1202
1475
|
},
|
|
1203
|
-
serialize({ date }) {
|
|
1476
|
+
serialize({ date, rawDate }) {
|
|
1477
|
+
if (date && !rawDate) return {
|
|
1478
|
+
attributes: propsToAttributes({ value: date }),
|
|
1479
|
+
children: [],
|
|
1480
|
+
name: "date",
|
|
1481
|
+
type: "mdxJsxTextElement"
|
|
1482
|
+
};
|
|
1204
1483
|
return {
|
|
1205
1484
|
attributes: [],
|
|
1206
1485
|
children: [{
|
|
1207
1486
|
type: "text",
|
|
1208
|
-
value: date ?? ""
|
|
1487
|
+
value: rawDate ?? date ?? ""
|
|
1209
1488
|
}],
|
|
1210
1489
|
name: "date",
|
|
1211
1490
|
type: "mdxJsxTextElement"
|
|
@@ -1230,13 +1509,47 @@ const defaultRules = {
|
|
|
1230
1509
|
value: node.texExpression
|
|
1231
1510
|
})
|
|
1232
1511
|
},
|
|
1233
|
-
footnoteDefinition: {
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1512
|
+
footnoteDefinition: {
|
|
1513
|
+
deserialize: (mdastNode, deco, options) => {
|
|
1514
|
+
const paragraphType = getPluginType(options.editor, KEYS.p);
|
|
1515
|
+
const blocks = convertNodesDeserialize(mdastNode.children, deco, options).map((child) => child.type === paragraphType ? child : {
|
|
1516
|
+
children: [child],
|
|
1517
|
+
type: paragraphType
|
|
1518
|
+
});
|
|
1519
|
+
const identifier = mdastNode.identifier;
|
|
1520
|
+
if (blocks.length === 0) return {
|
|
1521
|
+
children: [{
|
|
1522
|
+
children: [{ text: "" }],
|
|
1523
|
+
type: paragraphType
|
|
1524
|
+
}],
|
|
1525
|
+
identifier,
|
|
1526
|
+
type: getPluginType(options.editor, "footnoteDefinition")
|
|
1527
|
+
};
|
|
1528
|
+
return {
|
|
1529
|
+
children: blocks,
|
|
1530
|
+
identifier,
|
|
1531
|
+
type: getPluginType(options.editor, "footnoteDefinition")
|
|
1532
|
+
};
|
|
1533
|
+
},
|
|
1534
|
+
serialize: (node, options) => ({
|
|
1535
|
+
children: convertNodesSerialize(node.children, options),
|
|
1536
|
+
identifier: node.identifier,
|
|
1537
|
+
type: "footnoteDefinition"
|
|
1538
|
+
})
|
|
1539
|
+
},
|
|
1540
|
+
footnoteReference: {
|
|
1541
|
+
deserialize: (mdastNode, _deco, options) => {
|
|
1542
|
+
return {
|
|
1543
|
+
children: [{ text: "" }],
|
|
1544
|
+
identifier: mdastNode.identifier ?? "",
|
|
1545
|
+
type: getPluginType(options.editor, "footnoteReference")
|
|
1546
|
+
};
|
|
1547
|
+
},
|
|
1548
|
+
serialize: (node) => ({
|
|
1549
|
+
identifier: node.identifier ?? node.children?.map((child) => child.text ?? "").join("") ?? "",
|
|
1550
|
+
type: "footnoteReference"
|
|
1551
|
+
})
|
|
1552
|
+
},
|
|
1240
1553
|
heading: {
|
|
1241
1554
|
deserialize: (mdastNode, deco, options) => {
|
|
1242
1555
|
const defaultType = {
|
|
@@ -1308,11 +1621,11 @@ const defaultRules = {
|
|
|
1308
1621
|
...rest
|
|
1309
1622
|
};
|
|
1310
1623
|
},
|
|
1311
|
-
serialize: ({ caption, url }) => {
|
|
1624
|
+
serialize: ({ caption, title, url }) => {
|
|
1312
1625
|
return {
|
|
1313
1626
|
children: [{
|
|
1314
1627
|
alt: caption ? caption.map((c) => c.text).join("") : void 0,
|
|
1315
|
-
title:
|
|
1628
|
+
title: typeof title === "string" ? title : void 0,
|
|
1316
1629
|
type: "image",
|
|
1317
1630
|
url
|
|
1318
1631
|
}],
|
|
@@ -1758,6 +2071,34 @@ const getDeserializerByKey = (key, options) => {
|
|
|
1758
2071
|
|
|
1759
2072
|
//#endregion
|
|
1760
2073
|
//#region src/lib/deserializer/utils/customMdxDeserialize.ts
|
|
2074
|
+
const MDX_ATTR_NAME_TO_HTML_ATTR = {
|
|
2075
|
+
className: "class",
|
|
2076
|
+
htmlFor: "for"
|
|
2077
|
+
};
|
|
2078
|
+
const serializeUnknownMdxChild = (child) => {
|
|
2079
|
+
if (child?.type === "mdxJsxTextElement" || child?.type === "mdxJsxFlowElement") return serializeUnknownMdxNode(child);
|
|
2080
|
+
if ("value" in (child ?? {})) return child.value ?? "";
|
|
2081
|
+
if (Array.isArray(child?.children)) return child.children.map(serializeUnknownMdxChild).join("");
|
|
2082
|
+
return "";
|
|
2083
|
+
};
|
|
2084
|
+
const serializeUnknownMdxAttributes = (attributes) => {
|
|
2085
|
+
if (!attributes?.length) return "";
|
|
2086
|
+
const serialized = attributes.map((attribute) => {
|
|
2087
|
+
const name = MDX_ATTR_NAME_TO_HTML_ATTR[attribute.name] ?? attribute.name;
|
|
2088
|
+
if (attribute.value === void 0 || attribute.value === null) return name;
|
|
2089
|
+
if (typeof attribute.value === "object" && attribute.value?.type === "mdxJsxAttributeValueExpression") return `${name}={${attribute.value.value}}`;
|
|
2090
|
+
return `${name}="${String(attribute.value)}"`;
|
|
2091
|
+
});
|
|
2092
|
+
return serialized.length > 0 ? ` ${serialized.join(" ")}` : "";
|
|
2093
|
+
};
|
|
2094
|
+
const serializeUnknownMdxNode = (mdastNode) => {
|
|
2095
|
+
const attrs = serializeUnknownMdxAttributes(mdastNode.attributes);
|
|
2096
|
+
const openTag = `<${mdastNode.name}${attrs}`;
|
|
2097
|
+
if (!mdastNode.children?.length) return `${openTag} />`;
|
|
2098
|
+
const inner = mdastNode.children.map(serializeUnknownMdxChild).join(mdastNode.type === "mdxJsxFlowElement" ? "\n" : "");
|
|
2099
|
+
if (mdastNode.type === "mdxJsxFlowElement") return `${openTag}>\n${inner}\n</${mdastNode.name}>`;
|
|
2100
|
+
return `${openTag}>${inner}</${mdastNode.name}>`;
|
|
2101
|
+
};
|
|
1761
2102
|
const customMdxDeserialize = (mdastNode, deco, options) => {
|
|
1762
2103
|
const customJsxElementKey = mdastNode.name;
|
|
1763
2104
|
const key = getPluginKey(options.editor, customJsxElementKey) ?? mdastNode.name;
|
|
@@ -1765,26 +2106,11 @@ const customMdxDeserialize = (mdastNode, deco, options) => {
|
|
|
1765
2106
|
const nodeParserDeserialize = getDeserializerByKey(mdastToPlate(options.editor, key), options);
|
|
1766
2107
|
if (nodeParserDeserialize) return nodeParserDeserialize(mdastNode, deco, options);
|
|
1767
2108
|
} else console.warn("This MDX node does not have a parser for deserialization", mdastNode);
|
|
1768
|
-
if (mdastNode.type === "mdxJsxTextElement") {
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
return "";
|
|
1774
|
-
}).join("");
|
|
1775
|
-
return [{ text: `<${tagName}>${textContent}</${tagName}>` }];
|
|
1776
|
-
}
|
|
1777
|
-
if (mdastNode.type === "mdxJsxFlowElement") {
|
|
1778
|
-
const tagName = mdastNode.name;
|
|
1779
|
-
return [{
|
|
1780
|
-
children: [
|
|
1781
|
-
{ text: `<${tagName}>\n` },
|
|
1782
|
-
...convertChildrenDeserialize(mdastNode.children, deco, options),
|
|
1783
|
-
{ text: `\n</${tagName}>` }
|
|
1784
|
-
],
|
|
1785
|
-
type: getPluginType(options.editor, KEYS.p)
|
|
1786
|
-
}];
|
|
1787
|
-
}
|
|
2109
|
+
if (mdastNode.type === "mdxJsxTextElement") return [{ text: serializeUnknownMdxNode(mdastNode) }];
|
|
2110
|
+
if (mdastNode.type === "mdxJsxFlowElement") return [{
|
|
2111
|
+
children: [{ text: serializeUnknownMdxNode(mdastNode) }],
|
|
2112
|
+
type: getPluginType(options.editor, KEYS.p)
|
|
2113
|
+
}];
|
|
1788
2114
|
};
|
|
1789
2115
|
|
|
1790
2116
|
//#endregion
|
|
@@ -2292,4 +2618,4 @@ const remarkMention = () => (tree) => {
|
|
|
2292
2618
|
};
|
|
2293
2619
|
|
|
2294
2620
|
//#endregion
|
|
2295
|
-
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, serializeMdWithSourceMap, splitIncompleteMdx, stripMarkdown, stripMarkdownBlocks, stripMarkdownInline, tagRemarkPlugin, unreachable, wrapWithBlockId };
|
|
2621
|
+
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, resolveSelectionByPath, serializeInlineMd, serializeMd, serializeMdWithSourceMap, splitIncompleteMdx, stripMarkdown, stripMarkdownBlocks, stripMarkdownInline, tagRemarkPlugin, unreachable, wrapWithBlockId };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lofcz/platejs-markdown",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "53.0.0",
|
|
4
4
|
"description": "Markdown serializer plugin for Plate",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -42,18 +42,21 @@
|
|
|
42
42
|
"remark-stringify": "^11.0.0",
|
|
43
43
|
"ts-essentials": "10.1.0",
|
|
44
44
|
"unified": "^11.0.5",
|
|
45
|
-
"unist-util-visit": "5.0.0"
|
|
45
|
+
"unist-util-visit": "5.0.0",
|
|
46
|
+
"@platejs/date": "npm:@lofcz/platejs-date@53.0.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/mdast": "^4.0.4",
|
|
49
50
|
"@types/unist": "^3.0.3",
|
|
51
|
+
"remark-emoji": "5.0.1",
|
|
50
52
|
"remark-gfm": "4.0.1",
|
|
51
53
|
"remark-math": "6.0.0",
|
|
52
54
|
"@plate/scripts": "1.0.0",
|
|
53
|
-
"platejs": "npm:@lofcz/platejs@
|
|
55
|
+
"@platejs/footnote": "npm:@lofcz/platejs-footnote@53.0.0",
|
|
56
|
+
"platejs": "npm:@lofcz/platejs@53.0.0"
|
|
54
57
|
},
|
|
55
58
|
"peerDependencies": {
|
|
56
|
-
"platejs": ">=
|
|
59
|
+
"platejs": ">=53.0.0",
|
|
57
60
|
"react": ">=18.0.0",
|
|
58
61
|
"react-dom": ">=18.0.0"
|
|
59
62
|
},
|