Compare commits

...

1 Commits

Author SHA1 Message Date
24eb458621 feat(frontend): per-message markdown rendering
Self-contained, HTML-escaped (XSS-safe) markdown for history messages;
each message rendered independently so a syntax error (e.g. unclosed
code fence) cannot leak into the next message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:47:02 +01:00
3 changed files with 72 additions and 1 deletions

View File

@@ -556,6 +556,76 @@ button {
color: var(--text-faint);
font-style: italic;
}
.md > *:first-child {
margin-top: 0;
}
.md > *:last-child {
margin-bottom: 0;
}
.md p {
margin: 4px 0;
}
.md h1,
.md h2,
.md h3,
.md h4,
.md h5,
.md h6 {
margin: 8px 0 4px;
line-height: 1.25;
}
.md h1 {
font-size: 20px;
}
.md h2 {
font-size: 18px;
}
.md h3 {
font-size: 16px;
}
.md ul,
.md ol {
margin: 4px 0;
padding-left: 22px;
}
.md li {
margin: 2px 0;
}
.md a {
color: var(--accent);
}
.md code {
font-family: var(--mono);
font-size: 12.5px;
background: var(--elevated);
border: 1px solid var(--border);
border-radius: 4px;
padding: 1px 5px;
}
.md pre {
margin: 6px 0;
padding: 10px 12px;
background: var(--elevated);
border: 1px solid var(--border);
border-radius: 8px;
overflow: auto;
}
.md pre code {
border: 0;
padding: 0;
background: none;
font-size: 12.5px;
white-space: pre;
}
.md blockquote {
margin: 6px 0;
padding: 2px 12px;
border-left: 3px solid var(--border);
color: var(--text-muted);
}
.md del {
color: var(--text-faint);
}
.meta-badge {
font-family: var(--mono);
font-size: 10px;

BIN
src/lib/markdown.ts Normal file

Binary file not shown.

View File

@@ -2,6 +2,7 @@ import axios from 'axios'
import { io, type Socket } from 'socket.io-client'
import { useEffect, useMemo, useState } from 'react'
import { useAuth } from '../auth/auth-context'
import { renderMarkdown } from '../lib/markdown'
import { guildMembersCenter, joinGuildCenter } from '../lib/center-auth-client'
type MessageItem = {
@@ -470,7 +471,7 @@ export default function ChatPage() {
<span className={`meta-badge ${m.wakeup ? 'on' : ''}`}>wakeup={String(m.wakeup)}</span>
) : null}
</div>
<div className="text">{m.content}</div>
<div className="text md" dangerouslySetInnerHTML={{ __html: renderMarkdown(m.content) }} />
{devMode ? (
<pre className="meta-raw">
{JSON.stringify(