nex_docus/frontend/src/components/LargeMarkdownViewer/LargeMarkdownViewer.jsx

108 lines
2.6 KiB
JavaScript

import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'
import { Alert } from 'antd'
import { Virtuoso } from 'react-virtuoso'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import rehypeRaw from 'rehype-raw'
import rehypeSlug from 'rehype-slug'
import './LargeMarkdownViewer.css'
export const LARGE_MARKDOWN_THRESHOLD = 250000
export const LARGE_MARKDOWN_NOTICE = '此文档内容过长,将采用精简模式显示'
export function isLargeMarkdownContent(content = '') {
return content.length > LARGE_MARKDOWN_THRESHOLD
}
export function MarkdownSizeNotice({ className = '' }) {
return (
<div className={`large-markdown-notice${className ? ` ${className}` : ''}`}>
<Alert
className="large-markdown-alert"
type="info"
showIcon
message={LARGE_MARKDOWN_NOTICE}
/>
</div>
)
}
function splitMarkdownIntoBlocks(content) {
if (!content) return []
const blocks = []
const lines = content.split('\n')
let current = []
let inFence = false
const flush = () => {
if (current.length > 0) {
blocks.push(current.join('\n'))
current = []
}
}
for (const line of lines) {
const isFenceLine = /^\s*(```|~~~)/.test(line)
const isHeading = /^(#{1,6})\s+/.test(line)
if (!inFence && isHeading) {
flush()
}
current.push(line)
if (isFenceLine) {
inFence = !inFence
}
if (!inFence && line.trim() === '') {
flush()
}
}
flush()
return blocks
}
const LargeMarkdownViewer = forwardRef(function LargeMarkdownViewer({ content, components, onClick }, ref) {
const virtuosoRef = useRef(null)
const markdownBlocks = useMemo(() => splitMarkdownIntoBlocks(content), [content])
useImperativeHandle(ref, () => ({
scrollToTop: () => {
virtuosoRef.current?.scrollToIndex({
index: 0,
align: 'start',
behavior: 'smooth',
})
},
}), [])
return (
<div className="large-markdown-viewer">
<MarkdownSizeNotice />
<div className="markdown-body markdown-body-large" onClick={onClick}>
<Virtuoso
ref={virtuosoRef}
className="markdown-virtual-list"
data={markdownBlocks}
itemContent={(index, block) => (
<div className="markdown-block" data-index={index}>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSlug]}
components={components}
>
{block}
</ReactMarkdown>
</div>
)}
/>
</div>
</div>
)
})
export default LargeMarkdownViewer