@lofcz/platejs-markdown 52.3.8 → 52.3.9
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 +309 -49
- package/package.json +3 -3
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,9 @@
|
|
|
1
1
|
import { ElementApi, KEYS, TextApi, bindFirst, createTSlatePlugin, getPluginKey, getPluginType, isUrl } from "platejs";
|
|
2
2
|
import "mdast-util-mdx";
|
|
3
3
|
import kebabCase from "lodash/kebabCase.js";
|
|
4
|
-
import remarkStringify from "remark-stringify";
|
|
5
|
-
import { unified } from "unified";
|
|
6
4
|
import { defaultHandlers, toMarkdown } from "mdast-util-to-markdown";
|
|
5
|
+
import { unified } from "unified";
|
|
6
|
+
import remarkStringify from "remark-stringify";
|
|
7
7
|
import remarkParse from "remark-parse";
|
|
8
8
|
import { marked } from "marked";
|
|
9
9
|
import baseRemarkMdx from "remark-mdx";
|
|
@@ -655,45 +655,6 @@ const shouldIncludeNode$2 = (node, options) => {
|
|
|
655
655
|
return true;
|
|
656
656
|
};
|
|
657
657
|
|
|
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
658
|
//#endregion
|
|
698
659
|
//#region src/lib/serializer/serializeMdWithSourceMap.ts
|
|
699
660
|
const collectSlateText = (node) => {
|
|
@@ -770,7 +731,6 @@ const buildSource = (node, editor) => {
|
|
|
770
731
|
const path = Array.isArray(node.__sourceMapPath) ? node.__sourceMapPath : null;
|
|
771
732
|
if (!path) return null;
|
|
772
733
|
const text = collectSlateText(node);
|
|
773
|
-
if (!text.trim() && kind !== "media" && kind !== "block") return null;
|
|
774
734
|
return {
|
|
775
735
|
containsMdx: false,
|
|
776
736
|
kind,
|
|
@@ -826,6 +786,72 @@ const attachTableCellSources = (mdastTable, slateTable, editor) => {
|
|
|
826
786
|
}
|
|
827
787
|
}
|
|
828
788
|
};
|
|
789
|
+
/**
|
|
790
|
+
* Custom serializers (e.g. MDX components) build their own mdast subtree,
|
|
791
|
+
* bypassing convertNodesWithSource. Walk Slate and mdast element children
|
|
792
|
+
* in parallel (by position) and attach sourceMap data so the handler
|
|
793
|
+
* wrappers capture per-child segments during serialization.
|
|
794
|
+
*
|
|
795
|
+
* Position-based matching works because custom serializers preserve child
|
|
796
|
+
* order. We filter to element children only (skip text/leaf nodes) in both
|
|
797
|
+
* trees before zipping.
|
|
798
|
+
*/
|
|
799
|
+
/**
|
|
800
|
+
* Walk the mdast list tree and attach sources from the flat Slate list
|
|
801
|
+
* paragraphs. Slate uses flat paragraphs with `listStyleType` while mdast
|
|
802
|
+
* nests them as list > listItem > paragraph. We walk both in document order
|
|
803
|
+
* to zip them correctly.
|
|
804
|
+
*/
|
|
805
|
+
const attachListSources = (mdastList, slateListItems, editor, insideMdx) => {
|
|
806
|
+
let slateIdx = 0;
|
|
807
|
+
const walkList = (list) => {
|
|
808
|
+
if (!Array.isArray(list.children)) return;
|
|
809
|
+
for (const listItem of list.children) {
|
|
810
|
+
if (listItem.type !== "listItem" || !Array.isArray(listItem.children)) continue;
|
|
811
|
+
for (const child of listItem.children) if (child.type === "list") walkList(child);
|
|
812
|
+
else if (slateIdx < slateListItems.length) {
|
|
813
|
+
const slateChild = slateListItems[slateIdx++];
|
|
814
|
+
const source = buildSource(slateChild, editor);
|
|
815
|
+
if (source) {
|
|
816
|
+
if (insideMdx) source.containsMdx = true;
|
|
817
|
+
attachSource(child, source);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
walkList(mdastList);
|
|
823
|
+
};
|
|
824
|
+
const attachDescendantSources = (mdastNode, slateNode, editor, insideMdx) => {
|
|
825
|
+
if (!insideMdx && !MDX_MDAST_TYPES.has(mdastNode?.type ?? "")) return;
|
|
826
|
+
const slateChildren = slateNode?.children;
|
|
827
|
+
const mdastChildren = mdastNode?.children;
|
|
828
|
+
if (!Array.isArray(slateChildren) || !Array.isArray(mdastChildren)) return;
|
|
829
|
+
const slateElements = slateChildren.filter((c) => c?.type && Array.isArray(c.children));
|
|
830
|
+
const mdastElements = mdastChildren.filter((c) => c.type && c.type !== "text");
|
|
831
|
+
let si = 0;
|
|
832
|
+
for (const mdastChild of mdastElements) {
|
|
833
|
+
if (mdastChild.type === "list") {
|
|
834
|
+
const listRun = [];
|
|
835
|
+
while (si < slateElements.length && slateElements[si].listStyleType) {
|
|
836
|
+
listRun.push(slateElements[si]);
|
|
837
|
+
si++;
|
|
838
|
+
}
|
|
839
|
+
attachListSources(mdastChild, listRun, editor, insideMdx);
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
if (si >= slateElements.length) break;
|
|
843
|
+
const slateChild = slateElements[si];
|
|
844
|
+
si++;
|
|
845
|
+
const isMdx = insideMdx || MDX_MDAST_TYPES.has(mdastChild.type ?? "");
|
|
846
|
+
const source = buildSource(slateChild, editor);
|
|
847
|
+
if (source) {
|
|
848
|
+
if (isMdx) source.containsMdx = true;
|
|
849
|
+
attachSource(mdastChild, source);
|
|
850
|
+
}
|
|
851
|
+
if (mdastChild.type === "table") attachTableCellSources(mdastChild, slateChild, editor);
|
|
852
|
+
else if (insideMdx || MDX_MDAST_TYPES.has(mdastChild.type ?? "")) attachDescendantSources(mdastChild, slateChild, editor, isMdx);
|
|
853
|
+
}
|
|
854
|
+
};
|
|
829
855
|
const buildMdastNodeWithSource = (node, options, isBlock = false) => {
|
|
830
856
|
let mdastNode = buildMdastNode(node, options, isBlock);
|
|
831
857
|
if (!mdastNode) {
|
|
@@ -847,19 +873,20 @@ const buildMdastNodeWithSource = (node, options, isBlock = false) => {
|
|
|
847
873
|
return mdastNode;
|
|
848
874
|
}
|
|
849
875
|
const source = buildSource(node, options.editor);
|
|
876
|
+
const isMdxOutput = MDX_MDAST_TYPES.has(mdastNode?.type ?? "");
|
|
850
877
|
if (!source) {
|
|
851
|
-
if (
|
|
878
|
+
if (isMdxOutput) propagateMdxFlag(mdastNode);
|
|
879
|
+
attachDescendantSources(mdastNode, node, options.editor, isMdxOutput);
|
|
852
880
|
return mdastNode;
|
|
853
881
|
}
|
|
854
|
-
if (
|
|
855
|
-
source.containsMdx = true;
|
|
856
|
-
if (Array.isArray(mdastNode?.children)) for (const child of mdastNode.children) propagateMdxFlag(child);
|
|
857
|
-
}
|
|
882
|
+
if (isMdxOutput) source.containsMdx = true;
|
|
858
883
|
if (source.kind === "blockquote" && Array.isArray(mdastNode?.children) && mdastNode.children[0]) {
|
|
859
884
|
attachSource(mdastNode.children[0], source);
|
|
860
885
|
return mdastNode;
|
|
861
886
|
}
|
|
862
|
-
|
|
887
|
+
attachSource(mdastNode, source);
|
|
888
|
+
attachDescendantSources(mdastNode, node, options.editor, isMdxOutput);
|
|
889
|
+
return mdastNode;
|
|
863
890
|
};
|
|
864
891
|
const convertNodesWithSource = (nodes, options, isBlock = false) => {
|
|
865
892
|
const mdastNodes = [];
|
|
@@ -1046,11 +1073,244 @@ const serializeMdWithSourceMap = (editor, options) => {
|
|
|
1046
1073
|
}
|
|
1047
1074
|
const ordered = Array.from(deduped.values()).sort((a, b) => comparePaths(a.path, b.path));
|
|
1048
1075
|
return {
|
|
1076
|
+
allSegments: ordered,
|
|
1049
1077
|
markdown,
|
|
1050
1078
|
segments: ordered.filter((seg) => !ordered.some((other) => other !== seg && isPathPrefix(seg.path, other.path)))
|
|
1051
1079
|
};
|
|
1052
1080
|
};
|
|
1053
1081
|
|
|
1082
|
+
//#endregion
|
|
1083
|
+
//#region src/lib/serializer/resolveSelectionLines.ts
|
|
1084
|
+
const MDX_TAG_RE = /<[a-zA-Z_][\w.-]*/;
|
|
1085
|
+
/** `a` is an ancestor-or-self of `b`: a's path is a prefix of (or equal to) b's. */
|
|
1086
|
+
const isAncestorOrSelf = (a, b) => a.length <= b.length && a.every((v, i) => b[i] === v);
|
|
1087
|
+
const comparePoints = (a, b) => {
|
|
1088
|
+
const len = Math.max(a.path.length, b.path.length);
|
|
1089
|
+
for (let i = 0; i < len; i++) {
|
|
1090
|
+
const av = a.path[i] ?? 0;
|
|
1091
|
+
const bv = b.path[i] ?? 0;
|
|
1092
|
+
if (av !== bv) return av - bv;
|
|
1093
|
+
}
|
|
1094
|
+
return a.offset - b.offset;
|
|
1095
|
+
};
|
|
1096
|
+
/**
|
|
1097
|
+
* Find the segment covering the start of a selection at `path`.
|
|
1098
|
+
*
|
|
1099
|
+
* 1. Ancestor-or-self: segment whose path is a prefix of `path` (most specific wins)
|
|
1100
|
+
* 2. First descendant: `path` is a prefix of segment's path (container selection)
|
|
1101
|
+
*/
|
|
1102
|
+
const findStartSegment = (segments, path) => {
|
|
1103
|
+
let best = null;
|
|
1104
|
+
let bestLen = -1;
|
|
1105
|
+
for (const seg of segments) if (isAncestorOrSelf(seg.path, path) && seg.path.length > bestLen) {
|
|
1106
|
+
best = seg;
|
|
1107
|
+
bestLen = seg.path.length;
|
|
1108
|
+
}
|
|
1109
|
+
if (best) return best;
|
|
1110
|
+
for (const seg of segments) if (isAncestorOrSelf(path, seg.path)) return seg;
|
|
1111
|
+
return null;
|
|
1112
|
+
};
|
|
1113
|
+
/**
|
|
1114
|
+
* Find the segment covering the end of a selection at `path`.
|
|
1115
|
+
*
|
|
1116
|
+
* Same as findStartSegment but for descendant matches picks the LAST one
|
|
1117
|
+
* (the container's last child segment).
|
|
1118
|
+
*/
|
|
1119
|
+
const findEndSegment = (segments, path) => {
|
|
1120
|
+
let best = null;
|
|
1121
|
+
let bestLen = -1;
|
|
1122
|
+
for (const seg of segments) if (isAncestorOrSelf(seg.path, path) && seg.path.length > bestLen) {
|
|
1123
|
+
best = seg;
|
|
1124
|
+
bestLen = seg.path.length;
|
|
1125
|
+
}
|
|
1126
|
+
if (best) return best;
|
|
1127
|
+
let last = null;
|
|
1128
|
+
for (const seg of segments) if (isAncestorOrSelf(path, seg.path)) last = seg;
|
|
1129
|
+
return last;
|
|
1130
|
+
};
|
|
1131
|
+
/** Recursively compute the total text length of a Slate node. */
|
|
1132
|
+
const getTextLength = (node) => {
|
|
1133
|
+
if (typeof node?.text === "string") return node.text.length;
|
|
1134
|
+
if (Array.isArray(node?.children)) return node.children.reduce((sum, child) => sum + getTextLength(child), 0);
|
|
1135
|
+
return 0;
|
|
1136
|
+
};
|
|
1137
|
+
/** Walk the editor tree to reach the node at `path`. */
|
|
1138
|
+
const getNodeAtPath = (editor, path) => {
|
|
1139
|
+
let node = { children: editor.children };
|
|
1140
|
+
for (const idx of path) {
|
|
1141
|
+
if (!node?.children?.[idx]) return null;
|
|
1142
|
+
node = node.children[idx];
|
|
1143
|
+
}
|
|
1144
|
+
return node;
|
|
1145
|
+
};
|
|
1146
|
+
/**
|
|
1147
|
+
* Compute the absolute character offset within a segment's text.
|
|
1148
|
+
*
|
|
1149
|
+
* Given a segment at `segPath` (e.g. [1,3,0] = a paragraph) and a selection
|
|
1150
|
+
* point at `selPath` (e.g. [1,3,0,1] = second text node) with `selOffset`,
|
|
1151
|
+
* sum up text lengths of all Slate nodes preceding the selection point within
|
|
1152
|
+
* the segment, then add `selOffset`.
|
|
1153
|
+
*/
|
|
1154
|
+
const computeAbsoluteOffset = (editor, segPath, selPath, selOffset) => {
|
|
1155
|
+
const segNode = getNodeAtPath(editor, segPath);
|
|
1156
|
+
if (!segNode) return null;
|
|
1157
|
+
const remainingPath = selPath.slice(segPath.length);
|
|
1158
|
+
if (remainingPath.length === 0) {
|
|
1159
|
+
let textOffset = 0;
|
|
1160
|
+
const children = segNode.children ?? [];
|
|
1161
|
+
for (let i = 0; i < Math.min(selOffset, children.length); i++) textOffset += getTextLength(children[i]);
|
|
1162
|
+
return textOffset;
|
|
1163
|
+
}
|
|
1164
|
+
let current = segNode;
|
|
1165
|
+
let accumulated = 0;
|
|
1166
|
+
for (const childIdx of remainingPath) {
|
|
1167
|
+
const children = current.children ?? [];
|
|
1168
|
+
for (let i = 0; i < childIdx && i < children.length; i++) accumulated += getTextLength(children[i]);
|
|
1169
|
+
if (!children[childIdx]) return null;
|
|
1170
|
+
current = children[childIdx];
|
|
1171
|
+
}
|
|
1172
|
+
return accumulated + selOffset;
|
|
1173
|
+
};
|
|
1174
|
+
/**
|
|
1175
|
+
* Given a segment and an absolute text offset, determine which line within
|
|
1176
|
+
* the segment the offset falls on.
|
|
1177
|
+
*/
|
|
1178
|
+
const offsetToLine = (segText, segStartLine, absOffset) => {
|
|
1179
|
+
const clamped = Math.max(0, Math.min(absOffset, segText.length));
|
|
1180
|
+
return segStartLine + (segText.slice(0, clamped).match(/\n/g) ?? []).length;
|
|
1181
|
+
};
|
|
1182
|
+
/**
|
|
1183
|
+
* Whether a segment supports sub-line narrowing. Only for kinds where text
|
|
1184
|
+
* content lines map 1:1 to markdown output lines (no structural wrappers
|
|
1185
|
+
* like code fences, MDX tags, or table delimiters).
|
|
1186
|
+
*/
|
|
1187
|
+
const NARROWABLE_KINDS = new Set([
|
|
1188
|
+
"paragraph",
|
|
1189
|
+
"heading",
|
|
1190
|
+
"list_item",
|
|
1191
|
+
"blockquote"
|
|
1192
|
+
]);
|
|
1193
|
+
const canNarrow = (seg) => NARROWABLE_KINDS.has(seg.kind);
|
|
1194
|
+
const extractLines = (md, start, end) => md.split("\n").slice(start - 1, end).join("\n");
|
|
1195
|
+
/**
|
|
1196
|
+
* Resolve which markdown lines correspond to a Plate editor selection
|
|
1197
|
+
* using the source-map path mapping (no text matching).
|
|
1198
|
+
*
|
|
1199
|
+
* 1. Serializes editor to markdown with source-map segments
|
|
1200
|
+
* 2. Finds the segment matching the anchor path (longest prefix)
|
|
1201
|
+
* 3. Finds the segment matching the focus path
|
|
1202
|
+
* 4. Narrows to sub-line precision using selection offsets
|
|
1203
|
+
* 5. Returns the line range and extracted markdown
|
|
1204
|
+
*/
|
|
1205
|
+
/**
|
|
1206
|
+
* When the selection lands on a container path (e.g. clicking an MDX label),
|
|
1207
|
+
* look up the container segment in `allSegments` to include the MDX wrapper
|
|
1208
|
+
* lines (opening/closing tags) rather than just the inner content.
|
|
1209
|
+
*/
|
|
1210
|
+
const tryContainerExpand = ({ allSegments, leafSeg, selPath, currentLine, direction }) => {
|
|
1211
|
+
if (!leafSeg) return currentLine;
|
|
1212
|
+
if (selPath.length >= leafSeg.path.length) return currentLine;
|
|
1213
|
+
let containerSeg = null;
|
|
1214
|
+
let containerLen = -1;
|
|
1215
|
+
for (const seg of allSegments) if (isAncestorOrSelf(seg.path, selPath) && seg.path.length > containerLen && seg !== leafSeg) {
|
|
1216
|
+
containerSeg = seg;
|
|
1217
|
+
containerLen = seg.path.length;
|
|
1218
|
+
}
|
|
1219
|
+
for (const seg of allSegments) if (seg.path.length === selPath.length && seg.path.every((v, i) => selPath[i] === v)) {
|
|
1220
|
+
containerSeg = seg;
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
if (!containerSeg) return currentLine;
|
|
1224
|
+
return direction === "start" ? Math.min(currentLine, containerSeg.startLine) : Math.max(currentLine, containerSeg.endLine);
|
|
1225
|
+
};
|
|
1226
|
+
const resolveSelectionByPath = (editor, selection, options) => {
|
|
1227
|
+
const { allSegments, markdown, segments } = serializeMdWithSourceMap(editor, options);
|
|
1228
|
+
const [start, end] = comparePoints(selection.anchor, selection.focus) <= 0 ? [selection.anchor, selection.focus] : [selection.focus, selection.anchor];
|
|
1229
|
+
const startSeg = findStartSegment(segments, start.path);
|
|
1230
|
+
const endSeg = findEndSegment(segments, end.path);
|
|
1231
|
+
if (!startSeg && !endSeg) return {
|
|
1232
|
+
containsMdx: false,
|
|
1233
|
+
endLine: 1,
|
|
1234
|
+
extractedMarkdown: "",
|
|
1235
|
+
markdown,
|
|
1236
|
+
segments,
|
|
1237
|
+
startLine: 1
|
|
1238
|
+
};
|
|
1239
|
+
let startLine = startSeg?.startLine ?? endSeg.startLine;
|
|
1240
|
+
let endLine = endSeg?.endLine ?? startSeg.endLine;
|
|
1241
|
+
if (startSeg && startSeg.endLine > startSeg.startLine && canNarrow(startSeg)) {
|
|
1242
|
+
const absOffset = computeAbsoluteOffset(editor, startSeg.path, start.path, start.offset);
|
|
1243
|
+
if (absOffset !== null) startLine = offsetToLine(startSeg.text, startSeg.startLine, absOffset);
|
|
1244
|
+
}
|
|
1245
|
+
if (endSeg && endSeg.endLine > endSeg.startLine && canNarrow(endSeg)) {
|
|
1246
|
+
const absOffset = computeAbsoluteOffset(editor, endSeg.path, end.path, end.offset);
|
|
1247
|
+
if (absOffset !== null) endLine = offsetToLine(endSeg.text, endSeg.startLine, absOffset);
|
|
1248
|
+
}
|
|
1249
|
+
startLine = tryContainerExpand({
|
|
1250
|
+
allSegments,
|
|
1251
|
+
currentLine: startLine,
|
|
1252
|
+
direction: "start",
|
|
1253
|
+
leafSeg: startSeg,
|
|
1254
|
+
selPath: start.path
|
|
1255
|
+
});
|
|
1256
|
+
endLine = tryContainerExpand({
|
|
1257
|
+
allSegments,
|
|
1258
|
+
currentLine: endLine,
|
|
1259
|
+
direction: "end",
|
|
1260
|
+
leafSeg: endSeg,
|
|
1261
|
+
selPath: end.path
|
|
1262
|
+
});
|
|
1263
|
+
if (startLine > endLine) [startLine, endLine] = [endLine, startLine];
|
|
1264
|
+
const extractedMarkdown = extractLines(markdown, startLine, endLine);
|
|
1265
|
+
return {
|
|
1266
|
+
containsMdx: extractedMarkdown.includes("<") && MDX_TAG_RE.test(extractedMarkdown),
|
|
1267
|
+
endLine,
|
|
1268
|
+
extractedMarkdown,
|
|
1269
|
+
markdown,
|
|
1270
|
+
segments,
|
|
1271
|
+
startLine
|
|
1272
|
+
};
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
//#endregion
|
|
1276
|
+
//#region src/lib/serializer/serializeInlineMd.ts
|
|
1277
|
+
const serializeInlineMd = (editor, options) => {
|
|
1278
|
+
const mergedOptions = getMergedOptionsSerialize(editor, options);
|
|
1279
|
+
const toRemarkProcessor = unified().use(mergedOptions.remarkPlugins ?? []).use(remarkStringify, {
|
|
1280
|
+
emphasis: "_",
|
|
1281
|
+
...mergedOptions?.remarkStringifyOptions
|
|
1282
|
+
});
|
|
1283
|
+
if (options?.value?.length === 0) return "";
|
|
1284
|
+
const convertedTexts = convertTextsSerialize(mergedOptions.value, { editor });
|
|
1285
|
+
return toRemarkProcessor.stringify({
|
|
1286
|
+
children: convertedTexts,
|
|
1287
|
+
type: "root"
|
|
1288
|
+
});
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
//#endregion
|
|
1292
|
+
//#region src/lib/serializer/serializeMd.ts
|
|
1293
|
+
/** Serialize the editor value to Markdown. */
|
|
1294
|
+
const serializeMd = (editor, options) => {
|
|
1295
|
+
const mergedOptions = getMergedOptionsSerialize(editor, options);
|
|
1296
|
+
const { remarkPlugins, value } = mergedOptions;
|
|
1297
|
+
const toRemarkProcessor = unified().use(remarkPlugins ?? []).use(remarkStringify, {
|
|
1298
|
+
emphasis: "_",
|
|
1299
|
+
...mergedOptions?.remarkStringifyOptions
|
|
1300
|
+
});
|
|
1301
|
+
const mdast = slateToMdast({
|
|
1302
|
+
children: value,
|
|
1303
|
+
options: mergedOptions
|
|
1304
|
+
});
|
|
1305
|
+
return toRemarkProcessor.stringify(mdast);
|
|
1306
|
+
};
|
|
1307
|
+
const slateToMdast = ({ children, options }) => {
|
|
1308
|
+
return {
|
|
1309
|
+
children: convertNodesSerialize(children, options, true),
|
|
1310
|
+
type: "root"
|
|
1311
|
+
};
|
|
1312
|
+
};
|
|
1313
|
+
|
|
1054
1314
|
//#endregion
|
|
1055
1315
|
//#region src/lib/rules/defaultRules.ts
|
|
1056
1316
|
const LEADING_NEWLINE_REGEX = /^\n/;
|
|
@@ -2292,4 +2552,4 @@ const remarkMention = () => (tree) => {
|
|
|
2292
2552
|
};
|
|
2293
2553
|
|
|
2294
2554
|
//#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 };
|
|
2555
|
+
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": "52.3.
|
|
3
|
+
"version": "52.3.9",
|
|
4
4
|
"description": "Markdown serializer plugin for Plate",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"@types/unist": "^3.0.3",
|
|
50
50
|
"remark-gfm": "4.0.1",
|
|
51
51
|
"remark-math": "6.0.0",
|
|
52
|
-
"
|
|
53
|
-
"
|
|
52
|
+
"platejs": "npm:@lofcz/platejs@52.3.6",
|
|
53
|
+
"@plate/scripts": "1.0.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"platejs": ">=52.0.11",
|