mosaicmap/components/blog-viewer.tsx
2025-08-12 21:25:52 +08:00

100 lines
3.7 KiB
TypeScript
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 { EditorContent, useEditor } from '@tiptap/react';
import { StarterKit } from '@tiptap/starter-kit';
import { Image } from '@tiptap/extension-image';
import { TaskItem, TaskList } from '@tiptap/extension-list';
import { TextAlign } from '@tiptap/extension-text-align';
import { Typography } from '@tiptap/extension-typography';
import { Highlight } from '@tiptap/extension-highlight';
import { Subscript } from '@tiptap/extension-subscript';
import { Superscript } from '@tiptap/extension-superscript';
import { HorizontalRule } from '@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension';
import { useEffect } from 'react';
// 导入必要的样式
import '@/components/tiptap-node/blockquote-node/blockquote-node.scss';
import '@/components/tiptap-node/code-block-node/code-block-node.scss';
import '@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss';
import '@/components/tiptap-node/list-node/list-node.scss';
import '@/components/tiptap-node/image-node/image-node.scss';
import '@/components/tiptap-node/heading-node/heading-node.scss';
import '@/components/tiptap-node/paragraph-node/paragraph-node.scss';
interface BlogViewerProps {
content: any;
onContentReady?: () => void;
}
export default function BlogViewer({ content, onContentReady }: BlogViewerProps) {
const editor = useEditor({
immediatelyRender: false,
editable: false,
editorProps: {
attributes: {
class: 'simple-editor prose prose-gray max-w-none dark:prose-invert',
},
},
extensions: [
StarterKit.configure({
horizontalRule: false,
link: {
openOnClick: true,
enableClickSelection: false,
},
}),
HorizontalRule,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
TaskList,
TaskItem.configure({ nested: true }),
Highlight.configure({ multicolor: true }),
Image,
Typography,
Superscript,
Subscript,
],
content: content || '',
});
// 为标题添加 ID并在内容加载完成后通知父组件
useEffect(() => {
if (editor && editor.view.dom) {
// 等待 DOM 更新
setTimeout(() => {
const headings = editor.view.dom.querySelectorAll('h1, h2, h3, h4, h5, h6');
headings.forEach((heading, index) => {
const element = heading as HTMLElement;
if (!element.id) {
const text = element.textContent || '';
let id = text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
// 确保 ID 唯一
let uniqueId = id;
let counter = 1;
while (document.getElementById(uniqueId)) {
uniqueId = `${id}-${counter}`;
counter++;
}
element.id = uniqueId;
}
});
// 通知父组件内容已准备好
onContentReady?.();
}, 100);
}
}, [editor, content, onContentReady]);
return (
<div className="blog-viewer-wrapper min-h-[400px] p-6">
<EditorContent
editor={editor}
className="blog-viewer-content"
/>
</div>
);
}