mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
feat(ui): render chat notice rows as markdown (#113450)
System-notice rows (local slash-command output such as /help) now render through the shared sanitized markdown pipeline (code-block chrome off, DOMPurify allowlist, breaks preserved) instead of literal pre-wrap text, so authored bold/inline-code formatting displays properly. Block content (lists, pre, blockquote) centers as a block but reads left-aligned. Follow-up to #112938.
This commit is contained in:
committed by
GitHub
parent
ab04b2103e
commit
cd76809d9a
@@ -1,5 +1,8 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { toSanitizedMarkdownHtml } from "../../../components/markdown.ts";
|
||||
import type { ChatItem } from "../../../lib/chat/chat-types.ts";
|
||||
import { detectTextDirection } from "../../../lib/text-direction.ts";
|
||||
|
||||
export function renderChatDivider(
|
||||
item: Extract<ChatItem, { kind: "divider" }>,
|
||||
@@ -51,7 +54,9 @@ export function renderChatDivider(
|
||||
export function renderChatNotice(item: Extract<ChatItem, { kind: "notice" }>) {
|
||||
return html`
|
||||
<div class="chat-notice" data-chat-row-key=${item.key} data-ts=${String(item.timestamp)}>
|
||||
${item.text}
|
||||
<div class="chat-text" dir=${detectTextDirection(item.text)}>
|
||||
${unsafeHTML(toSanitizedMarkdownHtml(item.text, { codeBlockChrome: "none" }))}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "./chat-message.ts";
|
||||
|
||||
const localStorageValues = new Map<string, string>();
|
||||
const renderMarkdownHtml = markdown.toSanitizedMarkdownHtml;
|
||||
const markdownRenderMock = vi.fn(
|
||||
(value: string, _options?: { codeBlockChrome?: "copy" | "none"; fileLinks?: boolean }) => value,
|
||||
);
|
||||
@@ -1903,21 +1904,31 @@ describe("grouped chat rendering", () => {
|
||||
expect(attribution?.nextElementSibling?.classList.contains("chat-bubble")).toBe(true);
|
||||
});
|
||||
|
||||
it("renders multiline system notices as plain centered rows", () => {
|
||||
it("renders multiline system notices as sanitized markdown", () => {
|
||||
const container = document.createElement("div");
|
||||
markdownRenderMock.mockImplementationOnce(renderMarkdownHtml);
|
||||
render(
|
||||
renderChatNotice({
|
||||
kind: "notice",
|
||||
key: "notice:command",
|
||||
text: "first line\n second line",
|
||||
text: "**first line**\nsecond line\n<img src=x onerror=alert(1)><script>alert(1)</script>",
|
||||
timestamp: 1000,
|
||||
}),
|
||||
container,
|
||||
);
|
||||
|
||||
const notice = container.querySelector<HTMLElement>(".chat-notice");
|
||||
expect(notice?.textContent?.trim()).toBe("first line\n second line");
|
||||
expect(notice?.querySelector("strong")?.textContent).toBe("first line");
|
||||
expect(notice?.textContent).not.toContain("**");
|
||||
expect(notice?.querySelector("br")).not.toBeNull();
|
||||
expect(notice?.textContent).toContain("first line");
|
||||
expect(notice?.textContent).toContain("second line");
|
||||
expect(notice?.querySelector("script")).toBeNull();
|
||||
expect(notice?.querySelector("img[onerror]")).toBeNull();
|
||||
expect(notice?.dataset.chatRowKey).toBe("notice:command");
|
||||
expect(markdownRenderMock).toHaveBeenCalledWith(expect.any(String), {
|
||||
codeBlockChrome: "none",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the current profile display name for the signed-in user's historical messages", () => {
|
||||
|
||||
@@ -411,7 +411,21 @@
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.chat-notice .chat-text {
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.chat-notice :where(ul, ol, pre, blockquote) {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.chat-notice :where(:not(pre) > code) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Avatar Styles */
|
||||
|
||||
Reference in New Issue
Block a user