mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 11:10:18 +00:00
fix: keep markdown tables block-level
This commit is contained in:
@@ -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
@@ -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  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)=>{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user