返回 presentation-ai
useHistoryGuard.ts
根目录 / src / components / notebook / presentation / editor / hooks / useHistoryGuard.ts
1 "use client";
2
3 import { type Value } from "platejs";
4 import { type PlateEditor } from "platejs/react";
5 import { useEffect, useRef } from "react";
6
7 interface UseHistoryGuardArgs {
8 editor: PlateEditor;
9 initialContent?: { content?: Value; id?: string } | null;
10 isGenerating: boolean;
11 readOnly: boolean;
12 isPresenting: boolean;
13 currentSlideId: string | null;
14 lastContentRef: React.MutableRefObject<string>;
15 }
16
17 /**
18 * Simplified history guard - syncs editor state when slide content changes externally.
19 * No longer needs to track "applying history" since we use type='history' in state.
20 */
21 export function useHistoryGuard({
22 editor,
23 initialContent,
24 isGenerating,
25 readOnly,
26 isPresenting,
27 currentSlideId,
28 lastContentRef,
29 }: UseHistoryGuardArgs) {
30 const lastSyncedIdRef = useRef<string | null>(null);
31
32 // Handle content updates during generation
33 useEffect(() => {
34 if (isGenerating && initialContent?.content) {
35 requestAnimationFrame(() => {
36 editor.tf.setValue(initialContent.content);
37 lastContentRef.current = JSON.stringify(initialContent.content);
38 });
39 }
40 }, [initialContent?.content, isGenerating, editor, lastContentRef]);
41
42 // Handle external updates (from undo/redo or other sources)
43 useEffect(() => {
44 if (!initialContent?.content || isGenerating || readOnly || isPresenting) {
45 return;
46 }
47
48 const newContentString = JSON.stringify(initialContent.content);
49 const currentContentString = JSON.stringify(editor.children);
50
51 // Skip if content hasn't changed or matches what we last synced
52 if (
53 newContentString === lastContentRef.current ||
54 newContentString === currentContentString
55 ) {
56 return;
57 }
58
59 // Content changed externally - sync editor
60 const isCurrentSlide = initialContent?.id === currentSlideId;
61 const savedSelection = isCurrentSlide ? editor.selection : null;
62
63 requestAnimationFrame(() => {
64 editor.tf.setValue(initialContent.content);
65 lastContentRef.current = newContentString;
66 lastSyncedIdRef.current = initialContent?.id ?? null;
67
68 // Restore cursor if this is the active slide
69 if (savedSelection && isCurrentSlide) {
70 requestAnimationFrame(() => {
71 try {
72 editor.tf.select(savedSelection, { edge: "end" });
73 editor.tf.focus();
74 } catch {
75 editor.tf.focus();
76 }
77 });
78 }
79 });
80 }, [
81 initialContent?.content,
82 initialContent?.id,
83 editor,
84 isGenerating,
85 readOnly,
86 isPresenting,
87 currentSlideId,
88 lastContentRef,
89 ]);
90 }
91
91 lines TYPESCRIPT