import React, { memo, useEffect, useRef, useState } from 'react';
import rehypeKatex from 'rehype-katex';
import ReactMarkdown from 'react-markdown';
import rehypeHighlight from 'rehype-highlight';
import copy from 'copy-to-clipboard';
import { handleDoubleClick, langSubset } from '~/utils';
import Clipboard from '~/components/svg/Clipboard';
import CheckMark from '~/components/svg/CheckMark';
import useLocalize from '~/hooks/useLocalize';
type TCodeProps = {
inline: boolean;
className?: string;
children: React.ReactNode;
};
export const code: React.ElementType = memo(({ inline, className, children }: TCodeProps) => {
const match = /language-(\w+)/.exec(className ?? '');
const lang = match && match[1];
if (inline) {
return (
{children}
);
}
return {children};
});
export const CodeMarkdown = memo(
({ content = '', isSubmitting }: { content: string; isSubmitting: boolean }) => {
const scrollRef = useRef(null);
const [userScrolled, setUserScrolled] = useState(false);
const currentContent = content;
const rehypePlugins = [
[rehypeKatex, { output: 'mathml' }],
[
rehypeHighlight,
{
detect: true,
ignoreMissing: true,
subset: langSubset,
},
],
];
useEffect(() => {
const scrollContainer = scrollRef.current;
if (!scrollContainer) {
return;
}
const handleScroll = () => {
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
const isNearBottom = scrollHeight - scrollTop - clientHeight < 50;
if (!isNearBottom) {
setUserScrolled(true);
} else {
setUserScrolled(false);
}
};
scrollContainer.addEventListener('scroll', handleScroll);
return () => {
scrollContainer.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
const scrollContainer = scrollRef.current;
if (!scrollContainer || !isSubmitting || userScrolled) {
return;
}
scrollContainer.scrollTop = scrollContainer.scrollHeight;
}, [content, isSubmitting, userScrolled]);
return (
{currentContent}
);
},
);
export const CopyCodeButton: React.FC<{ content: string }> = ({ content }) => {
const localize = useLocalize();
const [isCopied, setIsCopied] = useState(false);
const handleCopy = () => {
copy(content, { format: 'text/plain' });
setIsCopied(true);
setTimeout(() => setIsCopied(false), 3000);
};
return (
);
};