eleven / ocho Public

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>
James Adam bd41320f76567d3a5b665e5eb47e201028aa610c parent afdb8e4
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