feat: chat
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Renders a message body from its entity spans.
|
||||
*
|
||||
* Nothing here parses markup — the server already turned the composer's
|
||||
* markers into clean text plus spans, so a message cannot inject markup into
|
||||
* anyone else's thread. This component only decorates ranges.
|
||||
*/
|
||||
|
||||
import { buildEntityTree, type EntityNode } from "@/lib/chat-format";
|
||||
import type { MessageEntity } from "@/lib/chat";
|
||||
|
||||
function renderNodes(nodes: EntityNode[], keyPrefix: string): React.ReactNode[] {
|
||||
return nodes.map((node, i) => {
|
||||
const key = `${keyPrefix}-${i}`;
|
||||
if (node.kind === "text") return <span key={key}>{node.text}</span>;
|
||||
|
||||
const children = renderNodes(node.children, key);
|
||||
switch (node.entity.type) {
|
||||
case "bold":
|
||||
return <strong key={key} className="font-semibold">{children}</strong>;
|
||||
case "strike":
|
||||
return <s key={key}>{children}</s>;
|
||||
case "code":
|
||||
return (
|
||||
<code
|
||||
key={key}
|
||||
className="rounded bg-black/10 px-1 py-0.5 font-mono text-[0.9em] dark:bg-white/15"
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
case "url":
|
||||
return (
|
||||
<a
|
||||
key={key}
|
||||
href={node.entity.url}
|
||||
target="_blank"
|
||||
// noreferrer as well as noopener: the destination shouldn't learn
|
||||
// which conversation the link was shared in.
|
||||
rel="noopener noreferrer"
|
||||
className="underline underline-offset-2 hover:opacity-80"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
default:
|
||||
return <span key={key}>{children}</span>;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function MessageText({
|
||||
body,
|
||||
entities,
|
||||
}: {
|
||||
body: string;
|
||||
entities: MessageEntity[];
|
||||
}) {
|
||||
// whitespace-pre-wrap keeps the sender's line breaks without turning the
|
||||
// body into markup.
|
||||
return (
|
||||
<span className="whitespace-pre-wrap break-words">
|
||||
{renderNodes(buildEntityTree(body, entities), "e")}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user