jingrow/components/presentation/Presentation.jsx

301 lines
9.1 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 } from 'react';
import Reveal from 'reveal.js';
import 'reveal.js/dist/reveal.css';
import 'reveal.js/dist/theme/black.css';
import { marked } from 'marked';
export default function Presentation({ data }) {
const deckRef = useRef(null);
const revealRef = useRef(null);
useEffect(() => {
if (!data) return;
// 配置 marked 解析器
marked.setOptions({
breaks: true,
gfm: true,
headerIds: false,
mangle: false,
});
// 初始化 Reveal.js
if (revealRef.current) {
revealRef.current.destroy();
}
revealRef.current = new Reveal(deckRef.current, {
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: false,
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: []
});
revealRef.current.initialize();
// 在Reveal.js初始化后更新slides内容
setTimeout(() => {
if (deckRef.current) {
const slides = parseMarkdownToSlides(data);
const slidesContainer = deckRef.current.querySelector('.slides');
if (slidesContainer) {
slidesContainer.innerHTML = slides;
}
}
}, 100);
// 清理函数
return () => {
if (revealRef.current) {
revealRef.current.destroy();
}
};
}, [data]);
// 将数据解析为幻灯片
const parseMarkdownToSlides = (data) => {
if (!data) {
return `<section><h1>演示文稿</h1><p>暂无内容</p></section>`;
}
// 如果有 content 字段,直接使用 markdown 内容
if (data.content) {
const slideSeparators = /^---$/gm;
const sections = data.content.split(slideSeparators);
return sections.map((section, index) => {
const trimmedSection = section.trim();
if (!trimmedSection) return '';
const htmlContent = marked(trimmedSection);
// 如果是第一个幻灯片且没有标题,添加标题
if (index === 0 && !htmlContent.includes('<h1>') && data.title) {
return `<section><h1>${data.title}</h1>${htmlContent}</section>`;
}
return `<section>${htmlContent}</section>`;
}).join('');
}
// 如果没有 content 字段,使用 items 数据构建幻灯片
const items = data.items || [];
if (items.length === 0) {
// 使用主数据构建单个幻灯片
return `<section>
<h1>${data.title || '演示文稿'}</h1>
${data.subtitle ? `<p class="lead">${data.subtitle}</p>` : ''}
${data.description ? `<div class="mt-4">${marked(data.description)}</div>` : ''}
${data.image ? `<img src="${data.image}" alt="${data.title}" style="max-width: 100%; height: auto; margin: 1rem 0;" />` : ''}
</section>`;
}
// 使用 items 构建多个幻灯片
return items.map((item, index) => {
const slideContent = [];
// 标题
if (item.item_title) {
slideContent.push(`<h1>${item.item_title}</h1>`);
}
// 副标题
if (item.item_subtitle) {
slideContent.push(`<p class="lead">${item.item_subtitle}</p>`);
}
// 描述
if (item.item_description) {
slideContent.push(`<div class="mt-4">${marked(item.item_description)}</div>`);
}
// 图片
if (item.item_image) {
slideContent.push(`<img src="${item.item_image}" alt="${item.item_title || 'Slide'}" style="max-width: 100%; height: auto; margin: 1rem 0;" />`);
}
// 视频
if (item.item_video_src) {
slideContent.push(`<video controls style="max-width: 100%; height: auto; margin: 1rem 0;">
<source src="${item.item_video_src}" type="video/mp4">
您的浏览器不支持视频播放。
</video>`);
}
// 按钮
if (item.item_button_text && item.item_button_link) {
slideContent.push(`<div class="mt-6">
<a href="${item.item_button_link}" class="btn btn-primary" target="_blank" rel="noopener noreferrer">
${item.item_button_text}
</a>
</div>`);
}
return `<section>${slideContent.join('')}</section>`;
}).join('');
};
return (
<div className="reveal-container" style={{ height: '100vh', width: '100%', position: 'fixed', top: 0, left: 0, zIndex: 1000, background: 'black' }}>
<style jsx global>{`
.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;
color: white !important;
}
.reveal-container .reveal .slides section h1,
.reveal-container .reveal .slides section h2,
.reveal-container .reveal .slides section h3,
.reveal-container .reveal .slides section h4,
.reveal-container .reveal .slides section h5,
.reveal-container .reveal .slides section h6 {
margin-bottom: 1rem !important;
color: white !important;
}
.reveal-container .reveal .slides section p {
margin-bottom: 0.5rem !important;
line-height: 1.6 !important;
color: white !important;
}
.reveal-container .reveal .slides section ul,
.reveal-container .reveal .slides section ol {
text-align: left !important;
margin: 1rem 0 !important;
color: white !important;
}
.reveal-container .reveal .slides section li {
margin-bottom: 0.5rem !important;
color: white !important;
}
.reveal-container .reveal .slides section code {
background: #333 !important;
color: #fff !important;
padding: 0.2rem 0.4rem !important;
border-radius: 3px !important;
font-family: 'Courier New', monospace !important;
}
.reveal-container .reveal .slides section pre {
background: #333 !important;
color: #fff !important;
padding: 1rem !important;
border-radius: 5px !important;
text-align: left !important;
overflow-x: auto !important;
}
.reveal-container .reveal .slides section blockquote {
border-left: 4px solid #666 !important;
padding-left: 1rem !important;
margin: 1rem 0 !important;
text-align: left !important;
color: #ccc !important;
}
.reveal-container .reveal .slides section table {
border-collapse: collapse !important;
width: 100% !important;
margin: 1rem 0 !important;
color: white !important;
}
.reveal-container .reveal .slides section th,
.reveal-container .reveal .slides section td {
border: 1px solid #666 !important;
padding: 0.5rem !important;
text-align: left !important;
color: white !important;
}
.reveal-container .reveal .slides section th {
background: #444 !important;
}
.reveal-container .reveal .slides section img {
max-width: 100% !important;
height: auto !important;
margin: 1rem 0 !important;
}
.reveal-container .reveal .slides section .btn {
display: inline-block !important;
padding: 0.5rem 1rem !important;
background: #007bff !important;
color: white !important;
text-decoration: none !important;
border-radius: 0.25rem !important;
margin: 0.5rem !important;
}
.reveal-container .reveal .slides section .btn:hover {
background: #0056b3 !important;
}
`}</style>
<div
ref={deckRef}
className="reveal"
style={{ height: '100%', width: '100%' }}
>
<div className="slides">
{/* 初始内容,会被 JavaScript 替换 */}
<section>
<p>正在加载内容...</p>
</section>
</div>
</div>
</div>
);
}