fix: shadow markdown (#1871)
问题:[Bug]: 当模型回复的html代码没有正确在代码框中时,html代码内容会影响到UI界面 #1767 原因:解析html之后css会污染应用样式 解决:将markdown完全放入shadow dom中,shadow dom天然隔绝样式,即可解决 * feat: Conditionally hide thinking loader for paused messages * feat: Implement Shadow DOM for Markdown rendering * feat: Add StyleProvider to Shadow DOM Markdown rendering * fix: Refactor Markdown rendering with inline ShadowDOM component Modify ReactMarkdown component to use style component for ShadowDOM rendering instead of wrapping component, simplifying the rendering approach --------- Co-authored-by: lizhixuan <zhixuan.li@banosuperapp.com>
This commit is contained in:
parent
a12d10f4f7
commit
13b465fe73
63
src/renderer/src/components/MarkdownShadowDOMRenderer.tsx
Normal file
63
src/renderer/src/components/MarkdownShadowDOMRenderer.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { StyleProvider } from '@ant-design/cssinjs'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { StyleSheetManager } from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ShadowDOMRenderer: React.FC<Props> = ({ children }) => {
|
||||
const hostRef = useRef<HTMLDivElement>(null)
|
||||
const [shadowRoot, setShadowRoot] = React.useState<ShadowRoot | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const host = hostRef.current
|
||||
if (!host) return
|
||||
|
||||
// 创建 shadow root
|
||||
const shadow = host.shadowRoot || host.attachShadow({ mode: 'open' })
|
||||
|
||||
// 获取原始样式表
|
||||
const markdownStyleSheet = Array.from(document.styleSheets).find((sheet) => {
|
||||
try {
|
||||
return Array.from(sheet.cssRules).some((rule: CSSRule) => {
|
||||
return rule.cssText?.includes('.markdown')
|
||||
})
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (markdownStyleSheet) {
|
||||
const style = document.createElement('style')
|
||||
const cssRules = Array.from(markdownStyleSheet.cssRules)
|
||||
.map((rule) => rule.cssText)
|
||||
.join('\n')
|
||||
|
||||
style.textContent = cssRules
|
||||
shadow.appendChild(style)
|
||||
}
|
||||
|
||||
setShadowRoot(shadow)
|
||||
}, [])
|
||||
|
||||
if (!shadowRoot) {
|
||||
return <div ref={hostRef} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={hostRef}>
|
||||
{createPortal(
|
||||
<StyleSheetManager target={shadowRoot}>
|
||||
<StyleProvider container={shadowRoot} layer>
|
||||
{children}
|
||||
</StyleProvider>
|
||||
</StyleSheetManager>,
|
||||
shadowRoot
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShadowDOMRenderer
|
||||
@ -1,13 +1,14 @@
|
||||
import 'katex/dist/katex.min.css'
|
||||
import 'katex/dist/contrib/copy-tex'
|
||||
|
||||
import MarkdownShadowDOMRenderer from '@renderer/components/MarkdownShadowDOMRenderer'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { Message } from '@renderer/types'
|
||||
import type { Message } from '@renderer/types'
|
||||
import { escapeBrackets, removeSvgEmptyLines, withGeminiGrounding } from '@renderer/utils/formats'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { FC, useMemo } from 'react'
|
||||
import { type FC, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ReactMarkdown, { Components } from 'react-markdown'
|
||||
import ReactMarkdown, { type Components } from 'react-markdown'
|
||||
import rehypeKatex from 'rehype-katex'
|
||||
// @ts-ignore next-line
|
||||
import rehypeMathjax from 'rehype-mathjax'
|
||||
@ -52,11 +53,12 @@ const Markdown: FC<Props> = ({ message }) => {
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className="markdown"
|
||||
rehypePlugins={rehypePlugins}
|
||||
remarkPlugins={[remarkMath, remarkGfm]}
|
||||
className="markdown"
|
||||
components={
|
||||
{
|
||||
style: MarkdownShadowDOMRenderer,
|
||||
a: Link,
|
||||
code: CodeBlock,
|
||||
img: ImagePreview
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user