KI-Chat: Markdown-Tabellen und Trennlinien werden gerendert
Der Mini-Renderer kannte weder Tabellen noch ---, also landeten beide als roher Text in einem Absatz: aus einer Anforderung/Erfahrung-Tabelle wurde eine einzeilige Pipe-Wueste, aus --- blieb ---. Das Modell nutzt aber gerade fuer Passungs-Vergleiche gern Tabellen. - GFM-Tabellen inkl. Ausrichtung (:--:), auch ohne Rand-Pipes. - Krumme Zeilen werden auf die Kopfbreite gezogen (fehlende Zellen leer, ueberzaehlige raus), sonst zerreisst eine Zeile das Raster. - Wrapper scrollt horizontal, damit eine breite Tabelle die Chat-Blase auf dem Handy nicht auseinanderzieht. - Zellinhalte laufen durch dieselbe Escape-/inlineFmt-Kette wie der Rest, HTML aus dem Modell kann also weiterhin nichts injizieren. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,24 @@
|
||||
return s.replace(/\u0000(\d+)\u0000/g, (m, i) => parked[Number(i)]);
|
||||
}
|
||||
|
||||
// Trennzeile einer Markdown-Tabelle: |---|:---:|---:| (mind. zwei Striche je
|
||||
// Spalte, Doppelpunkte für die Ausrichtung).
|
||||
function isTableSeparator(line) {
|
||||
return /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(line);
|
||||
}
|
||||
|
||||
function tableCells(line) {
|
||||
return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
|
||||
}
|
||||
|
||||
function alignOf(sep) {
|
||||
const links = sep.startsWith(':');
|
||||
const rechts = sep.endsWith(':');
|
||||
if (links && rechts) return 'center';
|
||||
if (rechts) return 'right';
|
||||
return null; // linksbündig ist der Standard, kein style nötig
|
||||
}
|
||||
|
||||
function renderMarkdown(src) {
|
||||
const esc = escapeHtml(src == null ? '' : String(src)).replace(/\r\n/g, '\n');
|
||||
const lines = esc.split('\n');
|
||||
@@ -109,6 +127,41 @@
|
||||
html += '<ol>' + items.join('') + '</ol>';
|
||||
continue;
|
||||
}
|
||||
// Tabelle (GFM): Kopfzeile + Trennzeile (|---|:--:|), dann Datenzeilen.
|
||||
// Das Modell nutzt Tabellen gern für Anforderung/Erfahrung-Vergleiche;
|
||||
// ohne diesen Zweig landeten sie als Pipe-Wüste in einem Absatz.
|
||||
if (line.indexOf('|') >= 0 && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
|
||||
flushPara();
|
||||
const kopf = tableCells(line);
|
||||
const ausricht = tableCells(lines[i + 1]).map(alignOf);
|
||||
i += 2;
|
||||
const zeilen = [];
|
||||
while (i < lines.length && lines[i].indexOf('|') >= 0 && !/^\s*$/.test(lines[i])) {
|
||||
zeilen.push(tableCells(lines[i]));
|
||||
i++;
|
||||
}
|
||||
const zelle = (tag, inhalt, idx) => {
|
||||
const a = ausricht[idx];
|
||||
return '<' + tag + (a ? ' style="text-align:' + a + '"' : '') + '>' + inlineFmt(inhalt || '') + '</' + tag + '>';
|
||||
};
|
||||
let t = '<div class="chat-md-table"><table><thead><tr>';
|
||||
kopf.forEach((c, idx) => { t += zelle('th', c, idx); });
|
||||
t += '</tr></thead><tbody>';
|
||||
for (const z of zeilen) {
|
||||
t += '<tr>';
|
||||
// An der Kopfbreite ausrichten: fehlende Zellen leer, überzählige raus —
|
||||
// sonst zerreißt eine krumme Zeile das Tabellenraster.
|
||||
for (let c = 0; c < kopf.length; c++) t += zelle('td', z[c], c);
|
||||
t += '</tr>';
|
||||
}
|
||||
html += t + '</tbody></table></div>';
|
||||
continue;
|
||||
}
|
||||
if (/^\s*([-*_])\1{2,}\s*$/.test(line)) { // Trennlinie
|
||||
flushPara();
|
||||
html += '<hr>';
|
||||
i++; continue;
|
||||
}
|
||||
if (/^\s*$/.test(line)) { flushPara(); i++; continue; }
|
||||
para.push(line.trim());
|
||||
i++;
|
||||
|
||||
Reference in New Issue
Block a user