创作视图增加编辑栏
This commit is contained in:
@@ -1462,7 +1462,6 @@ ${previewHtml}
|
|||||||
hybridPreviewArticleRef={hybridPreviewArticleRef}
|
hybridPreviewArticleRef={hybridPreviewArticleRef}
|
||||||
hybridPreviewRef={hybridPreviewRef}
|
hybridPreviewRef={hybridPreviewRef}
|
||||||
hybridPreviewShellRef={hybridPreviewShellRef}
|
hybridPreviewShellRef={hybridPreviewShellRef}
|
||||||
hybridEditorViewRef={hybridEditorViewRef}
|
|
||||||
hybridLinePulse={hybridLinePulse}
|
hybridLinePulse={hybridLinePulse}
|
||||||
previewHtml={previewHtml}
|
previewHtml={previewHtml}
|
||||||
baseFilePath={activeTab?.filePath ?? null}
|
baseFilePath={activeTab?.filePath ?? null}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ type ContentAreaProps = {
|
|||||||
hybridPreviewArticleRef: React.RefObject<HTMLElement | null>
|
hybridPreviewArticleRef: React.RefObject<HTMLElement | null>
|
||||||
hybridPreviewRef: React.RefObject<HTMLDivElement | null>
|
hybridPreviewRef: React.RefObject<HTMLDivElement | null>
|
||||||
hybridPreviewShellRef: React.RefObject<HTMLDivElement | null>
|
hybridPreviewShellRef: React.RefObject<HTMLDivElement | null>
|
||||||
hybridEditorViewRef: React.RefObject<EditorView | null>
|
|
||||||
hybridLinePulse: { seq: number; topPx: number } | null
|
hybridLinePulse: { seq: number; topPx: number } | null
|
||||||
previewHtml: string
|
previewHtml: string
|
||||||
baseFilePath: string | null
|
baseFilePath: string | null
|
||||||
@@ -73,7 +72,7 @@ export function ContentArea(props: ContentAreaProps): React.ReactElement {
|
|||||||
editorMountKey, editorRef,
|
editorMountKey, editorRef,
|
||||||
readPreviewArticleRef, readPreviewOuterRef,
|
readPreviewArticleRef, readPreviewOuterRef,
|
||||||
hybridPreviewArticleRef, hybridPreviewRef, hybridPreviewShellRef,
|
hybridPreviewArticleRef, hybridPreviewRef, hybridPreviewShellRef,
|
||||||
hybridEditorViewRef, hybridLinePulse,
|
hybridLinePulse,
|
||||||
previewHtml, baseFilePath, activeId, resolveRelativePath,
|
previewHtml, baseFilePath, activeId, resolveRelativePath,
|
||||||
onOpenMarkdown, onOpenExternal,
|
onOpenMarkdown, onOpenExternal,
|
||||||
onDrop, onDragOver, onDragLeave,
|
onDrop, onDragOver, onDragLeave,
|
||||||
@@ -109,7 +108,7 @@ export function ContentArea(props: ContentAreaProps): React.ReactElement {
|
|||||||
if (article) scrollArticleToQuery(article, searchQuery)
|
if (article) scrollArticleToQuery(article, searchQuery)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hybridEditorViewRef.current?.searchText?.(searchQuery)
|
editorRef.current?.searchText(searchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -151,7 +150,7 @@ export function ContentArea(props: ContentAreaProps): React.ReactElement {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<section className={`content${dragOver ? ' content-drag-over' : ''}`} onDrop={onDrop} onDragOver={onDragOver} onDragLeave={onDragLeave}>
|
<section className={`content${dragOver ? ' content-drag-over' : ''}`} onDrop={onDrop} onDragOver={onDragOver} onDragLeave={onDragLeave}>
|
||||||
{viewMode === 'hybrid' && activeTab ? (
|
{(viewMode === 'hybrid' || viewMode === 'live') && activeTab ? (
|
||||||
<MarkdownFormatToolbar editorRef={editorRef} />
|
<MarkdownFormatToolbar editorRef={editorRef} />
|
||||||
) : null}
|
) : null}
|
||||||
{viewMode === 'read' ? (
|
{viewMode === 'read' ? (
|
||||||
|
|||||||
@@ -11,11 +11,15 @@ function ensureMermaid(): void {
|
|||||||
theme: 'neutral',
|
theme: 'neutral',
|
||||||
securityLevel: 'loose',
|
securityLevel: 'loose',
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit',
|
||||||
flowchart: { useMaxWidth: true, htmlLabels: true, wrappingWidth: 180 },
|
themeVariables: {
|
||||||
sequence: { useMaxWidth: true, wrap: true, width: 150 },
|
fontSize: '15px',
|
||||||
er: { useMaxWidth: true },
|
fontFamily: 'inherit'
|
||||||
gantt: { useMaxWidth: true },
|
},
|
||||||
mindmap: { useMaxWidth: true }
|
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
|
mermaidInited = true
|
||||||
}
|
}
|
||||||
@@ -69,7 +73,12 @@ async function renderMermaidIn(root: HTMLElement, isStale: () => boolean): Promi
|
|||||||
if (!root.isConnected || !wrap.isConnected) return
|
if (!root.isConnected || !wrap.isConnected) return
|
||||||
host.innerHTML = svg
|
host.innerHTML = svg
|
||||||
bindFunctions?.(host)
|
bindFunctions?.(host)
|
||||||
tuneMermaidDiagram(host, wrap)
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
tuneMermaidDiagram(host, wrap)
|
||||||
|
observeMermaidWrap(wrap, host)
|
||||||
|
})
|
||||||
|
})
|
||||||
wrap.dataset.mermaidRendered = '1'
|
wrap.dataset.mermaidRendered = '1'
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isStale()) return
|
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 {
|
function tuneMermaidDiagram(host: HTMLElement, wrap: HTMLElement): void {
|
||||||
const svg = host.querySelector('svg')
|
const svg = host.querySelector('svg')
|
||||||
if (!svg) return
|
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.maxWidth = '100%'
|
||||||
svg.style.height = 'auto'
|
svg.style.height = 'auto'
|
||||||
svg.removeAttribute('width')
|
svg.style.display = 'block'
|
||||||
svg.removeAttribute('height')
|
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) => {
|
host.querySelectorAll('foreignObject').forEach((fo) => {
|
||||||
fo.setAttribute('overflow', 'visible')
|
fo.setAttribute('overflow', 'visible')
|
||||||
@@ -97,27 +150,27 @@ function tuneMermaidDiagram(host: HTMLElement, wrap: HTMLElement): void {
|
|||||||
inner.style.wordBreak = 'break-word'
|
inner.style.wordBreak = 'break-word'
|
||||||
inner.style.overflowWrap = 'anywhere'
|
inner.style.overflowWrap = 'anywhere'
|
||||||
inner.style.whiteSpace = 'normal'
|
inner.style.whiteSpace = 'normal'
|
||||||
inner.style.maxWidth = '100%'
|
inner.style.lineHeight = '1.4'
|
||||||
inner.style.lineHeight = '1.35'
|
|
||||||
inner.style.fontSize = '13px'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const vb = svg.viewBox?.baseVal
|
type WrapWithObserver = HTMLElement & { __mermaidRO?: ResizeObserver }
|
||||||
const svgW = vb?.width || svg.getBoundingClientRect().width
|
|
||||||
const maxW = Math.max(120, wrap.clientWidth - 24)
|
function observeMermaidWrap(wrap: WrapWithObserver, host: HTMLElement): void {
|
||||||
if (svgW > maxW && maxW > 0) {
|
wrap.__mermaidRO?.disconnect()
|
||||||
const scale = maxW / svgW
|
const ro = new ResizeObserver(() => {
|
||||||
const svgH = vb?.height || svg.getBoundingClientRect().height
|
requestAnimationFrame(() => tuneMermaidDiagram(host, wrap))
|
||||||
host.style.width = '100%'
|
})
|
||||||
host.style.overflowX = 'auto'
|
ro.observe(wrap)
|
||||||
host.style.overflowY = 'hidden'
|
wrap.__mermaidRO = ro
|
||||||
svg.style.display = 'block'
|
}
|
||||||
svg.style.margin = '0 auto'
|
|
||||||
svg.style.transform = `scale(${scale})`
|
function disconnectMermaidObservers(root: HTMLElement): void {
|
||||||
svg.style.transformOrigin = 'top center'
|
root.querySelectorAll<WrapWithObserver>('.md-diagram-wrap').forEach((wrap) => {
|
||||||
host.style.minHeight = `${Math.ceil(svgH * scale) + 8}px`
|
wrap.__mermaidRO?.disconnect()
|
||||||
}
|
delete wrap.__mermaidRO
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(s: string): string {
|
function escapeHtml(s: string): string {
|
||||||
@@ -262,6 +315,7 @@ export function PreviewArticle({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
renderGenRef.current += 1
|
renderGenRef.current += 1
|
||||||
|
disconnectMermaidObservers(root)
|
||||||
}
|
}
|
||||||
}, [html])
|
}, [html])
|
||||||
|
|
||||||
|
|||||||
@@ -1774,7 +1774,8 @@ html[data-bg='22'] .app-root {
|
|||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
contain: layout style;
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview .md-diagram-wrap pre.mermaid {
|
.preview .md-diagram-wrap pre.mermaid {
|
||||||
@@ -1804,8 +1805,11 @@ html[data-bg='22'] .app-root {
|
|||||||
|
|
||||||
.preview .md-diagram-wrap .mermaid-render-host svg {
|
.preview .md-diagram-wrap .mermaid-render-host svg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview .md-diagram-wrap .mermaid-render-host svg foreignObject {
|
.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-code-wrap,
|
||||||
.live-block-preview-inner .md-diagram-wrap {
|
.live-block-preview-inner .md-diagram-wrap {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.live-block-preview-inner .md-diagram-wrap {
|
||||||
|
padding: 20px 16px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.live-block--preview .live-block-preview-inner a {
|
.live-block--preview .live-block-preview-inner a {
|
||||||
|
|||||||
Reference in New Issue
Block a user