From 24eb4586211edb80126c75fa093431dfbe911d42 Mon Sep 17 00:00:00 2001 From: hzhang Date: Fri, 15 May 2026 15:47:02 +0100 Subject: [PATCH] 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) --- src/index.css | 70 +++++++++++++++++++++++++++++++++++++++++ src/lib/markdown.ts | Bin 0 -> 3572 bytes src/pages/ChatPage.tsx | 3 +- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/lib/markdown.ts diff --git a/src/index.css b/src/index.css index 24fb718..7b096e8 100644 --- a/src/index.css +++ b/src/index.css @@ -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; diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts new file mode 100644 index 0000000000000000000000000000000000000000..15115d3fa496d0bcf706b38f9762fcb7ae25b5c6 GIT binary patch literal 3572 zcmb_eO>^Q#5cS!=qL-yC3ENV{qef{b@xOOeM$3lBeDz9GFx6IMQ%iz%LRQA>V8pt&*^eZzy0;)cUl&T zlC?0jky?wD)Zu9q(XS#|H(w~#q-4I33i>JjOMb~s_DFsrvW$v6kyJ|6CMm z;^Y{y(j=55xk(Lk5Uj@wrJ)IJ=WGa=D#v@UvWSNOX`q5dm zx*+dNY|77GaBwzsXPJ3@G#vO|_09G3 zMx>c3u7fBIjnu}kLPWOlgMewR?ms2lVPqfYt}t~4=O;K~uVE(hm08mzU2RY1aJpqV z_NsSY;|bixQ}~Uiv5)5<*8CDQD9i)sSgFi{nGXf6m0XT`{in8AINSC>JGGK#)SKO9 zBERo}PuZxK7X=y)B+d(Hp_QcS0Xb{}yBJ&G2Y~$B=Si&nY2f=e*L9uaD2@Wa)I&gP zfR!Cs{ikyK|3E{eZ$gU)@cD z7pT2J?d8R(Eo5e0@&7SDoIL;pJ_d?X^4kEfRvjSaX59ZYjPUoS`fM6lv{ritZ3p>> z$|rujI1f(P6Vo@fjIm6d{84T0r1H~X$`ouz4SG^Ol!Y?XPAE65n@R6$l^pM`bV zEM>7I$k2z;h&(seJbHe%-$wk+3mSKx2iLACc?Q@5sie~hD$Z|gn&R9GLS1I5@jZTS z^@^(P_5D^((NsS{jN7^tN;H@osn)_WLks^22`KoT{^oF4vF_QRZhOL1H%;4@=$v}w z@Eo_|UyC$%AcLS2O77Tlr%+|kxpbYzlbyBT+YTX21OP)3h`z7WOp>3{kZf`eaO~0s zT`Mq2r>sYWp=#G&y#nvDBDp8O-HY~&w_Lp^cgvcviQV9G)}7>tqTBg=?uUb*ABFNk zCO-Rff}Ev#$MVYJUzdJ2h`M07?wO zdJotXV7FD$+OzeAf8;L02;B>*6VB^I?qWcJUfFX+TZ0eS#~+`rE`Hu(b)3Ilxb-e| zuYJ`7`;6;rE!*z5!g}&)8ZgnD(>4?D$I#|v9P67-rQc19gZd`F z-eHwZ|A8xwM(VgbLaJR`bM>q`Ze(82@ckIsIt;Q6$(Jp@GIYkr0h8XD9vus9Uf*U! zq1%mw32_KsbhB%#>e5)@Vz4Jaxg4BNkI8eb8d(hZR`JKI!i5;DYIJYND>9ES zg;mBIfqxE5YUJj~Rc$ALJ9XF2UVM)CFl)ayQMj+Qmv@+<60SvAqDvwp>5p2ySFzWqXQKP!AZpIqH+v(6PGPMP#q0ok?w{EYH6EUawa_^V5wn IY}f7Szhk;Bh5!Hn literal 0 HcmV?d00001 diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 4004d30..9c46f29 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -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() { wakeup={String(m.wakeup)} ) : null} -
{m.content}
+
{devMode ? (
 {JSON.stringify(