@lofcz/platejs-markdown 53.1.1 → 53.1.4

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 CHANGED
@@ -77,9 +77,60 @@ type ResolveSelectionByPathResult = {
77
77
  endLine: number;
78
78
  extractedMarkdown: string;
79
79
  markdown: string;
80
+ /**
81
+ * `false` when neither endpoint of the selection mapped to any segment
82
+ * (e.g. stale paths after a concurrent edit). The line numbers in this
83
+ * case are placeholders (1, 1) and the extracted markdown is empty.
84
+ * Callers must check this flag before using the result; the previous
85
+ * silent default produced invalid line-1 references.
86
+ */
87
+ resolved: boolean;
80
88
  segments: MarkdownSourceMapSegment[];
81
89
  startLine: number;
82
90
  };
91
+ /**
92
+ * Order two Slate selection points in document order.
93
+ *
94
+ * Slate selection points come in two flavours that previously got compared
95
+ * incompatibly:
96
+ *
97
+ * - *Element-level*: `path` ends at an element, `offset` is the boundary
98
+ * index between its children (0 = before the first child, N = after the
99
+ * N-th child).
100
+ * - *Leaf-level*: `path` descends into a text leaf, `offset` is a character
101
+ * index within that text.
102
+ *
103
+ * The previous implementation padded the shorter path with `0`s and then
104
+ * fell back to comparing offsets — which only works when both points are
105
+ * at the same depth. For mixed-depth points like `[3] offset 2`
106
+ * (≈ "before child 2 of [3]") vs `[3, 1] offset 5` (≈ "inside child 1 of
107
+ * [3]"), it returned the wrong order silently because the offsets are not
108
+ * commensurate.
109
+ *
110
+ * Fix: when the shorter point is element-level, expand it to the leaf-level
111
+ * coordinate space by treating its offset as the next path step ("before
112
+ * child `offset`"), then continue path comparison. Offsets are only
113
+ * compared when both points are at identical depth.
114
+ */
115
+ declare const comparePoints: (a: SelectionPoint, b: SelectionPoint) => number;
116
+ /**
117
+ * Find the segment covering the start of a selection at `path`.
118
+ *
119
+ * 1. Ancestor-or-self: segment whose path is a prefix of `path` (most specific wins)
120
+ * 2. First descendant: `path` is a prefix of segment's path (container selection)
121
+ */
122
+ declare const findStartSegment: (segments: MarkdownSourceMapSegment[], path: number[]) => MarkdownSourceMapSegment | null;
123
+ /**
124
+ * Find the segment covering the end of a selection at `path`.
125
+ *
126
+ * Same as findStartSegment but for descendant matches picks the segment
127
+ * with the LARGEST `endLine` (the container's deepest/latest child
128
+ * segment). The previous implementation returned the last segment by
129
+ * iteration order, which only happened to work because `allSegments` is
130
+ * sorted by path today — any caller passing an unsorted array would
131
+ * silently get the wrong range end.
132
+ */
133
+ declare const findEndSegment: (segments: MarkdownSourceMapSegment[], path: number[]) => MarkdownSourceMapSegment | null;
83
134
  declare const resolveSelectionByPath: (editor: SlateEditor, selection: SelectionRange, options?: Omit<SerializeMdOptions, "editor">) => ResolveSelectionByPathResult;
84
135
  //#endregion
85
136
  //#region src/lib/serializer/serializeInlineMd.d.ts
@@ -536,4 +587,4 @@ declare const tagRemarkPlugin: (pluginFn: any, tag: string) => {
536
587
  };
537
588
  declare const getRemarkPluginsWithoutMdx: (plugins: Plugin[]) => Plugin[];
