From 0f104ffed36ed072107c444005e902f2e81227d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BF=97=E5=86=9B?= <851516902@qq.com> Date: Tue, 19 May 2026 15:31:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9B=E4=BD=9C=E8=A7=86=E5=9B=BE=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=BC=96=E8=BE=91=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/App.tsx | 1 - src/renderer/src/ContentArea.tsx | 7 +- src/renderer/src/PreviewArticle.tsx | 106 +++++++++++++++++++++------- src/renderer/src/index.css | 11 ++- 4 files changed, 93 insertions(+), 32 deletions(-) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 1e6a032..d937026 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1462,7 +1462,6 @@ ${previewHtml} hybridPreviewArticleRef={hybridPreviewArticleRef} hybridPreviewRef={hybridPreviewRef} hybridPreviewShellRef={hybridPreviewShellRef} - hybridEditorViewRef={hybridEditorViewRef} hybridLinePulse={hybridLinePulse} previewHtml={previewHtml} baseFilePath={activeTab?.filePath ?? null} diff --git a/src/renderer/src/ContentArea.tsx b/src/renderer/src/ContentArea.tsx index 460d80f..b3ec874 100644 --- a/src/renderer/src/ContentArea.tsx +++ b/src/renderer/src/ContentArea.tsx @@ -23,7 +23,6 @@ type ContentAreaProps = { hybridPreviewArticleRef: React.RefObject hybridPreviewRef: React.RefObject hybridPreviewShellRef: React.RefObject - hybridEditorViewRef: React.RefObject hybridLinePulse: { seq: number; topPx: number } | null previewHtml: string baseFilePath: string | null @@ -73,7 +72,7 @@ export function ContentArea(props: ContentAreaProps): React.ReactElement { editorMountKey, editorRef, readPreviewArticleRef, readPreviewOuterRef, hybridPreviewArticleRef, hybridPreviewRef, hybridPreviewShellRef, - hybridEditorViewRef, hybridLinePulse, + hybridLinePulse, previewHtml, baseFilePath, activeId, resolveRelativePath, onOpenMarkdown, onOpenExternal, onDrop, onDragOver, onDragLeave, @@ -109,7 +108,7 @@ export function ContentArea(props: ContentAreaProps): React.ReactElement { if (article) scrollArticleToQuery(article, searchQuery) return } - hybridEditorViewRef.current?.searchText?.(searchQuery) + editorRef.current?.searchText(searchQuery) } return ( @@ -151,7 +150,7 @@ export function ContentArea(props: ContentAreaProps): React.ReactElement { ) : null}
- {viewMode === 'hybrid' && activeTab ? ( + {(viewMode === 'hybrid' || viewMode === 'live') && activeTab ? ( ) : null} {viewMode === 'read' ? ( diff --git a/src/renderer/src/PreviewArticle.tsx b/src/renderer/src/PreviewArticle.tsx index 0eed165..14644c2 100644 --- a/src/renderer/src/PreviewArticle.tsx +++ b/src/renderer/src/PreviewArticle.tsx @@ -11,11 +11,15 @@ function ensureMermaid(): void { theme: 'neutral', securityLevel: 'loose', fontFamily: 'inherit', - flowchart: { useMaxWidth: true, htmlLabels: true, wrappingWidth: 180 }, - sequence: { useMaxWidth: true, wrap: true, width: 150 }, - er: { useMaxWidth: true }, - gantt: { useMaxWidth: true }, - mindmap: { useMaxWidth: true } + themeVariables: { + fontSize: '15px', + fontFamily: 'inherit' + }, + flowchart: { useMaxWidth: false, htmlLabels: true, wrappingWidth: 220 }, + sequence: { useMaxWidth: false, wrap: true, width: 200 }, + er: { useMaxWidth: false }, + gantt: { useMaxWidth: false }, + mindmap: { useMaxWidth: false } }) mermaidInited = true } @@ -69,7 +73,12 @@ async function renderMermaidIn(root: HTMLElement, isStale: () => boolean): Promi if (!root.isConnected || !wrap.isConnected) return host.innerHTML = svg bindFunctions?.(host) - tuneMermaidDiagram(host, wrap) + requestAnimationFrame(() => { + requestAnimationFrame(() => { + tuneMermaidDiagram(host, wrap) + observeMermaidWrap(wrap, host) + }) + }) wrap.dataset.mermaidRendered = '1' } catch (err) { if (isStale()) return @@ -81,14 +90,58 @@ async function renderMermaidIn(root: HTMLElement, isStale: () => boolean): Promi } } +function diagramAvailableWidth(wrap: HTMLElement): number { + let w = wrap.clientWidth + if (w < 80) { + const shell = wrap.closest('.preview-shell, .live-line-shell, .hybrid-preview-shell') + if (shell instanceof HTMLElement) w = shell.clientWidth + if (w < 80 && wrap.parentElement) w = wrap.parentElement.clientWidth + } + return Math.max(320, Math.min(w - 48, 1100)) +} + function tuneMermaidDiagram(host: HTMLElement, wrap: HTMLElement): void { const svg = host.querySelector('svg') if (!svg) return + svg.style.transform = '' + svg.style.transformOrigin = '' + host.style.minHeight = '' + + const availW = diagramAvailableWidth(wrap) + if (availW < 80) return + + const vb = svg.viewBox?.baseVal + let naturalW = vb?.width ?? 0 + let naturalH = vb?.height ?? 0 + if (!naturalW || !naturalH) { + try { + const bb = svg.getBBox() + naturalW = bb.width + naturalH = bb.height + } catch { + const r = svg.getBoundingClientRect() + naturalW = r.width + naturalH = r.height + } + } + if (!naturalW || !naturalH) return + + const targetW = availW + const targetH = (naturalH / naturalW) * targetW + + svg.setAttribute('width', String(Math.round(targetW))) + svg.setAttribute('height', String(Math.round(targetH))) + svg.style.width = `${Math.round(targetW)}px` svg.style.maxWidth = '100%' svg.style.height = 'auto' - svg.removeAttribute('width') - svg.removeAttribute('height') + svg.style.display = 'block' + svg.style.margin = '0 auto' + + host.style.width = '100%' + host.style.overflowX = naturalW > availW * 1.05 ? 'auto' : 'hidden' + host.style.overflowY = 'hidden' + host.style.minHeight = `${Math.ceil(targetH) + 8}px` host.querySelectorAll('foreignObject').forEach((fo) => { fo.setAttribute('overflow', 'visible') @@ -97,27 +150,27 @@ function tuneMermaidDiagram(host: HTMLElement, wrap: HTMLElement): void { inner.style.wordBreak = 'break-word' inner.style.overflowWrap = 'anywhere' inner.style.whiteSpace = 'normal' - inner.style.maxWidth = '100%' - inner.style.lineHeight = '1.35' - inner.style.fontSize = '13px' + inner.style.lineHeight = '1.4' } }) +} - const vb = svg.viewBox?.baseVal - const svgW = vb?.width || svg.getBoundingClientRect().width - const maxW = Math.max(120, wrap.clientWidth - 24) - if (svgW > maxW && maxW > 0) { - const scale = maxW / svgW - const svgH = vb?.height || svg.getBoundingClientRect().height - host.style.width = '100%' - host.style.overflowX = 'auto' - host.style.overflowY = 'hidden' - svg.style.display = 'block' - svg.style.margin = '0 auto' - svg.style.transform = `scale(${scale})` - svg.style.transformOrigin = 'top center' - host.style.minHeight = `${Math.ceil(svgH * scale) + 8}px` - } +type WrapWithObserver = HTMLElement & { __mermaidRO?: ResizeObserver } + +function observeMermaidWrap(wrap: WrapWithObserver, host: HTMLElement): void { + wrap.__mermaidRO?.disconnect() + const ro = new ResizeObserver(() => { + requestAnimationFrame(() => tuneMermaidDiagram(host, wrap)) + }) + ro.observe(wrap) + wrap.__mermaidRO = ro +} + +function disconnectMermaidObservers(root: HTMLElement): void { + root.querySelectorAll('.md-diagram-wrap').forEach((wrap) => { + wrap.__mermaidRO?.disconnect() + delete wrap.__mermaidRO + }) } function escapeHtml(s: string): string { @@ -262,6 +315,7 @@ export function PreviewArticle({ return () => { renderGenRef.current += 1 + disconnectMermaidObservers(root) } }, [html]) diff --git a/src/renderer/src/index.css b/src/renderer/src/index.css index adb4d06..04fe7ee 100644 --- a/src/renderer/src/index.css +++ b/src/renderer/src/index.css @@ -1774,7 +1774,8 @@ html[data-bg='22'] .app-root { overflow-x: auto; overflow-y: hidden; text-align: center; - contain: layout style; + width: 100%; + box-sizing: border-box; } .preview .md-diagram-wrap pre.mermaid { @@ -1804,8 +1805,11 @@ html[data-bg='22'] .app-root { .preview .md-diagram-wrap .mermaid-render-host svg { max-width: 100%; + width: auto; height: auto; display: block; + margin-left: auto; + margin-right: auto; } .preview .md-diagram-wrap .mermaid-render-host svg foreignObject { @@ -2574,6 +2578,11 @@ html[data-bg='22'] .app-root { .live-block-preview-inner .md-code-wrap, .live-block-preview-inner .md-diagram-wrap { margin: 0; + width: 100%; +} + +.live-block-preview-inner .md-diagram-wrap { + padding: 20px 16px 24px; } .live-block--preview .live-block-preview-inner a {