From 589370e45626a99a9dd0851f6238002e715e74cf Mon Sep 17 00:00:00 2001 From: tianyaxiang Date: Sun, 2 Feb 2025 18:06:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20Mermaid=20=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=9B=BE=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/editor/WechatEditor.tsx | 18 ++- src/components/editor/utils/copy-handler.tsx | 129 +++++++++++++++++-- 2 files changed, 137 insertions(+), 10 deletions(-) diff --git a/src/components/editor/WechatEditor.tsx b/src/components/editor/WechatEditor.tsx index f4011b9..1714f77 100644 --- a/src/components/editor/WechatEditor.tsx +++ b/src/components/editor/WechatEditor.tsx @@ -18,7 +18,7 @@ import { Copy, Clock, Type, Trash2 } from 'lucide-react' import { useLocalStorage } from '@/hooks/use-local-storage' import { codeThemes, type CodeThemeId } from '@/config/code-themes' import '@/styles/code-themes.css' -import { copyHandler } from './utils/copy-handler' +import { copyHandler, initializeMermaid, MERMAID_CONFIG } from './utils/copy-handler' // 计算阅读时间(假设每分钟阅读300字) const calculateReadingTime = (text: string): string => { @@ -59,6 +59,14 @@ export default function WechatEditor() { const { handleScroll } = useEditorSync(editorRef) const { handleEditorChange } = useAutoSave(value, setIsDraft) + // 初始化 Mermaid + useEffect(() => { + const init = async () => { + await initializeMermaid() + } + init() + }, []) + // 处理编辑器输入 const handleInput = useCallback((e: React.ChangeEvent) => { const newValue = e.target.value @@ -241,6 +249,14 @@ export default function WechatEditor() { try { const content = getPreviewContent() setPreviewContent(content) + + // 等待下一个渲染周期,确保内容已更新到 DOM + await new Promise(resolve => requestAnimationFrame(resolve)) + // 重新初始化 Mermaid,但不阻塞界面 + initializeMermaid().catch(error => { + console.error('Failed to initialize Mermaid:', error) + // 不显示错误提示,让界面继续运行 + }) } catch (error) { console.error('Error updating preview:', error) toast({ diff --git a/src/components/editor/utils/copy-handler.tsx b/src/components/editor/utils/copy-handler.tsx index 351bbef..a9369d8 100644 --- a/src/components/editor/utils/copy-handler.tsx +++ b/src/components/editor/utils/copy-handler.tsx @@ -4,6 +4,86 @@ declare global { interface Window { mermaid: { init: (config: any, nodes: NodeListOf) => Promise + 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((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 { const tempDiv = document.createElement('div') tempDiv.innerHTML = previewContent - // 等待所有 Mermaid 图表渲染完成 + // 首先找到所有 Mermaid 图表的源代码 const mermaidElements = tempDiv.querySelectorAll('.mermaid') if (mermaidElements.length > 0) { try { // 确保 mermaid 已经初始化 if (typeof window.mermaid !== 'undefined') { - // 重新初始化所有图表 + // 使用相同的配置 + window.mermaid.initialize(MERMAID_CONFIG) + // 重新渲染所有图表 await window.mermaid.init(undefined, mermaidElements) } } catch (error) { @@ -102,15 +184,29 @@ async function handlePreviewCopy(previewContent: string): Promise { } } - // 处理所有的 Mermaid 图表 + // 处理渲染后的 SVG const mermaidDiagrams = tempDiv.querySelectorAll('.mermaid svg') mermaidDiagrams.forEach(svg => { const container = svg.closest('.mermaid') if (container) { - // 保存原始的 SVG 内容 - const originalSvg = svg.cloneNode(true) - container.innerHTML = '' - container.appendChild(originalSvg) + // 设置 SVG 的样式 + const svgElement = svg as SVGElement + Object.assign(svgElement.style, { + 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 { const htmlContent = tempDiv.innerHTML const plainText = tempDiv.textContent || tempDiv.innerText - // 复制到剪贴板 + // 复制到剪贴板,添加必要的样式 + const styledHtml = ` +
+ ${htmlContent} +
+ ` + await navigator.clipboard.write([ 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' }) }) ])