jingrow/components/presentation/Presentation.jsx

253 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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 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 `<section><h1>演示文稿</h1><p>暂无内容</p></section>`;
}
try {
if (data.content) {
return parseContentSlides(data);
}
return `<section><h1>${data.title || '演示文稿'}</h1><p>暂无内容</p></section>`;
} catch (err) {
return `<section><h1>解析错误</h1><p>内容解析失败,请检查数据格式</p></section>`;
}
}, []);
const parseContentSlides = (data) => {
const slideSeparators = /^---$/gm;
const sections = data.content.split(slideSeparators);
return sections.map((section, index) => {
const trimmedSection = section.trim();
if (!trimmedSection) return '';
const backgroundMatch = trimmedSection.match(/<!-- \.slide: data-background="([^"]+)"[^>]* -->/);
let backgroundAttr = '';
let contentWithoutBackground = trimmedSection;
if (backgroundMatch) {
backgroundAttr = ` data-background="${backgroundMatch[1]}"`;
contentWithoutBackground = trimmedSection.replace(/<!-- \.slide: data-background="[^"]+"[^>]* -->/g, '');
}
const htmlContent = marked(contentWithoutBackground);
if (index === 0 && !htmlContent.includes('<h1>') && data.title) {
return `<section${backgroundAttr}><h1>${data.title}</h1>${htmlContent}</section>`;
}
return `<section${backgroundAttr}>${htmlContent}</section>`;
}).join('');
};
const initializeReveal = useCallback(async () => {
if (!deckRef.current || !data) return;
try {
if (revealRef.current) {
revealRef.current.destroy();
}
const themeName = data.theme || '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 (
<div className="min-h-screen bg-black flex items-center justify-center">
<div className="text-white text-xl text-center">
<p className="mb-4">PPT加载失败</p>
<p className="text-sm text-gray-400">{error}</p>
<button
onClick={() => window.location.reload()}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
刷新页面
</button>
</div>
</div>
);
}
if (!mounted) {
return (
<div className="min-h-screen bg-black flex items-center justify-center">
<div className="text-white text-xl">正在加载内容...</div>
</div>
);
}
return (
<>
<div className="reveal-container">
<div
ref={deckRef}
className="reveal"
>
<div className="slides">
<section>
<p>正在加载内容...</p>
</section>
</div>
</div>
</div>
<style jsx global>{`
.reveal-container {
height: 100vh;
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
.reveal-container .reveal {
height: 100% !important;
width: 100% !important;
}
.reveal-container .reveal .slides {
height: 100% !important;
}
.reveal-container .reveal .slides section {
height: 100% !important;
display: flex !important;
flex-direction: column !important;
justify-content: center !important;
align-items: center !important;
text-align: center !important;
padding: 2rem !important;
box-sizing: border-box !important;
}
.reveal-container .reveal .slides section > * {
margin: 0.5rem 0 !important;
}
.reveal-container .reveal .slides section h1,
.reveal-container .reveal .slides section h2,
.reveal-container .reveal .slides section h3 {
margin-bottom: 1rem !important;
}
.reveal-container .reveal .slides section p {
margin: 0.5rem 0 !important;
line-height: 1.6 !important;
}
.reveal-container .reveal .slides section img,
.reveal-container .reveal .slides section video {
max-width: 100% !important;
height: auto !important;
margin: 1rem 0 !important;
}
`}</style>
</>
);
}