fix(git-sync): preserve subpages.recursive and details.open on round trip
Found proactively by deepening the round-trip test from node-TYPE survival to ATTRIBUTE fidelity (distinctive attr values per node). Two real losses (the other 3 candidates — mathInline/mathBlock/pageEmbed — were verified to be correct; the probe had used wrong attr names): - subpages `recursive`: the converter emitted a bare div and the schema mirror didn't model the attr, so a recursive subpages reverted to non-recursive on a round trip. Now emits `data-recursive="true"` and the mirror parses it back (matching @docmost/editor-ext). - details `open`: the `open` (collapsed/expanded) state lives on the details node, but the converter emitted the `<details>` wrapper from the summary case without it, so the state was dropped. The wrapper now carries `open`. The round-trip test now also asserts attribute fidelity (12 cases) so these are locked. Schema-surface snapshot updated for the new subpages attr. git-sync vitest 671 (+1 expected-fail), §13.1 gate 27. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -928,6 +928,17 @@ const Subpages = Node.create({
|
||||
atom: true,
|
||||
defining: true,
|
||||
draggable: true,
|
||||
addAttributes() {
|
||||
return {
|
||||
recursive: {
|
||||
default: false,
|
||||
parseHTML: (el: HTMLElement) =>
|
||||
el.getAttribute("data-recursive") === "true",
|
||||
renderHTML: (attrs: Record<string, any>) =>
|
||||
attrs.recursive ? { "data-recursive": "true" } : {},
|
||||
},
|
||||
};
|
||||
},
|
||||
parseHTML() {
|
||||
return [{ tag: 'div[data-type="subpages"]' }];
|
||||
},
|
||||
|
||||
@@ -445,16 +445,20 @@ export function convertProseMirrorToMarkdown(content: any): string {
|
||||
const calloutContent = nodeContent.map(processNode).join("\n");
|
||||
return `:::${calloutType.toLowerCase()}\n${calloutContent}\n:::`;
|
||||
|
||||
case "details":
|
||||
return nodeContent.map(processNode).join("\n");
|
||||
case "details": {
|
||||
// The `open` (collapsed/expanded) state lives on the details node, NOT on
|
||||
// the summary, so emit the <details> wrapper HERE carrying it — otherwise
|
||||
// the open state is dropped on a round trip. The schema's details node
|
||||
// parses `open` back from the attribute.
|
||||
const open = node.attrs?.open ? " open" : "";
|
||||
return `<details${open}>\n${nodeContent.map(processNode).join("")}</details>`;
|
||||
}
|
||||
|
||||
case "detailsSummary":
|
||||
const summaryText = nodeContent.map(processNode).join("");
|
||||
return `<details>\n<summary>${summaryText}</summary>\n`;
|
||||
return `<summary>${nodeContent.map(processNode).join("")}</summary>\n\n`;
|
||||
|
||||
case "detailsContent":
|
||||
const detailsText = nodeContent.map(processNode).join("\n");
|
||||
return `${detailsText}\n</details>`;
|
||||
return `${nodeContent.map(processNode).join("\n")}\n`;
|
||||
|
||||
case "mathInline": {
|
||||
// The schema's `text` attribute has no parseHTML, so TipTap's default
|
||||
@@ -648,13 +652,16 @@ export function convertProseMirrorToMarkdown(content: any): string {
|
||||
// (the divider silently disappeared and could not round-trip).
|
||||
return `<div data-type="pageBreak"></div>`;
|
||||
|
||||
case "subpages":
|
||||
case "subpages": {
|
||||
// Emit the schema-matching div[data-type="subpages"] so marked passes it
|
||||
// through as a block and generateJSON rebuilds the subpages atom. The old
|
||||
// `{{SUBPAGES}}` literal had no parseHTML inverse, so on import it stayed
|
||||
// as plain text — the embed rendered as the literal "{{SUBPAGES}}" on the
|
||||
// page after a round-trip (red-team: subpages round-trip data loss).
|
||||
return `<div data-type="subpages"></div>`;
|
||||
// `data-recursive` carries the recursive toggle so it round-trips too.
|
||||
const recursive = node.attrs?.recursive ? ` data-recursive="true"` : "";
|
||||
return `<div data-type="subpages"${recursive}></div>`;
|
||||
}
|
||||
|
||||
case "status": {
|
||||
// Inline status pill. The schema reads the label from the element's
|
||||
|
||||
Reference in New Issue
Block a user