feat: 收口文档桥接与 OnlyOffice/Sidebar 回归

- 为 documents.save/meta/content、blocks.patch 与 sidebar.dataset.list 补齐 Rust 协议映射、共享契约与桥接执行器

- 对齐 BlockNote、Mindmap、OnlyOffice 的保存/路由元信息,并补真实浏览器回归脚本与 OnlyOffice 部署基线

- 忽略 Rust 本地构建产物与 Harness 调试状态文件,避免临时产物进入仓库历史
This commit is contained in:
lix-2026
2026-04-15 03:06:29 +08:00
parent 84a8454fa9
commit b33ffb99e7
51 changed files with 3260 additions and 379 deletions
@@ -38,6 +38,8 @@ export interface DocumentContentProps {
title: string | null;
updatedAt: string | null;
initialContent: unknown;
initialContentRevision?: number | null;
initialConflictDetectionKey?: string | null;
initialOptions: PageOptionsState;
initialStats: DocumentStats | null;
openTableId?: string | null;
@@ -69,6 +71,8 @@ export function DocumentContent({
title,
updatedAt,
initialContent,
initialContentRevision = null,
initialConflictDetectionKey = null,
initialOptions,
initialStats,
openTableId,
@@ -90,6 +94,8 @@ export function DocumentContent({
const openMoveEmbedPicker = useMoveEmbedPickerStore((s) => s.openPicker);
const [pageTitle, setPageTitle] = useState(title ?? "无标题");
const [content, setContent] = useState<unknown>(initialContent);
const [contentRevision, setContentRevision] = useState<number | null>(initialContentRevision);
const [conflictDetectionKey, setConflictDetectionKey] = useState<string | null>(initialConflictDetectionKey);
const [contentLoading, setContentLoading] = useState(() => initialContent == null);
const [contentError, setContentError] = useState<string | null>(null);
const [contentReloadKey, setContentReloadKey] = useState(0);
@@ -204,6 +210,14 @@ export function DocumentContent({
useEffect(() => {
setStats(initialStats ?? defaultStats);
}, [initialStats]);
useEffect(() => {
setContentRevision(initialContentRevision);
}, [initialContentRevision]);
useEffect(() => {
setConflictDetectionKey(initialConflictDetectionKey);
}, [initialConflictDetectionKey]);
useEffect(() => {
@@ -244,9 +258,23 @@ export function DocumentContent({
const payload = await response.json().catch(() => ({}));
throw new Error(payload?.error ?? "加载页面内容失败");
}
const payload = (await response.json()) as { content?: unknown };
const payload = (await response.json()) as {
content?: unknown;
revision?: number | null;
conflictDetectionKey?: string | null;
};
if (canceled) return;
setContent(payload.content ?? null);
setContentRevision(
typeof payload.revision === "number" && Number.isInteger(payload.revision)
? payload.revision
: 0,
);
setConflictDetectionKey(
typeof payload.conflictDetectionKey === "string" && payload.conflictDetectionKey.trim()
? payload.conflictDetectionKey
: `${documentId}:0`,
);
} catch (error) {
if (canceled) return;
if ((error as { name?: string })?.name === "AbortError") return;
@@ -683,11 +711,17 @@ export function DocumentContent({
documentId={documentId}
workspaceId={workspaceId}
initialContent={content}
initialRevision={contentRevision}
initialConflictDetectionKey={conflictDetectionKey}
pageOptions={options}
readOnly={readOnly}
onStatsChange={handleStatsChange}
onSnapshot={handleSnapshot}
onCloseToc={closeToc}
onPersistedMetaChange={({ revision, conflictDetectionKey: nextConflictDetectionKey }) => {
setContentRevision(revision);
setConflictDetectionKey(nextConflictDetectionKey);
}}
/>
)}
<PageBacklinksPanel