创作视图增加编辑栏

This commit is contained in:
2026-05-19 15:31:36 +08:00
parent 025f226205
commit 0f104ffed3
4 changed files with 93 additions and 32 deletions
-1
View File
@@ -1462,7 +1462,6 @@ ${previewHtml}
hybridPreviewArticleRef={hybridPreviewArticleRef}
hybridPreviewRef={hybridPreviewRef}
hybridPreviewShellRef={hybridPreviewShellRef}
hybridEditorViewRef={hybridEditorViewRef}
hybridLinePulse={hybridLinePulse}
previewHtml={previewHtml}
baseFilePath={activeTab?.filePath ?? null}
+3 -4
View File
@@ -23,7 +23,6 @@ type ContentAreaProps = {
hybridPreviewArticleRef: React.RefObject<HTMLElement | null>
hybridPreviewRef: React.RefObject<HTMLDivElement | null>
hybridPreviewShellRef: React.RefObject<HTMLDivElement | null>
hybridEditorViewRef: React.RefObject<EditorView | null>
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}
<section className={`content${dragOver ? ' content-drag-over' : ''}`} onDrop={onDrop} onDragOver={onDragOver} onDragLeave={onDragLeave}>
{viewMode === 'hybrid' && activeTab ? (
{(viewMode === 'hybrid' || viewMode === 'live') && activeTab ? (
<MarkdownFormatToolbar editorRef={editorRef} />
) : null}
{viewMode === 'read' ? (
+80 -26
View File
@@ -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<WrapWithObserver>('.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])
+10 -1
View File
@@ -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 {