'use client'; import { useEffect, useRef, useState, useCallback, useMemo } from 'react'; import Reveal from 'reveal.js'; import 'reveal.js/dist/reveal.css'; import { marked } from 'marked'; const parseThemeFromContent = (content) => { if (!content || !content.startsWith('---')) return null; const frontmatterEnd = content.indexOf('---', 3); if (frontmatterEnd === -1) return null; const frontmatter = content.substring(3, frontmatterEnd); const themeMatch = frontmatter.match(/theme:\s*(\w+)/); return themeMatch ? themeMatch[1] : null; }; const loadTheme = async (themeName) => { if (!themeName) return; try { await import(`./themes/${themeName}.css`); } catch (error) { try { await import(`reveal.js/dist/theme/${themeName}.css`); } catch (error2) { await import('reveal.js/dist/theme/black.css'); } } }; const REVEAL_CONFIG = { hash: true, transition: 'slide', transitionSpeed: 'default', backgroundTransition: 'fade', controls: true, progress: true, center: true, touch: true, loop: false, rtl: false, navigationMode: 'default', shuffle: false, fragments: true, fragmentInURL: false, embedded: false, help: true, showNotes: true, autoPlayMedia: null, preloadIframes: null, autoSlide: 0, autoSlideStoppable: true, autoSlideMethod: Reveal.navigateNext, defaultTiming: null, mouseWheel: false, hideInactiveCursor: true, hideCursorTime: 5000, previewLinks: false, postMessage: true, postMessageEvents: false, focusBodyOnPageVisibilityChange: true, viewDistance: 3, mobileViewDistance: 2, display: 'block', hideAnchorsOnUrl: false, plugins: [] }; marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false, }); export default function Presentation({ data }) { const deckRef = useRef(null); const revealRef = useRef(null); const [mounted, setMounted] = useState(false); const [error, setError] = useState(null); useEffect(() => { setMounted(true); }, []); const parseMarkdownToSlides = useCallback((data) => { if (!data) { return `

演示文稿

暂无内容

`; } try { if (data.content) { return parseContentSlides(data); } return `

${data.title || '演示文稿'}

暂无内容

`; } catch (err) { return `

解析错误

内容解析失败,请检查数据格式

`; } }, []); const parseContentSlides = (data) => { // 移除frontmatter部分,只处理内容部分 let content = data.content; // 如果内容以---开头,说明有frontmatter,需要移除 if (content.startsWith('---')) { const frontmatterEnd = content.indexOf('---', 3); if (frontmatterEnd !== -1) { content = content.substring(frontmatterEnd + 3).trim(); } } const slideSeparators = /^---$/gm; const sections = content.split(slideSeparators); return sections.map((section, index) => { const trimmedSection = section.trim(); if (!trimmedSection) return ''; // 匹配背景和类属性 const slideCommentMatch = trimmedSection.match(//); let backgroundAttr = ''; let classAttr = ''; let contentWithoutComment = trimmedSection; if (slideCommentMatch) { const slideAttrs = slideCommentMatch[1]; // 提取data-background属性 const backgroundMatch = slideAttrs.match(/data-background="([^"]+)"/); if (backgroundMatch) { backgroundAttr = ` data-background="${backgroundMatch[1]}"`; } // 提取class属性 const classMatch = slideAttrs.match(/class="([^"]+)"/); if (classMatch) { classAttr = ` class="${classMatch[1]}"`; } // 移除整个slide注释 contentWithoutComment = trimmedSection.replace(//g, ''); } const htmlContent = marked(contentWithoutComment); if (index === 0 && !htmlContent.includes('

') && data.title) { return `

${data.title}

${htmlContent}`; } return `${htmlContent}`; }).join(''); }; const initializeReveal = useCallback(async () => { if (!deckRef.current || !data) return; try { if (revealRef.current) { revealRef.current.destroy(); } const themeName = data.theme || parseThemeFromContent(data.content) || 'default'; await loadTheme(themeName); revealRef.current = new Reveal(deckRef.current, REVEAL_CONFIG); revealRef.current.initialize(); const slides = parseMarkdownToSlides(data); const slidesContainer = deckRef.current.querySelector('.slides'); if (slidesContainer) { slidesContainer.innerHTML = slides; } setError(null); } catch (err) { setError('PPT初始化失败,请刷新页面重试'); } }, [data, parseMarkdownToSlides]); useEffect(() => { if (mounted && data) { initializeReveal(); } return () => { if (revealRef.current) { revealRef.current.destroy(); } }; }, [mounted, data, initializeReveal]); if (error) { return (

PPT加载失败

{error}

); } return ( <>
); }