From 41052635ad444be6e17b31788060944fc9aedde8 Mon Sep 17 00:00:00 2001 From: Gustav Ahlberg Date: Mon, 26 Oct 2026 18:09:33 +0100 Subject: [PATCH] fix(react-email): render block tokens in tight list items Parse every list item with marked's loose flag so tight text stays bare and block tokens still render. Co-authored-by: Cursor --- .changeset/tight-list-blocks.md | 5 ++ .../src/components/markdown/markdown.spec.tsx | 47 +++++++++++++++++-- .../src/components/markdown/markdown.tsx | 6 +-- 3 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 .changeset/tight-list-blocks.md diff --git a/.changeset/tight-list-blocks.md b/.changeset/tight-list-blocks.md new file mode 100644 index 0000000000..fa1506a710 --- /dev/null +++ b/.changeset/tight-list-blocks.md @@ -0,0 +1,5 @@ +--- +"react-email": patch +--- + +render block tokens in tight list items diff --git a/packages/react-email/src/components/markdown/markdown.spec.tsx b/packages/react-email/src/components/markdown/markdown.spec.tsx index 5c39487c73..9f39d0d976 100644 --- a/packages/react-email/src/components/markdown/markdown.spec.tsx +++ b/packages/react-email/src/components/markdown/markdown.spec.tsx @@ -202,14 +202,12 @@ console.log(\`Hello, $\{name}!\`); ); expect(actualOutput).toMatchInlineSnapshot(` "
    -
  • parent list item

    -
      +
    • parent list item
      • nested list item 1
      • nested list item 2
    • -
    • another parent item

      -
        +
      1. another parent item
        1. nested ordered item 1
        2. nested ordered item 2
        @@ -218,6 +216,47 @@ console.log(\`Hello, $\{name}!\`);
" `); }); + + it('renders a tight list followed by a heading and paragraph', async () => { + const actualOutput = await render( + {'- Hello\n ## Heading\n body\n'}, + ); + expect(actualOutput).toMatchInlineSnapshot(` + "
    +
  • Hello
  • +
+

Heading

body

+
" + `); + }); + + it('renders a heading that starts a tight list item', async () => { + const actualOutput = await render( + {'- ## Heading\n body\n'}, + ); + expect(actualOutput).toMatchInlineSnapshot(` + "
    +
  • Heading

  • +
+

body

+
" + `); + }); + + it('renders a tight list followed by a code block', async () => { + const actualOutput = await render( + {'- item\n ```\n code\n ```\n'}, + ); + expect(actualOutput).toMatchInlineSnapshot(` + "
    +
  • item
  • +
+
code
+      
+
" + `); + }); + it('applies th styles to table header cells and td styles to body cells', async () => { const actualOutput = await render( ( }; renderer.listitem = ({ tokens, loose }) => { - const hasNestedList = tokens.some((token) => token.type === 'list'); - const text = - loose || hasNestedList - ? renderer.parser.parse(tokens) - : renderer.parser.parseInline(tokens); + const text = renderer.parser.parse(tokens, !!loose); return `