修复有序列表消息排版

区分有序与无序列表标记,避免有序项显示短横线
让列表项段落与文本保持同行
补充有序列表嵌套渲染回归测试
This commit is contained in:
2026-09-04 13:18:26 +08:00
parent c46aab1922
commit 397e321a88
2 changed files with 56 additions and 14 deletions
@@ -45,18 +45,22 @@ class MarkdownErrorBoundary extends Component<
}
const ListDepthContext = createContext(0);
const ListKindContext = createContext<'unordered' | 'ordered' | null>(null);
const ListItemContext = createContext(false);
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>
<ListKindContext.Provider value="unordered">
<ul
className={`m-0 mt-2 list-none space-y-1 first:mt-0 ${
depth > 0 ? 'pl-4' : 'pl-0'
}`}
>
{children}
</ul>
</ListKindContext.Provider>
</ListDepthContext.Provider>
);
}
@@ -65,13 +69,38 @@ 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>
<ListKindContext.Provider value="ordered">
<ol className="m-0 mt-2 list-decimal space-y-1 pl-5 first:mt-0">
{children}
</ol>
</ListKindContext.Provider>
</ListDepthContext.Provider>
);
}
function MarkdownParagraph({ children }: { children?: ReactNode }) {
const inListItem = useContext(ListItemContext);
return (
<p
className={`m-0 break-words ${inListItem ? 'inline' : 'mt-2 first:mt-0'}`}
>
{children}
</p>
);
}
function MarkdownListItem({ children }: { children?: ReactNode }) {
const listKind = useContext(ListKindContext);
return (
<ListItemContext.Provider value>
<li className="break-words">
{listKind === 'unordered' ? '- ' : null}
{children}
</li>
</ListItemContext.Provider>
);
}
const markdownComponents: Components = {
// TODO: 产品确认安全外链策略后,再将链接文本恢复为可点击元素。
a: ({ children }) => children,
@@ -85,12 +114,10 @@ const markdownComponents: Components = {
h3: ({ children }) => (
<h3 className="m-0 mt-3 text-base font-semibold first:mt-0">{children}</h3>
),
p: ({ children }) => (
<p className="m-0 mt-2 break-words first:mt-0">{children}</p>
),
p: MarkdownParagraph,
ul: MarkdownUnorderedList,
ol: MarkdownOrderedList,
li: ({ children }) => <li className="break-words">- {children}</li>,
li: MarkdownListItem,
blockquote: ({ children }) => (
<blockquote className="m-0 mt-2 border-l-2 border-(--platform-surface-border) pl-3 text-(--platform-text-soft) first:mt-0">
{children}
@@ -38,6 +38,21 @@ describe('ChatMarkdownMessage', () => {
expect(lists[2]?.className).toContain('pl-4');
});
it('有序列表不添加无序列表短横线,列表项段落与文本同行', () => {
const { container } = render(
<ChatMarkdownMessage
role="assistant"
text={'1. 第一件事\n\n2. 第二件事\n\n 1. 子步骤 2.1'}
/>,
);
const items = container.querySelectorAll('ol > li');
expect(items[0]?.textContent?.trim()).toBe('第一件事');
expect(items[1]?.textContent?.trim()).toContain('第二件事');
expect(items[0]?.querySelector('p')?.className ?? '').toContain('inline');
expect(items[0]?.textContent).not.toContain('-');
});
it('不会把 react-markdown 的 node 元数据泄漏到代码节点', () => {
const { container } = render(
<ChatMarkdownMessage