fix: keep markdown tables block-level

This commit is contained in:
Michael Lam
2026-05-16 02:12:11 -07:00
parent e3035b3e40
commit 3cbe206832
3 changed files with 40 additions and 2 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- **PR #2375** by @Michaelyklam (closes #2374) — Markdown tables rendered by chat messages now stay as block-level `<table>` elements instead of being wrapped in paragraph tags by the renderer's final paragraph pass. This keeps CommonMark-style pipe tables visible as tables across browsers.
## [v0.51.74] — 2026-05-16 — Release AX (stage-367 — 4-PR safe-lane batch — #2362 table-cell spacing + #2363 run-state-consistency RFC + #2365 custom_providers list-format + #2367 settings sidebar i18n)
### Added
+5 -2
View File
@@ -2614,7 +2614,10 @@ function renderMd(raw){
const parseHeader=r=>r.trim().replace(/^\|/,'').replace(/\|$/,'').split('|').map(c=>`<th>${inlineMd(c.trim())}</th>`).join('');
const header=`<tr>${parseHeader(rows[0])}</tr>`;
const body=rows.slice(2).map(r=>`<tr>${parseRow(r)}</tr>`).join('');
return `<table><thead>${header}</thead><tbody>${body}</tbody></table>`;
// Surround with blank lines so the final paragraph splitter treats the
// generated table as its own block even when the regex consumes one of the
// markdown block's trailing newlines.
return `\n\n<table><thead>${header}</thead><tbody>${body}</tbody></table>\n\n`;
});
// #487: Outer image pass — handles ![alt](url) in plain paragraphs (outside tables/lists).
// Runs AFTER the table pass (images in table cells are handled by inlineMd() above).
@@ -2757,7 +2760,7 @@ function renderMd(raw){
return '\x00E'+(_pre_stash.length-1)+'\x00';
});
const parts=s.split(/\n{2,}/);
s=parts.map(p=>{p=p.trim();if(!p)return '';if(/^<(h[1-6]|ul|ol|pre|hr|blockquote)|^\x00[EQ]/.test(p))return p;return `<p>${p.replace(/\n/g,'<br>')}</p>`;}).join('\n');
s=parts.map(p=>{p=p.trim();if(!p)return '';if(/^<(h[1-6]|ul|ol|table|pre|hr|blockquote)|^\x00[EQ]/.test(p))return p;return `<p>${p.replace(/\n/g,'<br>')}</p>`;}).join('\n');
s=s.replace(/\x00E(\d+)\x00/g,(_,i)=>_pre_stash[+i]);
// ── Restore MEDIA stash → inline images or download links ─────────────────
s=s.replace(/\x00D(\d+)\x00/g,(_,i)=>{
+31
View File
@@ -187,6 +187,37 @@ class TestRendererSanitization:
class TestCommonLLMShapes:
def test_commonmark_table_is_not_wrapped_in_paragraph(self, driver_path):
src = (
"| 升级时段 | 人数 |\n"
"|---------|------|\n"
"| 5/15(发布当天) | ~30 人 |\n"
"| 5/16(今天) | ~10 人 |"
)
out = _render(driver_path, src)
assert "<table><thead>" in out
assert "<th>升级时段</th>" in out
assert "<td>5/15(发布当天)</td>" in out
assert "<td>~10 人</td>" in out
assert "<p><table" not in out, (
f"Markdown tables are block elements and must not be paragraph-wrapped: {out!r}"
)
def test_table_between_paragraphs_stays_block_level(self, driver_path):
src = (
"Before the table.\n\n"
"| Key | Value |\n"
"| --- | --- |\n"
"| A | B |\n\n"
"After the table."
)
out = _render(driver_path, src)
assert "<p>Before the table.</p>" in out
assert "<table><thead>" in out
assert "<p>After the table.</p>" in out
assert "<p><table" not in out
assert "</table></p>" not in out
def test_strikethrough_outside_quote(self, driver_path):
out = _render(driver_path, "This was ~~outdated~~ but is now fine.")
assert "<del>outdated</del>" in out