web: render markdown-style formatting in chat previews (#36)
The sidebar chat-list snippet, the inline reply quote, and the composer's "Replying to…" banner all showed raw ** / _ / ~ / ` markers instead of the formatting a message actually renders with. Add renderPreviewText, sharing richTokens with the main bubble renderer but skipping links (these rows are already click targets).
Co-authored-by: Claude <noreply@anthropic.com>
1 file changed,
+18
−3
web/static/chat.js+18 −3
diff --git a/web/static/chat.js b/web/static/chat.js| index a4e572b..1a1ee5d 100644 |
| --- a/web/static/chat.js |
| +++ b/web/static/chat.js |
| @@ -800,7 +800,7 @@ function renderThreadList() { |
| if (threadPreview[t.id]) { |
| const prev = document.createElement("span"); |
| prev.className = "thread-row-preview"; |
| - prev.textContent = threadPreview[t.id]; |
| + renderPreviewText(prev, threadPreview[t.id]); |
| body.appendChild(prev); |
| } |
| row.appendChild(body); |
| @@ -1476,7 +1476,7 @@ function buildReplyQuote(rt) { |
| who.textContent = rt.name || ""; |
| const txt = document.createElement("div"); |
| txt.className = "reply-quote-text"; |
| - txt.textContent = rt.snippet || ""; |
| + renderPreviewText(txt, rt.snippet || ""); |
| q.appendChild(who); |
| q.appendChild(txt); |
| q.onclick = () => scrollToMessage(rt.id); |
| @@ -1622,7 +1622,7 @@ function startReply(meta) { |
| who.textContent = "Replying to " + (meta.name || ""); |
| const txt = document.createElement("div"); |
| txt.className = "reply-bar-text"; |
| - txt.textContent = meta.snippet || ""; |
| + renderPreviewText(txt, meta.snippet || ""); |
| body.appendChild(who); |
| body.appendChild(txt); |
| const x = document.createElement("button"); |
| @@ -3128,6 +3128,21 @@ function renderText(el, text) { |
| } |
| } |
| +// Render a chat-list snippet with inline formatting (bold/italic/strike/code) |
| +// but no links: the row itself is the click target, so a link renders as its |
| +// plain text rather than a nested, non-functional anchor inside the button. |
| +function renderPreviewText(el, text) { |
| + for (const t of richTokens(text)) { |
| + if (t.mark) { |
| + const w = document.createElement(MARK_TAG[t.mark]); |
| + w.textContent = t.text; |
| + el.appendChild(w); |
| + } else { |
| + el.appendChild(document.createTextNode(t.text)); |
| + } |
| + } |
| +} |
| + |
| // A centered modal sheet. Click the backdrop or Esc to dismiss. |
| function modal() { |
| const prevFocus = document.activeElement; // restored when the sheet closes |