108 lines
2.6 KiB
JavaScript
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
|