diff --git a/package.json b/package.json index 8a74d209..5d24effc 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "@types/react": "18.2.40", "@types/react-dom": "^18.3.0", "@types/uuid": "^9.0.8", - "@vitest/coverage-v8": "^1.6.0", + "@vitest/coverage-v8": "~1.2.2", "antd-style": "^3.6.2", "babel-plugin-antd-style": "^1.0.4", "commitlint": "^19.3.0", diff --git a/src/Markdown/index.tsx b/src/Markdown/index.tsx index 9cbd6e73..1edf5261 100644 --- a/src/Markdown/index.tsx +++ b/src/Markdown/index.tsx @@ -21,6 +21,7 @@ import { CodeFullFeatured, CodeLite } from './CodeBlock'; import type { TypographyProps } from './Typography'; import { useStyles as useMarkdownStyles } from './markdown.style'; import { useStyles } from './style'; +import { escapeBrackets, escapeDollarNumber, escapeMhchem } from './utils'; export interface MarkdownProps extends TypographyProps { allowHtml?: boolean; @@ -33,6 +34,7 @@ export interface MarkdownProps extends TypographyProps { video?: Partial; }; enableImageGallery?: boolean; + enableLatex?: boolean; fullFeaturedCodeBlock?: boolean; onDoubleClick?: () => void; style?: CSSProperties; @@ -46,6 +48,7 @@ const Markdown = memo( style, fullFeaturedCodeBlock, onDoubleClick, + enableLatex = true, enableImageGallery = true, componentProps, allowHtml, @@ -60,6 +63,11 @@ const Markdown = memo( const { styles: mdStyles } = useMarkdownStyles({ fontSize, headerMultiple, marginMultiple }); const isChatMode = variant === 'chat'; + const escapedContent = useMemo(() => { + if (!enableLatex) return children; + return escapeMhchem(escapeBrackets(escapeDollarNumber(children))); + }, [children, enableLatex]); + const components: Components = useMemo( () => ({ a: (props: any) => , @@ -88,11 +96,12 @@ const Markdown = memo( ); const rehypePlugins = useMemo( - () => [allowHtml && rehypeRaw, rehypeKatex].filter(Boolean) as any, + () => [allowHtml && rehypeRaw, enableLatex && rehypeKatex].filter(Boolean) as any, [allowHtml], ); const remarkPlugins = useMemo( - () => [remarkGfm, remarkMath, isChatMode && remarkBreaks].filter(Boolean) as any, + () => + [remarkGfm, enableLatex && remarkMath, isChatMode && remarkBreaks].filter(Boolean) as any, [isChatMode], ); @@ -128,7 +137,7 @@ const Markdown = memo( remarkPlugins={remarkPlugins} {...rest} > - {children} + {escapedContent} diff --git a/src/Markdown/utils.ts b/src/Markdown/utils.ts new file mode 100644 index 00000000..e40ba463 --- /dev/null +++ b/src/Markdown/utils.ts @@ -0,0 +1,34 @@ +export function escapeDollarNumber(text: string) { + let escapedText = ''; + + for (let i = 0; i < text.length; i += 1) { + let char = text[i]; + const nextChar = text[i + 1] || ' '; + + if (char === '$' && nextChar >= '0' && nextChar <= '9') { + char = '\\$'; + } + + escapedText += char; + } + + return escapedText; +} + +export function escapeBrackets(text: string) { + const pattern = /(```[\S\s]*?```|`.*?`)|\\\[([\S\s]*?[^\\])\\]|\\\((.*?)\\\)/g; + return text.replaceAll(pattern, (match, codeBlock, squareBracket, roundBracket) => { + if (codeBlock) { + return codeBlock; + } else if (squareBracket) { + return `$$${squareBracket}$$`; + } else if (roundBracket) { + return `$${roundBracket}$`; + } + return match; + }); +} + +export function escapeMhchem(text: string) { + return text.replaceAll('$\\ce{', '$\\\\ce{').replaceAll('$\\pu{', '$\\\\pu{'); +}