538
589
  //#endregion
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 };
590
+ 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, comparePoints, convertChildrenDeserialize, convertNodesDeserialize, convertNodesSerialize, convertTextsDeserialize, convertTextsSerialize, customMdxDeserialize, defaultRules, deserializeInlineMd, deserializeMd, findEndSegment, findStartSegment, 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
@@ -115,25 +115,50 @@ const getCustomMark = (options) => {
115
115
  //#region src/lib/rules/utils/parseAttributes.ts
116
116
  function parseAttributes(attributes) {
117
117
  const props = {};
118
- if (attributes && attributes.length > 0) attributes.forEach((attr) => {
119
- if (attr.name && attr.value !== void 0) {
120
- let value = attr.value;
118
+ if (!attributes || attributes.length === 0) return props;
119
+ for (const attr of attributes) {
120
+ if (!attr?.name) continue;
121
+ const raw = attr.value;
122
+ if (raw === void 0) continue;
123
+ if (raw === null) {
124
+ props[attr.name] = true;
125
+ continue;
126
+ }
127
+ if (typeof raw === "object") {
128
+ props[attr.name] = raw;
129
+ continue;
130
+ }
131
+ if (typeof raw === "string") {
121
132
  try {
122
- value = JSON.parse(attr.value);
123
- } catch (_error) {
124
- value = attr.value;
133
+ props[attr.name] = JSON.parse(raw);
134
+ } catch {
135
+ props[attr.name] = raw;
125
136
  }
126
- props[attr.name] = value;
137
+ continue;
127
138
  }
128
- });
139
+ props[attr.name] = raw;
140
+ }
129
141
  return props;
130
142
  }
131
143
  function propsToAttributes(props) {
132
- return Object.entries(props).map(([name, value]) => ({
133
- name,
134
- type: "mdxJsxAttribute",
135
- value: typeof value === "string" || value?.type === "mdxJsxAttributeValueExpression" ? value : JSON.stringify(value)
136
- }));
144
+ const out = [];
145
+ for (const [name, value] of Object.entries(props)) {
146
+ if (value === void 0 || value === null) continue;
147
+ if (value === true) {
148
+ out.push({
149
+ name,
150
+ type: "mdxJsxAttribute",
151
+ value: null
152
+ });
153
+ continue;
154
+ }
155
+ out.push({
156
+ name,
157
+ type: "mdxJsxAttribute",
158
+ value: typeof value === "string" || value?.type === "mdxJsxAttributeValueExpression" ? value : JSON.stringify(value)
159
+ });
160
+ }
161
+ return out;
137
162
  }
138
163
 
139
164
  //#endregion
@@ -519,12 +544,14 @@ function listToMdastTree(nodes, options, isBlock = false) {
519
544
  };
520
545
  stackTop = indentStack.at(-1);
521
546
  }
547
+ const listItemParagraph = {
548
+ children: convertNodesSerialize(node.children, options),
549
+ type: "paragraph"
550
+ };
551
+ if (Array.isArray(node.__sourceMapPath)) listItemParagraph.__sourceMapSlatePath = node.__sourceMapPath;
522
552
  const listItem = {
523
553
  checked: null,
524
- children: [{
525
- children: convertNodesSerialize(node.children, options),
526
- type: "paragraph"
527
- }],
554
+ children: [listItemParagraph],
528
555
  spread: options.spread ?? false,
529
556
  type: "listItem"
530
557
  };
@@ -565,12 +592,14 @@ function processListWithBlockIds(nodes, options) {
565
592
  start: node.listStyleType === "decimal" ? i + 1 : void 0,
566
593
  type: "list"
567
594
  };
595
+ const singleParagraph = {
596
+ children: convertNodesSerialize(node.children, options),
597
+ type: "paragraph"
598
+ };
599
+ if (Array.isArray(node.__sourceMapPath)) singleParagraph.__sourceMapSlatePath = node.__sourceMapPath;
568
600
  const listItem = {
569
601
  checked: null,
570
- children: [{
571
- children: convertNodesSerialize(node.children, options),
572
- type: "paragraph"
573
- }],
602
+ children: [singleParagraph],
574
603
  spread: options.spread ?? false,
575
604
  type: "listItem"
576
605
  };
@@ -621,6 +650,23 @@ const convertNodesSerialize = (nodes, options, isBlock = false) => {
621
650
  }
622
651
  return mdastNodes;
623
652
  };
