Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions static/js/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<table style="border-collapse: collapse; width: 100%; margin: 10px 0;">';

rows.forEach((row, idx) => {
if (idx === 1 && /^[\s|:\-]+$/.test(row)) {
if (idx === 1 && hasSeparator) {
html += '<tbody>';
return;
}
Expand All @@ -694,9 +708,10 @@ export function mdToHtml(src, opts) {

html += '<tr>';

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()}</${tag}>`;
const align = aligns[i] || 'left';
html += `<${tag} style="padding: 8px; text-align: ${align}; border-bottom: 1px solid var(--border);">${cell.trim()}</${tag}>`;
});

html += '</tr>';
Expand Down
37 changes: 37 additions & 0 deletions tests/test_markdown_rendering_js.py
Original file line number Diff line number Diff line change
Expand Up @@ -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</th>' in html
assert 'text-align: center; border-bottom: 1px solid var(--border);">Score</th>' in html
assert 'text-align: right; border-bottom: 1px solid var(--border);">Amount</th>' in html
# Body cells inherit the same per-column alignment.
assert 'text-align: left; border-bottom: 1px solid var(--border);">Alpha</td>' in html
assert 'text-align: center; border-bottom: 1px solid var(--border);">12</td>' in html
assert 'text-align: right; border-bottom: 1px solid var(--border);">3.50</td>' 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</th>' in html
assert 'text-align: right; border-bottom: 1px solid var(--border);">Value</th>' in html
assert 'text-align: left; border-bottom: 1px solid var(--border);">id</td>' in html
assert 'text-align: right; border-bottom: 1px solid var(--border);">42</td>' 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 reasoning<channel|>Final answer.",
Expand Down