import { FC, memo, useState } from 'react';
// @ts-ignore
import ReactMarkdown, { Options } from 'react-markdown';
import rehypeMathjax from 'rehype-mathjax';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import { CodeBlock } from './CodeBlock';
export const MemoizedReactMarkdown: FC<Options> = memo(
ReactMarkdown,
(prevProps, nextProps) => prevProps.children === nextProps.children,
);
const Markdown = ({
content,
inStreaming,
pending,
className,
}: {
content: string;
inStreaming?: boolean;
pending?: boolean;
className?: string;
}) => {
const [showModal, setShowModal] = useState(false); // 控制模态框显示的状态
const [modalImage, setModalImage] = useState(''); // 存储模态框中要显示的图片的 URL
// 图片预览模态框的 JSX 结构
const ImageModal = () => (
<div
className="fixed top-0 bottom-0 left-0 right-0 z-[9999] flex items-center justify-center bg-black bg-opacity-50"
style={{ display: showModal ? 'flex' : 'none' }}
onClick={() => setShowModal(false)}
>
<div
className="relative max-h-full overflow-y-auto max-w-[95%] lg:max-w-[85%]"
onClick={(event) => event.stopPropagation()} // 阻止事件冒泡
>
<img src={modalImage} alt="Preview" className="max-w-full" />
</div>
<span
className="absolute m-2 text-white top-10 right-10 text-[36px] cursor-pointer"
onClick={() => setShowModal(false)}
>
×
</span>
</div>
);
return (
<div>
<MemoizedReactMarkdown
className={className}
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] === '▍') {
return (
<div className="inline-block">
{inStreaming && !pending ? (
<span className="mt-1 cursor-default">▍</span>
) : null}
</div>
);
}
children[0] = (children[0] as string).replace('`▍`', '▍');
}
const match = /language-(\w+)/.exec(className || '');
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
table({ children }) {
return (
<table className="px-3 py-1 border border-collapse border-black dark:border-white">
{children}
</table>
);
},
th({ children }) {
return (
<th className="px-3 py-1 text-white break-words bg-gray-500 border border-black dark:border-white">
{children}
</th>
);
},
td({ children }) {
return (
<td className="px-3 py-1 break-words border border-black dark:border-white">
{children}
</td>
);
},
p({ children }) {
return <>{children}</>;
},
dl({ children }) {
// 去重children 数组中的空格
const newChildren = children.filter((item) => {
return item !== '\n';
});
return <dl className="">{newChildren}</dl>;
},
ol({ children }) {
// 去重children 数组中的空格
const newChildren = children.filter((item) => {
return item !== '\n';
});
return <ol className="">{newChildren}</ol>;
},
ul({ children }) {
// console.log('ul children', children);
// 去重children 数组中的空格
const newChildren = children?.filter((item) => {
return item !== '\n';
});
// console.log('ul newChildren', newChildren);
return <ul className="">{newChildren}</ul>;
},
li({ children }) {
// console.log('li children', children);
// 去重children 数组中的空格
const newChildren = children?.filter((item) => {
return item !== '\n';
});
// console.log('li newChildren', newChildren);
return <li>{newChildren}</li>;
},
a({ children, ...props }) {
return (
<>
{/* 1.处理 链接中的空格*/}
{children[0] === '详细' ? null : (
<a
className="text-blue-500 underline cursor-pointer hover:text-blue-700 "
href={props.href}
target="_blank"
rel="noreferrer"
>
{children}
</a>
)}
</>
);
},
img({ src, alt }) {
// img 拼接url
return (
<img
src={src}
alt={alt}
className="max-w-full h-auto lg:max-w-[800px] cursor-zoom-in pt-5 pb-5"
onClick={(event) => {
event.preventDefault();
setModalImage(src!);
setShowModal(true);
}}
/>
);
},
}}
>
{content}
</MemoizedReactMarkdown>
<ImageModal /> {/* 渲染图片预览模态框 */}
</div>
);
};
export default Markdown;
目测大家好像都放任页面抖动。因为输出的时候限于流式输出,没法做到准确预测内容布局,只能实时修改页面。
不过你可以适当缩小重绘的范围,让页面保持固定,只有当前组件重绘,应该会好一些。