Fix tiptap selection sync and toolbar event loop
This commit is contained in:
@@ -20,7 +20,7 @@ import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DocumentToc } from "@/components/editor/document-toc";
|
||||
import { DocumentReadView } from "@/components/editor/document-read-view";
|
||||
import { buildPageSubtreeProjection, extractPageBlocks, type PageSubtreeProjection } from "@/lib/documents/page-subtree";
|
||||
import { extractPageBlocks, type PageSubtreeProjection } from "@/lib/documents/page-subtree";
|
||||
import {
|
||||
deleteDocumentCommand,
|
||||
embedDocumentCommand,
|
||||
@@ -143,6 +143,11 @@ export function DocumentContent({
|
||||
const openMoveEmbedPicker = useMoveEmbedPickerStore((s) => s.openPicker);
|
||||
const [pageTitle, setPageTitle] = useState(title ?? "无标题");
|
||||
const [content, setContent] = useState<unknown>(initialContent);
|
||||
const [serverContentSnapshot, setServerContentSnapshot] = useState<unknown>(initialContent);
|
||||
const [serverPageSubtreeSnapshot, setServerPageSubtreeSnapshot] = useState<PageSubtreeProjection | null>(
|
||||
initialPageSubtree,
|
||||
);
|
||||
const [serverPageSubtreeTitle, setServerPageSubtreeTitle] = useState<string>(title ?? "无标题");
|
||||
const [contentRevision, setContentRevision] = useState<number | null>(initialContentRevision);
|
||||
const [conflictDetectionKey, setConflictDetectionKey] = useState<string | null>(initialConflictDetectionKey);
|
||||
const [contentLoading, setContentLoading] = useState(() => initialContent == null);
|
||||
@@ -259,6 +264,14 @@ export function DocumentContent({
|
||||
setPageTitle(title ?? "无标题");
|
||||
}, [title]);
|
||||
|
||||
useEffect(() => {
|
||||
setServerPageSubtreeTitle(title ?? "无标题");
|
||||
}, [title]);
|
||||
|
||||
useEffect(() => {
|
||||
setServerPageSubtreeSnapshot(initialPageSubtree);
|
||||
}, [initialPageSubtree]);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(initialOptions ?? defaultOptions);
|
||||
}, [initialOptions]);
|
||||
@@ -275,6 +288,10 @@ export function DocumentContent({
|
||||
setConflictDetectionKey(initialConflictDetectionKey);
|
||||
}, [initialConflictDetectionKey]);
|
||||
|
||||
useEffect(() => {
|
||||
setServerContentSnapshot(initialContent);
|
||||
}, [initialContent]);
|
||||
|
||||
useEffect(() => {
|
||||
const nextBlocks = extractPageBlocks(content);
|
||||
latestBlocksRef.current = nextBlocks.length > 0 ? (nextBlocks as Json) : null;
|
||||
@@ -354,9 +371,11 @@ export function DocumentContent({
|
||||
content?: unknown;
|
||||
revision?: number | null;
|
||||
conflictDetectionKey?: string | null;
|
||||
pageSubtree?: PageSubtreeProjection | null;
|
||||
};
|
||||
if (canceled) return;
|
||||
setContent(payload.content ?? null);
|
||||
setServerContentSnapshot(payload.content ?? null);
|
||||
setContentRevision(
|
||||
typeof payload.revision === "number" && Number.isInteger(payload.revision)
|
||||
? payload.revision
|
||||
@@ -367,6 +386,13 @@ export function DocumentContent({
|
||||
? payload.conflictDetectionKey
|
||||
: `${documentId}:0`,
|
||||
);
|
||||
setServerPageSubtreeSnapshot(payload.pageSubtree ?? null);
|
||||
setServerPageSubtreeTitle(
|
||||
typeof payload.pageSubtree?.rootNode.metadata.title === "string" &&
|
||||
payload.pageSubtree.rootNode.metadata.title.trim()
|
||||
? payload.pageSubtree.rootNode.metadata.title
|
||||
: title ?? "无标题",
|
||||
);
|
||||
} catch (error) {
|
||||
if (canceled) return;
|
||||
if ((error as { name?: string })?.name === "AbortError") return;
|
||||
@@ -393,7 +419,7 @@ export function DocumentContent({
|
||||
contentLoadingTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [documentId, initialContent, contentReloadKey, workspaceId]);
|
||||
}, [documentId, initialContent, contentReloadKey, title, workspaceId]);
|
||||
|
||||
const persistTitle = useCallback(
|
||||
async (nextTitle: string) => {
|
||||
@@ -730,18 +756,24 @@ export function DocumentContent({
|
||||
options.hideChildPages && "wolai-hide-child-pages",
|
||||
);
|
||||
const pageSubtree = useMemo(() => {
|
||||
if (initialPageSubtree && content === initialContent && pageTitle === (title ?? "无标题")) {
|
||||
return initialPageSubtree;
|
||||
const hasServerPageSubtree = Boolean(serverPageSubtreeSnapshot);
|
||||
const titleUnchanged = pageTitle === serverPageSubtreeTitle;
|
||||
const contentUnchanged = content === serverContentSnapshot;
|
||||
|
||||
if (hasServerPageSubtree && titleUnchanged && contentUnchanged) {
|
||||
return serverPageSubtreeSnapshot;
|
||||
}
|
||||
return buildPageSubtreeProjection({
|
||||
documentId,
|
||||
title: pageTitle,
|
||||
content,
|
||||
});
|
||||
}, [content, documentId, initialContent, initialPageSubtree, pageTitle, title]);
|
||||
return null;
|
||||
}, [
|
||||
content,
|
||||
pageTitle,
|
||||
serverContentSnapshot,
|
||||
serverPageSubtreeSnapshot,
|
||||
serverPageSubtreeTitle,
|
||||
]);
|
||||
const readViewTocEntries = useMemo(
|
||||
() =>
|
||||
pageSubtree.outline
|
||||
(pageSubtree?.outline ?? [])
|
||||
.filter((entry) => typeof entry.anchorBlockId === "string" && entry.anchorBlockId.trim())
|
||||
.map(({ anchorBlockId, level, numbering, title: entryTitle }) => ({
|
||||
id: anchorBlockId as string,
|
||||
|
||||
Reference in New Issue
Block a user