From 79c39daa91fd6bb0a4aae1e0105ce8260f85082e Mon Sep 17 00:00:00 2001 From: Chris Arter Date: Fri, 8 May 2026 13:36:02 -0400 Subject: [PATCH] fix(import): flatten paragraph wrappers in list items, sequential value Lexical 0.41 rejects listitem nodes that contain a paragraph child. Mdast wraps each list item's content as [paragraph, ...]; we now extract the inline children into the listitem directly. Also assign sequential 'value' per item rather than hardcoded 1. --- scripts/import/markdown-to-lexical.ts | 39 +++++++++++++++-------- tests/int/markdown-to-lexical.int.spec.ts | 17 ++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/scripts/import/markdown-to-lexical.ts b/scripts/import/markdown-to-lexical.ts index 4bfadd0..4c34198 100644 --- a/scripts/import/markdown-to-lexical.ts +++ b/scripts/import/markdown-to-lexical.ts @@ -54,24 +54,37 @@ async function convertMdast(node: Content, ctx: Ctx): Promise { return heading(`h${node.depth}`, text) } if (node.type === 'list') { + const items: LexNode[] = [] + for (let idx = 0; idx < node.children.length; idx++) { + const li = node.children[idx] + const liChildren: LexNode[] = [] + for (const c of li.children) { + if (c.type === 'paragraph') { + const inline = await Promise.all( + (c as Paragraph).children.map((ic) => convertInline(ic as Content, ctx)), + ) + liChildren.push(...(inline.flat().filter(Boolean) as LexNode[])) + } else { + const out = await convertMdast(c as Content, ctx) + if (out) liChildren.push(out) + } + } + items.push({ + type: 'listitem', + value: idx + 1, + children: liChildren, + direction: 'ltr', + format: '', + indent: 0, + version: 1, + }) + } return { type: 'list', listType: node.ordered ? 'number' : 'bullet', tag: node.ordered ? 'ol' : 'ul', start: node.start ?? 1, - children: await Promise.all( - node.children.map(async (li) => ({ - type: 'listitem', - value: 1, - children: ( - await Promise.all(li.children.map((c) => convertMdast(c as Content, ctx))) - ).filter(Boolean), - direction: 'ltr', - format: '', - indent: 0, - version: 1, - })), - ), + children: items, direction: 'ltr', format: '', indent: 0, diff --git a/tests/int/markdown-to-lexical.int.spec.ts b/tests/int/markdown-to-lexical.int.spec.ts index ac09ab4..203fbd3 100644 --- a/tests/int/markdown-to-lexical.int.spec.ts +++ b/tests/int/markdown-to-lexical.int.spec.ts @@ -42,4 +42,21 @@ describe('markdownToLexical', () => { expect(r.root.children[0].fields.title).toBe('T') expect(r.root.children[0].fields.content.root.children[0].type).toBe('paragraph') }) + + it('flattens paragraph wrappers inside list items and assigns sequential values', async () => { + const md = '- one\n- two\n- three' + const r = await markdownToLexical(md, stubCtx) + const list = r.root.children[0] as { type: string; children: { type: string; value: number; children: { type: string; text: string }[] }[] } + expect(list.type).toBe('list') + expect(list.children).toHaveLength(3) + expect(list.children.map((c) => c.value)).toEqual([1, 2, 3]) + // Each listitem's children must be inline (no nested paragraph) + for (const li of list.children) { + expect(li.type).toBe('listitem') + for (const c of li.children) { + expect(c.type).not.toBe('paragraph') + } + } + expect(list.children[0].children[0].text).toBe('one') + }) }) -- 2.51.2