refactor(SvgPreview): use shadow dom

This commit is contained in:
one 2025-05-29 18:18:03 +08:00 committed by 亢奋猫
parent 61a658611d
commit 01b1d7bc47
2 changed files with 45 additions and 21 deletions

View File

@ -1,28 +1,53 @@
import { CodeTool, usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import { memo, useCallback, useEffect, useRef } from 'react'
import styled from 'styled-components'
import { memo, useEffect, useRef } from 'react'
interface Props {
children: string
setTools?: (value: React.SetStateAction<CodeTool[]>) => void
}
/**
* 使 Shadow DOM SVG
*/
const SvgPreview: React.FC<Props> = ({ children, setTools }) => {
const svgContainerRef = useRef<HTMLDivElement>(null)
const sanitizeSvg = useCallback((svgContent: string): string => {
return svgContent.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
}, [])
useEffect(() => {
if (svgContainerRef.current) {
svgContainerRef.current.innerHTML = sanitizeSvg(children)
}
}, [children, sanitizeSvg])
const container = svgContainerRef.current
if (!container) return
const shadowRoot = container.shadowRoot || container.attachShadow({ mode: 'open' })
// 添加基础样式
const style = document.createElement('style')
style.textContent = `
:host {
padding: 1em;
background-color: white;
overflow: auto;
border: 0.5px solid var(--color-code-background);
border-top-left-radius: 0;
border-top-right-radius: 0;
display: block;
}
svg {
max-width: 100%;
height: auto;
}
`
// 清空并重新添加内容
shadowRoot.innerHTML = ''
shadowRoot.appendChild(style)
const svgContainer = document.createElement('div')
svgContainer.innerHTML = children
shadowRoot.appendChild(svgContainer)
}, [children])
// 使用通用图像工具
const { handleCopyImage, handleDownload } = usePreviewToolHandlers(svgContainerRef, {
imgSelector: '.svg-preview svg',
imgSelector: 'svg',
prefix: 'svg-image'
})
@ -33,16 +58,7 @@ const SvgPreview: React.FC<Props> = ({ children, setTools }) => {
handleDownload
})
return <SvgPreviewContainer ref={svgContainerRef} className="svg-preview" />
return <div ref={svgContainerRef} className="svg-preview" />
}
const SvgPreviewContainer = styled.div`
padding: 1em;
background-color: white;
overflow: auto;
border: 0.5px solid var(--color-code-background);
border-top-left-radius: 0;
border-top-right-radius: 0;
`
export default memo(SvgPreview)

View File

@ -32,6 +32,14 @@ export const usePreviewToolHandlers = (
// 创建选择器函数
const getImgElement = useCallback(() => {
if (!containerRef.current) return null
// 优先尝试从 Shadow DOM 中查找
const shadowRoot = containerRef.current.shadowRoot
if (shadowRoot) {
return shadowRoot.querySelector(imgSelector) as SVGElement | null
}
// 降级到常规 DOM 查找
return containerRef.current.querySelector(imgSelector) as SVGElement | null
}, [containerRef, imgSelector])