diff --git a/static/js/markdown.js b/static/js/markdown.js index 8735b83e7f..3eb2e36930 100644 --- a/static/js/markdown.js +++ b/static/js/markdown.js @@ -682,10 +682,24 @@ export function mdToHtml(src, opts) { const rows = table.trim().split('\n'); if (rows.length < 2) return table; + // Per-column text alignment from the GFM separator row (row index 1): + // :--- -> left :---: -> center ---: -> right --- -> default (left, + // matching the previous behaviour). Columns without a spec stay left. + const hasSeparator = /^[\s|:\-]+$/.test(rows[1]); + const aligns = hasSeparator + ? splitTableRow(rows[1]).map((spec) => { + const left = spec.startsWith(':'); + const right = spec.endsWith(':'); + if (left && right) return 'center'; + if (right) return 'right'; + return 'left'; + }) + : []; + let html = ''; rows.forEach((row, idx) => { - if (idx === 1 && /^[\s|:\-]+$/.test(row)) { + if (idx === 1 && hasSeparator) { html += ''; return; } @@ -694,9 +708,10 @@ export function mdToHtml(src, opts) { html += ''; - cells.forEach(cell => { + cells.forEach((cell, i) => { const tag = idx === 0 ? 'th' : 'td'; - html += `<${tag} style="padding: 8px; text-align: left; border-bottom: 1px solid var(--border);">${cell.trim()}`; + const align = aligns[i] || 'left'; + html += `<${tag} style="padding: 8px; text-align: ${align}; border-bottom: 1px solid var(--border);">${cell.trim()}`; }); html += ''; diff --git a/tests/test_markdown_rendering_js.py b/tests/test_markdown_rendering_js.py index 2ffe8914f7..e9c505891d 100644 --- a/tests/test_markdown_rendering_js.py +++ b/tests/test_markdown_rendering_js.py @@ -136,6 +136,43 @@ def test_table_separator_row_not_rendered_as_data(node_available): assert "---" not in html +def test_table_column_alignment_from_separator(node_available): + # GFM alignment markers in the separator row set per-column text-align: + # :--- -> left :---: -> centre ---: -> right. It applies to the header + # cells and every body cell in the column. + html = _run_markdown_case( + "| Name | Score | Amount |\n" + "| :--- | :---: | ---: |\n" + "| Alpha | 12 | 3.50 |\n" + "| Bravo | 7 | 128.00 |" + ) + + # Header cells carry the column's alignment. + assert 'text-align: left; border-bottom: 1px solid var(--border);">Name' in html + assert 'text-align: center; border-bottom: 1px solid var(--border);">Score' in html + assert 'text-align: right; border-bottom: 1px solid var(--border);">Amount' in html + # Body cells inherit the same per-column alignment. + assert 'text-align: left; border-bottom: 1px solid var(--border);">Alpha' in html + assert 'text-align: center; border-bottom: 1px solid var(--border);">12' in html + assert 'text-align: right; border-bottom: 1px solid var(--border);">3.50' in html + # One header + two body cells per column. + assert html.count("text-align: left") == 3 + assert html.count("text-align: center") == 3 + assert html.count("text-align: right") == 3 + + +def test_table_default_alignment_column_stays_left(node_available): + # A column whose separator has no colon (`---`) keeps the previous default, + # left alignment; a `---:` column is right-aligned; nothing becomes centre. + html = _run_markdown_case("| Key | Value |\n| --- | ---: |\n| id | 42 |") + + assert 'text-align: left; border-bottom: 1px solid var(--border);">Key' in html + assert 'text-align: right; border-bottom: 1px solid var(--border);">Value' in html + assert 'text-align: left; border-bottom: 1px solid var(--border);">id' in html + assert 'text-align: right; border-bottom: 1px solid var(--border);">42' in html + assert "text-align: center" not in html + + def test_process_with_thinking_handles_gemma4_thought_channel(node_available): html = _run_markdown_case( "<|channel>thought\ninternal reasoningFinal answer.",