优化 Mermaid 流程图的支持
This commit is contained in:
parent
5cf1777c00
commit
589370e456
@ -18,7 +18,7 @@ import { Copy, Clock, Type, Trash2 } from 'lucide-react'
|
|||||||
import { useLocalStorage } from '@/hooks/use-local-storage'
|
import { useLocalStorage } from '@/hooks/use-local-storage'
|
||||||
import { codeThemes, type CodeThemeId } from '@/config/code-themes'
|
import { codeThemes, type CodeThemeId } from '@/config/code-themes'
|
||||||
import '@/styles/code-themes.css'
|
import '@/styles/code-themes.css'
|
||||||
import { copyHandler } from './utils/copy-handler'
|
import { copyHandler, initializeMermaid, MERMAID_CONFIG } from './utils/copy-handler'
|
||||||
|
|
||||||
// 计算阅读时间(假设每分钟阅读300字)
|
// 计算阅读时间(假设每分钟阅读300字)
|
||||||
const calculateReadingTime = (text: string): string => {
|
const calculateReadingTime = (text: string): string => {
|
||||||
@ -59,6 +59,14 @@ export default function WechatEditor() {
|
|||||||
const { handleScroll } = useEditorSync(editorRef)
|
const { handleScroll } = useEditorSync(editorRef)
|
||||||
const { handleEditorChange } = useAutoSave(value, setIsDraft)
|
const { handleEditorChange } = useAutoSave(value, setIsDraft)
|
||||||
|
|
||||||
|
// 初始化 Mermaid
|
||||||
|
useEffect(() => {
|
||||||
|
const init = async () => {
|
||||||
|
await initializeMermaid()
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
}, [])
|
||||||
|
|
||||||
// 处理编辑器输入
|
// 处理编辑器输入
|
||||||
const handleInput = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleInput = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
const newValue = e.target.value
|
const newValue = e.target.value
|
||||||
@ -241,6 +249,14 @@ export default function WechatEditor() {
|
|||||||
try {
|
try {
|
||||||
const content = getPreviewContent()
|
const content = getPreviewContent()
|
||||||
setPreviewContent(content)
|
setPreviewContent(content)
|
||||||
|
|
||||||
|
// 等待下一个渲染周期,确保内容已更新到 DOM
|
||||||
|
await new Promise(resolve => requestAnimationFrame(resolve))
|
||||||
|
// 重新初始化 Mermaid,但不阻塞界面
|
||||||
|
initializeMermaid().catch(error => {
|
||||||
|
console.error('Failed to initialize Mermaid:', error)
|
||||||
|
// 不显示错误提示,让界面继续运行
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating preview:', error)
|
console.error('Error updating preview:', error)
|
||||||
toast({
|
toast({
|
||||||
|
@ -4,6 +4,86 @@ declare global {
|
|||||||
interface Window {
|
interface Window {
|
||||||
mermaid: {
|
mermaid: {
|
||||||
init: (config: any, nodes: NodeListOf<Element>) => Promise<void>
|
init: (config: any, nodes: NodeListOf<Element>) => Promise<void>
|
||||||
|
initialize: (config: any) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mermaid 配置
|
||||||
|
export const MERMAID_CONFIG = {
|
||||||
|
theme: 'neutral',
|
||||||
|
themeVariables: {
|
||||||
|
primaryColor: '#f5f8fe',
|
||||||
|
primaryBorderColor: '#c9e0ff',
|
||||||
|
primaryTextColor: '#000000',
|
||||||
|
lineColor: '#000000',
|
||||||
|
textColor: '#000000',
|
||||||
|
fontSize: '14px'
|
||||||
|
},
|
||||||
|
flowchart: {
|
||||||
|
htmlLabels: true,
|
||||||
|
curve: 'basis',
|
||||||
|
padding: 15,
|
||||||
|
nodeSpacing: 50,
|
||||||
|
rankSpacing: 50,
|
||||||
|
useMaxWidth: false
|
||||||
|
},
|
||||||
|
sequence: {
|
||||||
|
useMaxWidth: false,
|
||||||
|
boxMargin: 10,
|
||||||
|
mirrorActors: false,
|
||||||
|
bottomMarginAdj: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 Mermaid
|
||||||
|
*/
|
||||||
|
export async function initializeMermaid() {
|
||||||
|
// 等待 Mermaid 加载完成
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
try {
|
||||||
|
// 如果 mermaid 还没加载,等待它加载完成(最多等待 5 秒)
|
||||||
|
if (!window.mermaid) {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const startTime = Date.now()
|
||||||
|
const checkMermaid = () => {
|
||||||
|
// 如果等待超过 5 秒,就放弃等待
|
||||||
|
if (Date.now() - startTime > 5000) {
|
||||||
|
reject(new Error('Mermaid initialization timeout'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.mermaid) {
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
setTimeout(checkMermaid, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkMermaid()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置 Mermaid
|
||||||
|
window.mermaid.initialize({
|
||||||
|
...MERMAID_CONFIG,
|
||||||
|
startOnLoad: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化所有图表
|
||||||
|
const mermaidElements = document.querySelectorAll('.mermaid')
|
||||||
|
if (mermaidElements.length > 0) {
|
||||||
|
// 设置超时
|
||||||
|
const initPromise = window.mermaid.init(undefined, mermaidElements)
|
||||||
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
|
setTimeout(() => reject(new Error('Chart initialization timeout')), 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.race([initPromise, timeoutPromise])
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Mermaid initialization error:', error)
|
||||||
|
// 即使初始化失败也继续执行,不阻塞整个应用
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,13 +168,15 @@ async function handlePreviewCopy(previewContent: string): Promise<boolean> {
|
|||||||
const tempDiv = document.createElement('div')
|
const tempDiv = document.createElement('div')
|
||||||
tempDiv.innerHTML = previewContent
|
tempDiv.innerHTML = previewContent
|
||||||
|
|
||||||
// 等待所有 Mermaid 图表渲染完成
|
// 首先找到所有 Mermaid 图表的源代码
|
||||||
const mermaidElements = tempDiv.querySelectorAll('.mermaid')
|
const mermaidElements = tempDiv.querySelectorAll('.mermaid')
|
||||||
if (mermaidElements.length > 0) {
|
if (mermaidElements.length > 0) {
|
||||||
try {
|
try {
|
||||||
// 确保 mermaid 已经初始化
|
// 确保 mermaid 已经初始化
|
||||||
if (typeof window.mermaid !== 'undefined') {
|
if (typeof window.mermaid !== 'undefined') {
|
||||||
// 重新初始化所有图表
|
// 使用相同的配置
|
||||||
|
window.mermaid.initialize(MERMAID_CONFIG)
|
||||||
|
// 重新渲染所有图表
|
||||||
await window.mermaid.init(undefined, mermaidElements)
|
await window.mermaid.init(undefined, mermaidElements)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -102,15 +184,29 @@ async function handlePreviewCopy(previewContent: string): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理所有的 Mermaid 图表
|
// 处理渲染后的 SVG
|
||||||
const mermaidDiagrams = tempDiv.querySelectorAll('.mermaid svg')
|
const mermaidDiagrams = tempDiv.querySelectorAll('.mermaid svg')
|
||||||
mermaidDiagrams.forEach(svg => {
|
mermaidDiagrams.forEach(svg => {
|
||||||
const container = svg.closest('.mermaid')
|
const container = svg.closest('.mermaid')
|
||||||
if (container) {
|
if (container) {
|
||||||
// 保存原始的 SVG 内容
|
// 设置 SVG 的样式
|
||||||
const originalSvg = svg.cloneNode(true)
|
const svgElement = svg as SVGElement
|
||||||
container.innerHTML = ''
|
Object.assign(svgElement.style, {
|
||||||
container.appendChild(originalSvg)
|
backgroundColor: 'transparent',
|
||||||
|
maxWidth: '100%',
|
||||||
|
width: '100%',
|
||||||
|
height: 'auto'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新图表容器的样式
|
||||||
|
container.setAttribute('style', `
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -118,10 +214,25 @@ async function handlePreviewCopy(previewContent: string): Promise<boolean> {
|
|||||||
const htmlContent = tempDiv.innerHTML
|
const htmlContent = tempDiv.innerHTML
|
||||||
const plainText = tempDiv.textContent || tempDiv.innerText
|
const plainText = tempDiv.textContent || tempDiv.innerText
|
||||||
|
|
||||||
// 复制到剪贴板
|
// 复制到剪贴板,添加必要的样式
|
||||||
|
const styledHtml = `
|
||||||
|
<div style="
|
||||||
|
background-color: transparent;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 1.5;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
">
|
||||||
|
${htmlContent}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
await navigator.clipboard.write([
|
await navigator.clipboard.write([
|
||||||
new ClipboardItem({
|
new ClipboardItem({
|
||||||
'text/html': new Blob([htmlContent], { type: 'text/html' }),
|
'text/html': new Blob([styledHtml], { type: 'text/html' }),
|
||||||
'text/plain': new Blob([plainText], { type: 'text/plain' })
|
'text/plain': new Blob([plainText], { type: 'text/plain' })
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
|
Loading…
Reference in New Issue
Block a user