feat: make svg in markdown scalable

This commit is contained in:
one 2025-08-16 15:34:16 +08:00
parent 66b72e5149
commit ede226f74d
4 changed files with 50 additions and 3 deletions

View File

@ -32,8 +32,6 @@ export function renderSvgInShadowHost(svgContent: string, hostElement: HTMLEleme
}
svg {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
}

View File

@ -30,6 +30,7 @@ import { Pluggable } from 'unified'
import CodeBlock from './CodeBlock'
import Link from './Link'
import rehypeHeadingIds from './plugins/rehypeHeadingIds'
import rehypeScalableSvg from './plugins/rehypeScalableSvg'
import remarkDisableConstructs from './plugins/remarkDisableConstructs'
import Table from './Table'
@ -113,7 +114,7 @@ const Markdown: FC<Props> = ({ block, postProcess }) => {
const rehypePlugins = useMemo(() => {
const plugins: Pluggable[] = []
if (ALLOWED_ELEMENTS.test(messageContent)) {
plugins.push(rehypeRaw)
plugins.push(rehypeRaw, rehypeScalableSvg)
}
plugins.push([rehypeHeadingIds, { prefix: `heading-${block.id}` }])
if (mathEngine === 'KaTeX') {

View File

@ -0,0 +1,47 @@
import type { Element, Root } from 'hast'
import { visit } from 'unist-util-visit'
/**
* A Rehype plugin that makes SVG elements scalable.
*
* This plugin traverses the HAST (HTML Abstract Syntax Tree) and performs
* the following operations on each `<svg>` element:
*
* 1. Ensures a `viewBox` attribute exists. If it's missing but `width` and
* `height` are present, it generates a `viewBox` from them. This is
* crucial for making the SVG scalable.
*
* 2. Removes the `width` and `height` attributes. This allows the SVG's size
* to be controlled by CSS (e.g., `max-width: 100%`), making it responsive
* and preventing it from overflowing its container.
*
* @returns A unified transformer function.
*/
function rehypeScalableSvg() {
return (tree: Root) => {
visit(tree, 'element', (node: Element) => {
if (node.tagName === 'svg') {
const properties = node.properties || {}
const hasViewBox = 'viewBox' in properties
const width = properties.width as string | number | undefined
const height = properties.height as string | number | undefined
if (!hasViewBox && width && height) {
const numericWidth = parseFloat(String(width))
const numericHeight = parseFloat(String(height))
if (!isNaN(numericWidth) && !isNaN(numericHeight)) {
properties.viewBox = `0 0 ${numericWidth} ${numericHeight}`
}
}
// Remove fixed width and height to allow CSS to control the size
delete properties.width
delete properties.height
node.properties = properties
}
})
}
}
export default rehypeScalableSvg

View File

@ -291,6 +291,7 @@ export const makeSvgScalable = (element: Element): Element => {
const numericHeight = parseFloat(height)
if (!isNaN(numericWidth) && !isNaN(numericHeight)) {
element.setAttribute('viewBox', `0 0 ${numericWidth} ${numericHeight}`)
element.setAttribute('max-width', `${numericWidth}px`)
}
}