/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useEffect, useState } from 'react'; import { API, showError } from '../../../helpers'; import { Empty, Card, Spin, Typography } from '@douyinfe/semi-ui'; const { Title } = Typography; import { IllustrationConstruction, IllustrationConstructionDark, } from '@douyinfe/semi-illustrations'; import { useTranslation } from 'react-i18next'; import MarkdownRenderer from '../markdown/MarkdownRenderer'; // 检查是否为 URL const isUrl = (content) => { try { new URL(content.trim()); return true; } catch { return false; } }; // 检查是否为 HTML 内容 const isHtmlContent = (content) => { if (!content || typeof content !== 'string') return false; // 检查是否包含HTML标签 const htmlTagRegex = /<\/?[a-z][\s\S]*>/i; return htmlTagRegex.test(content); }; // 安全地渲染HTML内容 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; return { content, styles }; }; /** * 通用文档渲染组件 * @param {string} apiEndpoint - API 接口地址 * @param {string} title - 文档标题 * @param {string} cacheKey - 本地存储缓存键 * @param {string} emptyMessage - 空内容时的提示消息 */ 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); } try { const res = await API.get(apiEndpoint); const { success, message, data } = res.data; if (success && data) { setContent(data); processContent(data); localStorage.setItem(cacheKey, data); } else { if (!cachedContent) { showError(message || emptyMessage); setContent(''); } } } catch (error) { if (!cachedContent) { showError(emptyMessage); setContent(''); } } finally { setLoading(false); } }; const processContent = (rawContent) => { if (isHtmlContent(rawContent)) { const { content: htmlContent, styles } = sanitizeHtml(rawContent); setProcessedHtmlContent(htmlContent); setHtmlStyles(styles); } else { setProcessedHtmlContent(''); setHtmlStyles(''); } }; useEffect(() => { loadContent(); }, []); // 处理HTML样式注入 useEffect(() => { const styleId = `document-renderer-styles-${cacheKey}`; if (htmlStyles) { let styleEl = document.getElementById(styleId); if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = styleId; styleEl.type = 'text/css'; document.head.appendChild(styleEl); } styleEl.innerHTML = htmlStyles; } else { const el = document.getElementById(styleId); if (el) el.remove(); } return () => { const el = document.getElementById(styleId); if (el) el.remove(); }; }, [htmlStyles, cacheKey]); // 显示加载状态 if (loading) { return (
); } // 如果没有内容,显示空状态 if (!content || content.trim() === '') { return (
} darkModeImage={} className='p-8' />
); } // 如果是 URL,显示链接卡片 if (isUrl(content)) { return (
{title}

{t('管理员设置了外部链接,点击下方按钮访问')}

{t('访问' + title)}
); } // 如果是 HTML 内容,直接渲染 if (isHtmlContent(content)) { const { content: htmlContent, styles } = sanitizeHtml(content); // 设置样式(如果有的话) useEffect(() => { if (styles && styles !== htmlStyles) { setHtmlStyles(styles); } }, [content, styles, htmlStyles]); return (
{title}
); } // 其他内容统一使用 Markdown 渲染器 return (
{title}
); }; export default DocumentRenderer;