Sfoglia il codice sorgente

fix: document render (#4153)

Seefs 1 mese fa
parent
commit
c7cf20391e

+ 14 - 35
web/src/components/common/DocumentRenderer/index.jsx

@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
 import { API, showError } from '../../../helpers';
 import { Empty, Card, Spin, Typography } from '@douyinfe/semi-ui';
 const { Title } = Typography;
@@ -28,7 +28,7 @@ import {
 import { useTranslation } from 'react-i18next';
 import MarkdownRenderer from '../markdown/MarkdownRenderer';
 
-// 检查是否为 URL
+// Check whether content is a URL.
 const isUrl = (content) => {
   try {
     new URL(content.trim());
@@ -38,27 +38,23 @@ const isUrl = (content) => {
   }
 };
 
-// 检查是否为 HTML 内容
+// Check whether content contains HTML.
 const isHtmlContent = (content) => {
   if (!content || typeof content !== 'string') return false;
 
-  // 检查是否包含HTML标签
   const htmlTagRegex = /<\/?[a-z][\s\S]*>/i;
   return htmlTagRegex.test(content);
 };
 
-// 安全地渲染HTML内容
+// Parse HTML content and extract inline styles.
 const sanitizeHtml = (html) => {
-  // 创建一个临时元素来解析HTML
   const tempDiv = document.createElement('div');
   tempDiv.innerHTML = html;
 
-  // 提取样式
   const styles = Array.from(tempDiv.querySelectorAll('style'))
     .map((style) => style.innerHTML)
     .join('\n');
 
-  // 提取body内容,如果没有body标签则使用全部内容
   const bodyContent = tempDiv.querySelector('body');
   const content = bodyContent ? bodyContent.innerHTML : html;
 
@@ -76,15 +72,11 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
   const { t } = useTranslation();
   const [content, setContent] = useState('');
   const [loading, setLoading] = useState(true);
-  const [htmlStyles, setHtmlStyles] = useState('');
-  const [processedHtmlContent, setProcessedHtmlContent] = useState('');
 
   const loadContent = async () => {
-    // 先从缓存中获取
     const cachedContent = localStorage.getItem(cacheKey) || '';
     if (cachedContent) {
       setContent(cachedContent);
-      processContent(cachedContent);
       setLoading(false);
     }
 
@@ -93,7 +85,6 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
       const { success, message, data } = res.data;
       if (success && data) {
         setContent(data);
-        processContent(data);
         localStorage.setItem(cacheKey, data);
       } else {
         if (!cachedContent) {
@@ -111,16 +102,12 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
     }
   };
 
-  const processContent = (rawContent) => {
-    if (isHtmlContent(rawContent)) {
-      const { content: htmlContent, styles } = sanitizeHtml(rawContent);
-      setProcessedHtmlContent(htmlContent);
-      setHtmlStyles(styles);
-    } else {
-      setProcessedHtmlContent('');
-      setHtmlStyles('');
+  const htmlPayload = useMemo(() => {
+    if (!isHtmlContent(content)) {
+      return { content: '', styles: '' };
     }
-  };
+    return sanitizeHtml(content);
+  }, [content]);
 
   useEffect(() => {
     loadContent();
@@ -129,8 +116,9 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
   // 处理HTML样式注入
   useEffect(() => {
     const styleId = `document-renderer-styles-${cacheKey}`;
+    const { styles } = htmlPayload;
 
-    if (htmlStyles) {
+    if (styles) {
       let styleEl = document.getElementById(styleId);
       if (!styleEl) {
         styleEl = document.createElement('style');
@@ -138,7 +126,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
         styleEl.type = 'text/css';
         document.head.appendChild(styleEl);
       }
-      styleEl.innerHTML = htmlStyles;
+      styleEl.innerHTML = styles;
     } else {
       const el = document.getElementById(styleId);
       if (el) el.remove();
@@ -148,7 +136,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
       const el = document.getElementById(styleId);
       if (el) el.remove();
     };
-  }, [htmlStyles, cacheKey]);
+  }, [cacheKey, htmlPayload]);
 
   // 显示加载状态
   if (loading) {
@@ -207,15 +195,6 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
 
   // 如果是 HTML 内容,直接渲染
   if (isHtmlContent(content)) {
-    const { content: htmlContent, styles } = sanitizeHtml(content);
-
-    // 设置样式(如果有的话)
-    useEffect(() => {
-      if (styles && styles !== htmlStyles) {
-        setHtmlStyles(styles);
-      }
-    }, [content, styles, htmlStyles]);
-
     return (
       <div className='min-h-screen bg-gray-50'>
         <div className='max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8'>
@@ -225,7 +204,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
             </Title>
             <div
               className='prose prose-lg max-w-none'
-              dangerouslySetInnerHTML={{ __html: htmlContent }}
+              dangerouslySetInnerHTML={{ __html: htmlPayload.content }}
             />
           </div>
         </div>

+ 9 - 7
web/src/components/layout/headerbar/ThemeToggle.jsx

@@ -95,13 +95,15 @@ const ThemeToggle = ({ theme, onThemeToggle, t }) => {
         </Dropdown.Menu>
       }
     >
-      <Button
-        icon={currentButtonIcon}
-        aria-label={t('切换主题')}
-        theme='borderless'
-        type='tertiary'
-        className='!p-1.5 !text-current focus:!bg-semi-color-fill-1 !rounded-full !bg-semi-color-fill-0 hover:!bg-semi-color-fill-1'
-      />
+      <span className='inline-flex'>
+        <Button
+          icon={currentButtonIcon}
+          aria-label={t('切换主题')}
+          theme='borderless'
+          type='tertiary'
+          className='!p-1.5 !text-current focus:!bg-semi-color-fill-1 !rounded-full !bg-semi-color-fill-0 hover:!bg-semi-color-fill-1'
+        />
+      </span>
     </Dropdown>
   );
 };