diff --git a/packages/app/src/components/markdown/html.ts b/packages/app/src/components/markdown/html.ts index bdc054e5bc..b3bb121e54 100644 --- a/packages/app/src/components/markdown/html.ts +++ b/packages/app/src/components/markdown/html.ts @@ -98,9 +98,9 @@ export function normalizeMarkdownHtml(source: string): string { if (EMPHASIS[name]) return EMPHASIS[name]; if (name === "br") { if (closing) return ""; - // A Unicode line separator renders a break without ending the GFM row. + // A Unicode line separator keeps the row intact; its space ends GFM autolinks. return tableRanges.some(({ start, end }) => offset >= start && offset < end) - ? "\u2028" + ? " \u2028" : " \n"; } const boundary = layoutBoundary(name, source, raw, offset, closing, startsAfterBoundary); diff --git a/packages/app/src/components/markdown/renderer.test.ts b/packages/app/src/components/markdown/renderer.test.ts index 1594399c07..852b274203 100644 --- a/packages/app/src/components/markdown/renderer.test.ts +++ b/packages/app/src/components/markdown/renderer.test.ts @@ -57,9 +57,9 @@ describe("shared Markdown source adaptation", () => { (node.content ?? "") + (node.children?.map(text).join("") ?? ""); expect(rows.map((row) => row.children?.map(text))).toEqual([ ["A", "B"], - ["1", "first\u2028second"], - ["2", "first\u2028second"], - ["3", "first\u2028second"], + ["1", "first \u2028second"], + ["2", "first \u2028second"], + ["3", "first \u2028second"], ]); }); it("keeps table breaks inside a details wrapper without a blank line", async () => { @@ -78,12 +78,16 @@ describe("shared Markdown source adaptation", () => { expect(tables).toHaveLength(1); expect(tables[0].attributes?.bodyRowCount).toBe("1"); expect(tables[0].children?.[1]?.children?.[0]?.children?.[1]?.children).toEqual([ - { type: "Text", content: "first\u2028second" }, + { type: "Text", content: "first \u2028second" }, ]); }); - it("recognizes file links separately on either side of a table break", async () => { + it.each([ + ["src/first.ts", "src/second.ts"], + ["https://example.com/first", "src/second.ts"], + ["https://example.com/first", "https://example.com/second"], + ])("keeps %s and %s separate across a table break", async (first, second) => { const prepared = prepareMarkdownSource( - "| A | B |\n|---|---|\n| 1 | src/first.ts
src/second.ts |", + `| A | B |\n|---|---|\n| 1 | ${first}
${second} |`, "/repo", ); const tree = recognizeTextLinks( @@ -97,17 +101,17 @@ describe("shared Markdown source adaptation", () => { node.children?.forEach(collect); }; collect(tree); - expect(links).toEqual(["src/first.ts", "src/second.ts"]); + expect(links).toEqual([first, second]); }); it.each([ - ["| A
B | C |\n|---|---|\n| x | y |", "| A\u2028B | C |\n|---|---|\n| x | y |"], + ["| A
B | C |\n|---|---|\n| x | y |", "| A \u2028B | C |\n|---|---|\n| x | y |"], [ "> | A | B |\n> |---|---|\n> | x | first
second |", - "> | A | B |\n> |---|---|\n> | x | first\u2028second |", + "> | A | B |\n> |---|---|\n> | x | first \u2028second |", ], [ "| A | B |\n|---|---|\n| `
` | **first

second** |", - "| A | B |\n|---|---|\n| `
` | **first\u2028\u2028second** |", + "| A | B |\n|---|---|\n| `
` | **first \u2028 \u2028second** |", ], [ "| A | B |\n|---|---|\n| x | \\
<br> |",