render markdown in your own chat bubbles, and let you copy them
Your own messages were plain `whitespace-pre-wrap`, so pasting a fenced block gave you back the backticks and none of the formatting — the one place in the app where markdown was written and never read. The usual reason to leave user text alone is that rendering changes what was typed: `a_b_c` becomes italic, a leading `#` becomes a heading. That is real, and the answer is the Copy button beside it, which copies the RAW text. The original is one click away and nothing you wrote can be lost to a renderer. Same remark plugins, components and sanitize schema as the assistant's, deliberately — a code block should look identical whoever wrote it, and the schema already strips script, iframe, object, embed and form, so pasted HTML is inert either way. The copy button appears on hover rather than always, unlike the assistant's: a reply is something you copy out of, while your own message is something you already have, so the control earns its place only when reached for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -140,13 +140,40 @@ export const MessageBubble = ({ message, onAnswer, onRetry, onDecidePlan, defaul
|
||||
return (
|
||||
<>
|
||||
{metadata && <CollapsibleBlock label="Frontmatter" content={metadata} />}
|
||||
<div className="flex justify-end">
|
||||
<div className="flex justify-end group">
|
||||
<div className="max-w-[80%] min-w-0 rounded-2xl rounded-tr-sm bg-duck-yellow/10 border border-duck-yellow/20 px-4 py-2.5 text-sm text-foreground">
|
||||
{message.images?.map((img, i) => (
|
||||
<img key={i} src={img.dataUrl} alt={img.filename} className="max-w-full max-h-64 rounded-lg mb-2" />
|
||||
))}
|
||||
{/* break-words, or a pasted URL or base64 blob pushes the bubble past max-w and out of the pane. */}
|
||||
<div className="whitespace-pre-wrap break-words">{text}</div>
|
||||
{/* YOUR OWN MESSAGE IS RENDERED AS MARKDOWN TOO.
|
||||
|
||||
It used to be `whitespace-pre-wrap` plain text, so pasting a fenced block gave you the
|
||||
backticks and none of the formatting — the one place in the app where markdown was
|
||||
written and not read.
|
||||
|
||||
The reason this is usually left plain is that rendering changes what you typed:
|
||||
`a_b_c` becomes italic, a leading `#` becomes a heading. That is real, and the answer
|
||||
is the Copy button below, which copies the RAW text — so the original is never more
|
||||
than one click away and nothing you wrote can be lost to a renderer.
|
||||
|
||||
Same plugins, components and sanitize schema as the assistant's, deliberately: a code
|
||||
block should look identical whoever wrote it. `break-words` stays, or a pasted URL or
|
||||
base64 blob pushes the bubble past max-w and out of the pane. */}
|
||||
<div className="chat-md break-words">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}
|
||||
components={MD_COMPONENTS}
|
||||
>
|
||||
{text}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
{/* Hidden until hover, unlike the assistant's. A reply is something you copy out of; your
|
||||
own message is something you already have, so the control earns its place only when
|
||||
reached for. */}
|
||||
<div className="flex justify-end -mb-1 -mr-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100">
|
||||
<CopyButton text={text} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user