feat(editor): save leptos island and page aggregate alignment progress
- switch main document flow toward leptos tiptap island host and generated runtime assets - align page aggregate loading, page head single-source updates, and AI tool result recovery - add tests and smoke scripts for title sync, AI route recovery, and editor host cutover
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import type { Json } from "@/types/supabase";
|
||||
import { buildDocumentSavePayload, type DocumentSavePayload } from "@/lib/documents/save-contract";
|
||||
|
||||
export type PageBodyPersistedMeta = {
|
||||
revision: number | null;
|
||||
conflictDetectionKey: string | null;
|
||||
};
|
||||
|
||||
export type PageBodyPersistedState = PageBodyPersistedMeta & {
|
||||
workspaceId: string | null;
|
||||
};
|
||||
|
||||
export type ApplyPageBodyCommandInput = {
|
||||
documentId: string;
|
||||
workspaceId: string | null;
|
||||
revision: number | null;
|
||||
conflictDetectionKey: string | null;
|
||||
blocks: Json;
|
||||
applyEditorSnapshot?: (blocks: Json) => void;
|
||||
onPersistedMetaChange?: (meta: PageBodyPersistedMeta) => void;
|
||||
fetchImpl?: typeof fetch;
|
||||
persistPageBody?: (payload: DocumentSavePayload) => Promise<PageBodyPersistedMeta>;
|
||||
};
|
||||
|
||||
async function persistPageBodyViaRoute(
|
||||
payload: DocumentSavePayload,
|
||||
fetchImpl: typeof fetch,
|
||||
): Promise<PageBodyPersistedMeta> {
|
||||
const response = await fetchImpl("/api/documents/save", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const body = (await response.json().catch(() => null)) as
|
||||
| {
|
||||
ok?: boolean;
|
||||
revision?: number | null;
|
||||
conflictDetectionKey?: string | null;
|
||||
error?: string;
|
||||
}
|
||||
| null;
|
||||
|
||||
if (!response.ok) {
|
||||
const message = body && typeof body.error === "string" && body.error.trim() ? body.error.trim() : "页面正文保存失败";
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return {
|
||||
revision:
|
||||
typeof body?.revision === "number" && Number.isInteger(body.revision) ? body.revision : payload.revision,
|
||||
conflictDetectionKey:
|
||||
typeof body?.conflictDetectionKey === "string" && body.conflictDetectionKey.trim()
|
||||
? body.conflictDetectionKey.trim()
|
||||
: payload.conflictDetectionKey,
|
||||
};
|
||||
}
|
||||
|
||||
export async function applyPageBodyCommand(input: ApplyPageBodyCommandInput): Promise<PageBodyPersistedMeta> {
|
||||
const payload = buildDocumentSavePayload({
|
||||
documentId: input.documentId,
|
||||
workspaceId: input.workspaceId,
|
||||
revision: input.revision,
|
||||
conflictDetectionKey: input.conflictDetectionKey,
|
||||
content: input.blocks,
|
||||
editorDocument: undefined,
|
||||
tiptapDocument: undefined,
|
||||
blockCount: Array.isArray(input.blocks) ? input.blocks.length : null,
|
||||
snapshotCapturedAt: new Date().toISOString(),
|
||||
});
|
||||
const persistedMeta = input.persistPageBody
|
||||
? await input.persistPageBody(payload)
|
||||
: await persistPageBodyViaRoute(payload, input.fetchImpl ?? fetch);
|
||||
input.applyEditorSnapshot?.(input.blocks);
|
||||
input.onPersistedMetaChange?.(persistedMeta);
|
||||
return persistedMeta;
|
||||
}
|
||||
Reference in New Issue
Block a user