fix(markdown): end bare URL links at table cell breaks

Keep a whitespace boundary before inline line separators so GFM autolinking cannot absorb the next line. Cover URL-to-file and URL-to-URL breaks through the actual parser and link recognizer.
This commit is contained in:
paseo-bot[bot]
2026-11-05 08:41:29 +00:00
parent 3bb9d81520
commit 4a9c50b9c9
2 changed files with 16 additions and 12 deletions
+2 -2
View File
@@ -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);
@@ -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<br>src/second.ts |",
`| A | B |\n|---|---|\n| 1 | ${first}<br>${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<br>B | C |\n|---|---|\n| x | y |", "| A\u2028B | C |\n|---|---|\n| x | y |"],
["| A<br>B | C |\n|---|---|\n| x | y |", "| A \u2028B | C |\n|---|---|\n| x | y |"],
[
"> | A | B |\n> |---|---|\n> | x | first<br>second |",
"> | A | B |\n> |---|---|\n> | x | first\u2028second |",
"> | A | B |\n> |---|---|\n> | x | first \u2028second |",
],
[
"| A | B |\n|---|---|\n| `<br>` | **first<br><BR />second** |",
"| A | B |\n|---|---|\n| `<br>` | **first\u2028\u2028second** |",
"| A | B |\n|---|---|\n| `<br>` | **first \u2028 \u2028second** |",
],
[
"| A | B |\n|---|---|\n| x | \\<br> &lt;br&gt; |",