修复聊天嵌套列表缩进

为 Markdown 无序列表按嵌套层级增加内边距
补充多层无序列表渲染回归测试
This commit is contained in:
2026-09-04 13:14:05 +08:00
parent c58fee23b2
commit c46aab1922
2 changed files with 48 additions and 9 deletions
@@ -1,5 +1,5 @@
import type { ErrorInfo, ReactNode } from 'react';
import { Component } from 'react';
import { Component, createContext, useContext } from 'react';
import ReactMarkdown, { type Components } from 'react-markdown';
import remarkGfm from 'remark-gfm';
@@ -44,6 +44,34 @@ class MarkdownErrorBoundary extends Component<
}
}
const ListDepthContext = createContext(0);
function MarkdownUnorderedList({ children }: { children?: ReactNode }) {
const depth = useContext(ListDepthContext);
return (
<ListDepthContext.Provider value={depth + 1}>
<ul
className={`m-0 mt-2 list-none space-y-1 first:mt-0 ${
depth > 0 ? 'pl-4' : 'pl-0'
}`}
>
{children}
</ul>
</ListDepthContext.Provider>
);
}
function MarkdownOrderedList({ children }: { children?: ReactNode }) {
const depth = useContext(ListDepthContext);
return (
<ListDepthContext.Provider value={depth + 1}>
<ol className="m-0 mt-2 list-decimal space-y-1 pl-5 first:mt-0">
{children}
</ol>
</ListDepthContext.Provider>
);
}
const markdownComponents: Components = {
// TODO: 产品确认安全外链策略后,再将链接文本恢复为可点击元素。
a: ({ children }) => children,
@@ -60,14 +88,8 @@ const markdownComponents: Components = {
p: ({ children }) => (
<p className="m-0 mt-2 break-words first:mt-0">{children}</p>
),
ul: ({ children }) => (
<ul className="m-0 mt-2 list-none space-y-1 pl-0 first:mt-0">{children}</ul>
),
ol: ({ children }) => (
<ol className="m-0 mt-2 list-decimal space-y-1 pl-5 first:mt-0">
{children}
</ol>
),
ul: MarkdownUnorderedList,
ol: MarkdownOrderedList,
li: ({ children }) => <li className="break-words">- {children}</li>,
blockquote: ({ children }) => (
<blockquote className="m-0 mt-2 border-l-2 border-(--platform-surface-border) pl-3 text-(--platform-text-soft) first:mt-0">
@@ -21,6 +21,23 @@ describe('ChatMarkdownMessage', () => {
);
});
it('为嵌套无序列表保留逐层缩进', () => {
const { container } = render(
<ChatMarkdownMessage
role="assistant"
text={
'- 无序号 A\n- 无序号 B\n - 缩进一层的子项\n - 缩进两层的子项\n - 回到一层子项\n- 无序号 C'
}
/>,
);
const lists = container.querySelectorAll('ul');
expect(lists).toHaveLength(3);
expect(lists[0]?.className).toContain('pl-0');
expect(lists[1]?.className).toContain('pl-4');
expect(lists[2]?.className).toContain('pl-4');
});
it('不会把 react-markdown 的 node 元数据泄漏到代码节点', () => {
const { container } = render(
<ChatMarkdownMessage