feat(lesson-preparation): 正文 Tiptap 编辑器
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { AnchorMark, AnchorPoint } from "../../lib/anchor-mark";
|
||||
import { useLessonPlanEditor } from "../../hooks/use-lesson-plan-editor";
|
||||
import { PaperToolbar } from "./paper-toolbar";
|
||||
|
||||
interface Props {
|
||||
/** 教材正文 HTML */
|
||||
content: string;
|
||||
/** 是否只读 */
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* V4 正文 Tiptap 编辑器。
|
||||
* - 字体:Fraunces 衬线
|
||||
* - 锚点:AnchorMark(range)+ AnchorPoint(point)
|
||||
* - 编辑时浮动工具条出现
|
||||
*/
|
||||
export function TextbookTiptapEditor({ content, readonly }: Props) {
|
||||
const t = useTranslations("lessonPreparation");
|
||||
const updateTextbookContent = useLessonPlanEditor((s) => s.updateTextbookContent);
|
||||
const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
// V4:禁用一些冲突的特性
|
||||
codeBlock: false,
|
||||
}),
|
||||
Placeholder.configure({
|
||||
placeholder: t("v4.paper.textbookHeader"),
|
||||
}),
|
||||
Image,
|
||||
AnchorMark,
|
||||
AnchorPoint,
|
||||
],
|
||||
content,
|
||||
editable: !readonly,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: "lp-textbook-editor prose prose-sm max-w-none focus:outline-none",
|
||||
style: "font-family: 'Fraunces', Georgia, serif; font-size: 16px; line-height: 1.75; color: #1a1a1a;",
|
||||
},
|
||||
},
|
||||
onUpdate: ({ editor: e }) => {
|
||||
// debounce 3s 保存到 store
|
||||
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
||||
debounceTimer.current = setTimeout(() => {
|
||||
updateTextbookContent({ content: e.getHTML() });
|
||||
}, 3000);
|
||||
},
|
||||
});
|
||||
|
||||
// 外部 content 变化时同步(如切换课案)
|
||||
useEffect(() => {
|
||||
if (editor && content !== editor.getHTML()) {
|
||||
editor.commands.setContent(content, { emitUpdate: false });
|
||||
}
|
||||
}, [content, editor]);
|
||||
|
||||
// 卸载时清理
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{!readonly && <PaperToolbar editor={editor} />}
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user