feat: land page aggregate and phase7 document ai mainline
- 收口 page aggregate 读取、本地状态与命令客户端\n- 接入 phase7 document ai sidecar 与前端编排入口\n- 更新 architecture 与 design 状态迁移
This commit is contained in:
@@ -9,13 +9,15 @@ import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { useEditorBridgeStore } from "@/store/editor-bridge";
|
||||
import type { EditorReferenceBridge } from "@/store/editor-bridge";
|
||||
import type { Json } from "@/types/supabase";
|
||||
import type { DocumentStats, PageOptionsState } from "@/types/page-options";
|
||||
import type { DocumentStats } from "@/types/page-options";
|
||||
import { pickLeptosTiptapRuntimePageOptions } from "@/lib/documents/page-option-semantics";
|
||||
import {
|
||||
blocksFromTiptapDoc,
|
||||
editorBlockDocumentFromTiptapDoc,
|
||||
tiptapDocFromBlocks,
|
||||
} from "@/lib/documents/tiptap-content-converter";
|
||||
import { buildDocumentSavePayload } from "@/lib/documents/save-contract";
|
||||
import { executePageBodySavePayload } from "@/lib/documents/page-command-client";
|
||||
import {
|
||||
loadLeptosTiptapIslandAssets,
|
||||
} from "@/components/editor/leptos-tiptap-island-loader";
|
||||
@@ -116,21 +118,7 @@ type IslandBootstrapPayload = {
|
||||
readOnly: boolean;
|
||||
pageOptions: RuntimePageOptionsPayload;
|
||||
};
|
||||
|
||||
type RuntimePageOptionsPayload = Pick<
|
||||
PageOptionsState,
|
||||
"wideLayout" | "smallText" | "layoutDensity" | "showHeadingNumbers" | "embedDefaultBlockId"
|
||||
>;
|
||||
|
||||
function buildRuntimePageOptions(pageOptions: PageOptionsState): RuntimePageOptionsPayload {
|
||||
return {
|
||||
wideLayout: pageOptions.wideLayout,
|
||||
smallText: pageOptions.smallText,
|
||||
layoutDensity: pageOptions.layoutDensity,
|
||||
showHeadingNumbers: pageOptions.showHeadingNumbers,
|
||||
embedDefaultBlockId: pageOptions.embedDefaultBlockId,
|
||||
};
|
||||
}
|
||||
type RuntimePageOptionsPayload = ReturnType<typeof pickLeptosTiptapRuntimePageOptions>;
|
||||
|
||||
function toIsoNow(): string {
|
||||
return new Date().toISOString();
|
||||
@@ -316,7 +304,7 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
|
||||
revision: props.initialRevision ?? null,
|
||||
conflictDetectionKey: props.initialConflictDetectionKey ?? null,
|
||||
readOnly: Boolean(props.readOnly),
|
||||
pageOptions: buildRuntimePageOptions(props.pageOptions),
|
||||
pageOptions: pickLeptosTiptapRuntimePageOptions(props.pageOptions),
|
||||
});
|
||||
const [editorHeight, setEditorHeight] = useState(FALLBACK_MIN_HEIGHT);
|
||||
const [bridgeState, setBridgeState] = useState<RuntimeBridgeState>({
|
||||
@@ -375,7 +363,7 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
|
||||
revision: props.initialRevision ?? null,
|
||||
conflictDetectionKey: props.initialConflictDetectionKey ?? null,
|
||||
readOnly: Boolean(props.readOnly),
|
||||
pageOptions: buildRuntimePageOptions(props.pageOptions),
|
||||
pageOptions: pickLeptosTiptapRuntimePageOptions(props.pageOptions),
|
||||
};
|
||||
}, [
|
||||
mountIdentity,
|
||||
@@ -418,35 +406,22 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
|
||||
const persistDocument = useCallback(async () => {
|
||||
const normalizedDoc = latestDocRef.current;
|
||||
const normalizedBlocks = blocksFromTiptapDoc(normalizedDoc) as Json;
|
||||
const response = await fetch("/api/documents/save", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(
|
||||
buildDocumentSavePayload({
|
||||
documentId: props.documentId,
|
||||
workspaceId: props.workspaceId,
|
||||
revision: revisionRef.current,
|
||||
editorDocument: editorBlockDocumentFromTiptapDoc(normalizedDoc, props.documentId),
|
||||
content: normalizedBlocks,
|
||||
tiptapDocument: normalizedDoc,
|
||||
conflictDetectionKey: conflictDetectionKeyRef.current,
|
||||
snapshotCapturedAt: toIsoNow(),
|
||||
blockCount: Array.isArray(normalizedBlocks) ? normalizedBlocks.length : 0,
|
||||
}),
|
||||
),
|
||||
});
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
typeof payload?.error === "string" ? payload.error : `保存失败(${response.status})`,
|
||||
);
|
||||
}
|
||||
revisionRef.current =
|
||||
typeof payload?.revision === "number" ? payload.revision : revisionRef.current;
|
||||
const persistedMeta = await executePageBodySavePayload(
|
||||
buildDocumentSavePayload({
|
||||
documentId: props.documentId,
|
||||
workspaceId: props.workspaceId,
|
||||
revision: revisionRef.current,
|
||||
editorDocument: editorBlockDocumentFromTiptapDoc(normalizedDoc, props.documentId),
|
||||
content: normalizedBlocks,
|
||||
tiptapDocument: normalizedDoc,
|
||||
conflictDetectionKey: conflictDetectionKeyRef.current,
|
||||
snapshotCapturedAt: toIsoNow(),
|
||||
blockCount: Array.isArray(normalizedBlocks) ? normalizedBlocks.length : 0,
|
||||
}),
|
||||
);
|
||||
revisionRef.current = persistedMeta.revision ?? revisionRef.current;
|
||||
conflictDetectionKeyRef.current =
|
||||
typeof payload?.conflictDetectionKey === "string"
|
||||
? payload.conflictDetectionKey
|
||||
: conflictDetectionKeyRef.current;
|
||||
persistedMeta.conflictDetectionKey ?? conflictDetectionKeyRef.current;
|
||||
onPersistedMetaChangeRef.current?.({
|
||||
revision: revisionRef.current,
|
||||
conflictDetectionKey: conflictDetectionKeyRef.current,
|
||||
@@ -717,7 +692,7 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
|
||||
}
|
||||
dispatchRuntimeCommand(target, {
|
||||
command: "setPageOptions",
|
||||
pageOptions: buildRuntimePageOptions(props.pageOptions),
|
||||
pageOptions: pickLeptosTiptapRuntimePageOptions(props.pageOptions),
|
||||
});
|
||||
}, [props.pageOptions]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user