优化 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
	 tianyaxiang
						tianyaxiang