653
+ /**
654
+ * Mirror the Slate `__sourceMapPath` annotation onto the produced mdast node.
655
+ *
656
+ * This breadcrumb lets `attachDescendantSources` (in
657
+ * `serializeMdWithSourceMap`) match mdast children back to their originating
658
+ * Slate child by *path* instead of by array position. Without it, any custom
659
+ * MDX serializer that injects, drops, or reorders mdast children would
660
+ * silently shift the source-map zipper and assign each Slate child the line
661
+ * range of a different mdast node.
662
+ *
663
+ * The property is a plain own-property on the mdast object — invisible to
664
+ * `mdast-util-to-markdown`, which only consumes the documented mdast fields.
665
+ */
666
+ const tagWithSlatePath = (mdastNode, slateNode) => {
667
+ if (mdastNode && typeof mdastNode === "object" && Array.isArray(slateNode?.__sourceMapPath)) mdastNode.__sourceMapSlatePath = slateNode.__sourceMapPath;
668
+ return mdastNode;
669
+ };
624
670
  const buildMdastNode = (node, options, isBlock = false) => {
625
671
  const editor = options.editor;
626
672
  let key = getPluginKey(editor, node.type) ?? node.type;
@@ -629,8 +675,8 @@ const buildMdastNode = (node, options, isBlock = false) => {
629
675
  const nodeParser = getSerializerByKey(key, options);
630
676
  if (nodeParser) {
631
677
  const mdastNode = nodeParser(node, options);
632
- if (options.withBlockId && node.id && isBlock) return wrapWithBlockId(mdastNode, node.id);
633
- return mdastNode;
678
+ if (options.withBlockId && node.id && isBlock) return tagWithSlatePath(wrapWithBlockId(mdastNode, node.id), node);
679
+ return tagWithSlatePath(mdastNode, node);
634
680
  }
635
681
  unreachable(node);
636
682
  };
@@ -723,6 +769,9 @@ const slateNodeKind = (node, editor) => {
723
769
  const pType = getPluginType(editor, KEYS.p) ?? KEYS.p;
724
770
  if (node.type === pType) return "paragraph";
725
771
  if (SEGMENT_SKIP_TYPES.has(key)) return null;
772
+ try {
773
+ if (editor?.api?.isInline?.(node)) return null;
774
+ } catch {}
726
775
  if (Array.isArray(node.children)) return "block";
727
776
  return null;
728
777
  };
@@ -805,14 +854,23 @@ const attachTableCellSources = (mdastTable, slateTable, editor) => {
805
854
  * to zip them correctly.
806
855
  */
807
856
  const attachListSources = (mdastList, slateListItems, editor, insideMdx) => {
857
+ const slateByPath = /* @__PURE__ */ new Map();
858
+ for (const item of slateListItems) if (Array.isArray(item?.__sourceMapPath)) slateByPath.set(toPathKey(item.__sourceMapPath), item);
808
859
  let slateIdx = 0;
809
860
  const walkList = (list) => {
810
861
  if (!Array.isArray(list.children)) return;
811
862
  for (const listItem of list.children) {
812
863
  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++];
864
+ for (const child of listItem.children) {
865
+ if (child.type === "list") {
866
+ walkList(child);
867
+ continue;
868
+ }
869
+ const taggedPath = child.__sourceMapSlatePath;
870
+ let slateChild = null;
871
+ if (Array.isArray(taggedPath)) slateChild = slateByPath.get(toPathKey(taggedPath)) ?? null;
872
+ if (!slateChild && slateIdx < slateListItems.length) slateChild = slateListItems[slateIdx++];
873
+ if (!slateChild) continue;
816
874
  const source = buildSource(slateChild, editor);
817
875
  if (source) {
818
876
  if (insideMdx) source.containsMdx = true;
@@ -823,35 +881,78 @@ const attachListSources = (mdastList, slateListItems, editor, insideMdx) => {
823
881
  };
824
882
  walkList(mdastList);
825
883
  };
884
+ /**
885
+ * Count the number of Slate list-paragraph slots a mdast list tree needs.
886
+ *
887
+ * Each non-`list` child of a `listItem` corresponds to exactly one Slate
888
+ * list paragraph; nested `list` nodes recurse. This lets us consume exactly
889
+ * the right slice of consecutive Slate list paragraphs for a given mdast
890
+ * list, instead of greedily swallowing every list-styled Slate sibling.
891
+ */
892
+ const countMdastListSlots = (list) => {
893
+ if (!list || !Array.isArray(list.children)) return 0;
894
+ let count = 0;
895
+ for (const listItem of list.children) {
896
+ if (listItem.type !== "listItem" || !Array.isArray(listItem.children)) continue;
897
+ for (const child of listItem.children) if (child?.type === "list") count += countMdastListSlots(child);
898
+ else count += 1;
899
+ }
900
+ return count;
901
+ };
902
+ const pathsEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
826
903
  const attachDescendantSources = (mdastNode, slateNode, editor, insideMdx) => {
827
904
  if (!insideMdx && !MDX_MDAST_TYPES.has(mdastNode?.type ?? "")) return;
828
905
  const slateChildren = slateNode?.children;
829
906
  const mdastChildren = mdastNode?.children;
830
907
  if (!Array.isArray(slateChildren) || !Array.isArray(mdastChildren)) return;
831
908
  const slateElements = slateChildren.filter((c) => c?.type && Array.isArray(c.children));
832
- const mdastElements = mdastChildren.filter((c) => c.type && c.type !== "text");
909
+ const consumedSlateIndices = /* @__PURE__ */ new Set();
910
+ const attachSingleByMdast = (mdastChild, slateChild) => {
911
+ const isMdx = insideMdx || MDX_MDAST_TYPES.has(mdastChild.type ?? "");
912
+ const source = buildSource(slateChild, editor);
913
+ if (source) {
914
+ if (isMdx) source.containsMdx = true;
915
+ attachSource(mdastChild, source);
916
+ }
917
+ if (mdastChild.type === "table") attachTableCellSources(mdastChild, slateChild, editor);
918
+ else if (insideMdx || MDX_MDAST_TYPES.has(mdastChild.type ?? "")) attachDescendantSources(mdastChild, slateChild, editor, isMdx);
919
+ };
920
+ for (const mdastChild of mdastChildren) {
921
+ if (!mdastChild || !mdastChild.type || mdastChild.type === "text") continue;
922
+ const taggedPath = mdastChild.__sourceMapSlatePath;
923
+ if (!Array.isArray(taggedPath)) continue;
924
+ const slateIdx = slateElements.findIndex((c) => Array.isArray(c.__sourceMapPath) && pathsEqual(c.__sourceMapPath, taggedPath));
925
+ if (slateIdx === -1) continue;
926
+ consumedSlateIndices.add(slateIdx);
927
+ attachSingleByMdast(mdastChild, slateElements[slateIdx]);
928
+ }
833
929
  let si = 0;
834
- for (const mdastChild of mdastElements) {
930
+ const advanceToUnconsumed = () => {
931
+ while (si < slateElements.length && consumedSlateIndices.has(si)) si++;
932
+ };
933
+ for (const mdastChild of mdastChildren) {
934
+ if (!mdastChild || !mdastChild.type || mdastChild.type === "text") continue;
935
+ if (Array.isArray(mdastChild.__sourceMapSlatePath)) continue;
835
936
  if (mdastChild.type === "list") {
937
+ const slotsNeeded = countMdastListSlots(mdastChild);
836
938
  const listRun = [];
837
- while (si < slateElements.length && slateElements[si].listStyleType) {
939
+ for (let k = 0; k < slotsNeeded; k++) {
940
+ advanceToUnconsumed();
941
+ if (si >= slateElements.length) break;
942
+ if (!slateElements[si].listStyleType) break;
838
943
  listRun.push(slateElements[si]);
944
+ consumedSlateIndices.add(si);
839
945
  si++;
840
946
  }
841
947
  attachListSources(mdastChild, listRun, editor, insideMdx);
842
948
  continue;
843
949
  }
950
+ advanceToUnconsumed();
844
951
  if (si >= slateElements.length) break;
845
952
  const slateChild = slateElements[si];
953
+ consumedSlateIndices.add(si);
846
954
  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);
955
+ attachSingleByMdast(mdastChild, slateChild);
855
956
  }
856
957
  };
857
958
  const buildMdastNodeWithSource = (node, options, isBlock = false) => {
@@ -1048,7 +1149,7 @@ const serializeMdWithSourceMap = (editor, options) => {
1048
1149
  const startLine = info.now.line;
1049
1150
  const emitted = handler(node, parent, state, info);
1050
1151
  const source = node?.data?.sourceMap;
1051
- if (source && emitted.trim()) pushSegment(source, emitted, startLine, startLine + (emitted.split(/\r?\n|\r/g).length - 1));
1152
+ if (source && emitted.trim()) pushSegment(source, emitted, startLine, startLine + (emitted.replace(/(?:\r?\n|\r)+$/g, "").split(/\r?\n|\r/g).length - 1));
1052
1153
  if (type === "table" && Array.isArray(node?.children)) for (let r = 0; r < node.children.length; r++) {
1053
1154
  const line = r === 0 ? startLine : startLine + 1 + r;
1054
1155
  const cells = node.children[r]?.children;
@@ -1086,14 +1187,44 @@ const serializeMdWithSourceMap = (editor, options) => {
1086
1187
  const MDX_TAG_RE = /<[a-zA-Z_][\w.-]*/;
1087
1188
  /** `a` is an ancestor-or-self of `b`: a's path is a prefix of (or equal to) b's. */
1088
1189
  const isAncestorOrSelf = (a, b) => a.length <= b.length && a.every((v, i) => b[i] === v);
1190
+ /**
1191
+ * Order two Slate selection points in document order.
1192
+ *
1193
+ * Slate selection points come in two flavours that previously got compared
1194
+ * incompatibly:
1195
+ *
1196
+ * - *Element-level*: `path` ends at an element, `offset` is the boundary
1197
+ * index between its children (0 = before the first child, N = after the
1198
+ * N-th child).
1199
+ * - *Leaf-level*: `path` descends into a text leaf, `offset` is a character
1200
+ * index within that text.
1201
+ *
1202
+ * The previous implementation padded the shorter path with `0`s and then
1203
+ * fell back to comparing offsets — which only works when both points are
1204
+ * at the same depth. For mixed-depth points like `[3] offset 2`
1205
+ * (≈ "before child 2 of [3]") vs `[3, 1] offset 5` (≈ "inside child 1 of
1206
+ * [3]"), it returned the wrong order silently because the offsets are not
1207
+ * commensurate.
1208
+ *
1209
+ * Fix: when the shorter point is element-level, expand it to the leaf-level
1210
+ * coordinate space by treating its offset as the next path step ("before
1211
+ * child `offset`"), then continue path comparison. Offsets are only
1212
+ * compared when both points are at identical depth.
1213
+ */
1089
1214
  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;
1215
+ const aPath = a.path;
1216
+ const bPath = b.path;
1217
+ const aLen = aPath.length;
1218
+ const bLen = bPath.length;
1219
+ const common = Math.min(aLen, bLen);
1220
+ for (let i = 0; i < common; i++) if (aPath[i] !== bPath[i]) return aPath[i] - bPath[i];
1221
+ if (aLen === bLen) return a.offset - b.offset;
1222
+ if (aLen < bLen) {
1223
+ if (a.offset !== bPath[common]) return a.offset - bPath[common];
1224
+ return -1;
1095
1225
  }
1096
- return a.offset - b.offset;
1226
+ if (aPath[common] !== b.offset) return aPath[common] - b.offset;
1227
+ return 1;
1097
1228
  };
1098
1229
  /**
1099
1230
  * Find the segment covering the start of a selection at `path`.
@@ -1115,8 +1246,12 @@ const findStartSegment = (segments, path) => {
1115
1246
  /**
1116
1247
  * Find the segment covering the end of a selection at `path`.
1117
1248
  *
1118
- * Same as findStartSegment but for descendant matches picks the LAST one
1119
- * (the container's last child segment).
1249
+ * Same as findStartSegment but for descendant matches picks the segment
1250
+ * with the LARGEST `endLine` (the container's deepest/latest child
1251
+ * segment). The previous implementation returned the last segment by
1252
+ * iteration order, which only happened to work because `allSegments` is
1253
+ * sorted by path today — any caller passing an unsorted array would
1254
+ * silently get the wrong range end.
1120
1255
  */
1121
1256
  const findEndSegment = (segments, path) => {
1122
1257
  let best = null;
@@ -1126,9 +1261,12 @@ const findEndSegment = (segments, path) => {
1126
1261
  bestLen = seg.path.length;
1127
1262
  }
1128
1263
  if (best) return best;
1129
- let last = null;
1130
- for (const seg of segments) if (isAncestorOrSelf(path, seg.path)) last = seg;
1131
- return last;
1264
+ let latest = null;
1265
+ for (const seg of segments) {
1266
+ if (!isAncestorOrSelf(path, seg.path)) continue;
1267
+ if (!latest || seg.endLine > latest.endLine) latest = seg;
1268
+ }
1269
+ return latest;
1132
1270
  };
1133
1271
  /** Recursively compute the total text length of a Slate node. */
1134
1272
  const getTextLength = (node) => {
@@ -1228,13 +1366,14 @@ const tryContainerExpand = ({ allSegments, leafSeg, selPath, currentLine, direct
1228
1366
  const resolveSelectionByPath = (editor, selection, options) => {
1229
1367
  const { allSegments, markdown, segments } = serializeMdWithSourceMap(editor, options);
1230
1368
  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);
1369
+ const startSeg = findStartSegment(segments, start.path) ?? findStartSegment(allSegments, start.path);
1370
+ const endSeg = findEndSegment(segments, end.path) ?? findEndSegment(allSegments, end.path);
1233
1371
  if (!startSeg && !endSeg) return {
1234
1372
  containsMdx: false,
1235
1373
  endLine: 1,
1236
1374
  extractedMarkdown: "",
1237
1375
  markdown,
1376
+ resolved: false,
1238
1377
  segments,
1239
1378
  startLine: 1
1240
1379
  };
@@ -1269,6 +1408,7 @@ const resolveSelectionByPath = (editor, selection, options) => {
1269
1408
  endLine,
1270
1409
  extractedMarkdown,
1271
1410
  markdown,
1411
+ resolved: true,
1272
1412
  segments,
1273
1413
  startLine
1274
1414
  };
@@ -2618,4 +2758,4 @@ const remarkMention = () => (tree) => {
2618
2758
  };
2619
2759
 
2620
2760
  //#endregion
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 };
2761
+ export { MarkdownPlugin, REMARK_MDX_TAG, basicMarkdownMarks, buildMdastNode, buildRules, buildSlateNode, columnRules, comparePoints, convertChildrenDeserialize, convertNodesDeserialize, convertNodesSerialize, convertTextsDeserialize, convertTextsSerialize, customMdxDeserialize, defaultRules, deserializeInlineMd, deserializeMd, findEndSegment, findStartSegment, 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": "53.1.1",
3
+ "version": "53.1.4",
4
4
  "description": "Markdown serializer plugin for Plate",
5
5
  "keywords": [
6
6
  "markdown",
@@ -52,8 +52,8 @@
52
52
  "remark-gfm": "4.0.1",
53
53
  "remark-math": "6.0.0",
54
54
  "@plate/scripts": "1.0.0",
55
- "@platejs/footnote": "npm:@lofcz/platejs-footnote@53.0.0",
56
- "platejs": "npm:@lofcz/platejs@53.1.1"
55
+ "platejs": "npm:@lofcz/platejs@53.1.1",
56
+ "@platejs/footnote": "npm:@lofcz/platejs-footnote@53.0.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "platejs": ">=53.0.0",