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
@@ -102,10 +102,21 @@ type MindMapInstance = {
setLayout: (layout: string) => void;
};
type MindMapData = {
data: Record<string, unknown>;
children?: unknown[];
};
type MindMapData = {
data: Record<string, unknown>;
children?: unknown[];
};
type MindmapRouteMeta = {
requestId?: string;
traceId?: string;
workspaceId?: string | null;
documentId?: string;
pageId?: string;
mindmapId?: string;
attachmentId?: string;
updatedAt?: string | null;
};
export const defaultMindmapData = {
data: { text: "中心主题" },
@@ -505,15 +516,35 @@ const MindmapBlockView = ({
return instance?.renderer?.root ?? instance?.renderer?.renderTree?._node ?? null;
}, [mindmap]);
const docId = useMemo(
() =>
block.props.docId ||
(typeof window !== "undefined"
? window.location.pathname.split("/").pop() ?? ""
: ""),
[block.props.docId],
);
const mindmapId = block.id;
const docId = useMemo(
() =>
block.props.docId ||
(typeof window !== "undefined"
? window.location.pathname.split("/").pop() ?? ""
: ""),
[block.props.docId],
);
const mindmapId = block.id;
const pageId = docId;
const attachmentId = mindmapId;
const [requestMeta, setRequestMeta] = useState<{ requestId: string; traceId: string } | null>(null);
const syncMindmapRouteMeta = useCallback((meta: unknown) => {
if (!isRecord(meta)) return;
const requestId = typeof meta.requestId === "string" ? meta.requestId.trim() : "";
const traceId = typeof meta.traceId === "string" ? meta.traceId.trim() : "";
if (requestId && traceId) {
setRequestMeta((prev) =>
prev?.requestId === requestId && prev?.traceId === traceId
? prev
: { requestId, traceId },
);
}
const nextWorkspaceId = typeof meta.workspaceId === "string" ? meta.workspaceId.trim() : "";
if (nextWorkspaceId) {
setWorkspaceId((prev) => (prev === nextWorkspaceId ? prev : nextWorkspaceId));
}
}, []);
// 获取 workspaceId(用于上传图片)
useEffect(() => {
@@ -581,12 +612,13 @@ const MindmapBlockView = ({
// 多导图:按 docId + mindmapId 拉取
const resp = await fetch(`/api/mindmap/${docId}/${mindmapId}`);
if (!resp.ok) return;
const payload = await resp.json().catch(() => null);
const data = payload?.data;
if (!data || cancelled) return;
// 若用户已在本地做过编辑,则不再用远端数据覆盖,避免“插入节点后又被重置”
if (hasLocalEditsRef.current) return;
initialDataRef.current = canonicalizeMindmapData(data);
const payload = await resp.json().catch(() => null);
const data = payload?.data;
if (!data || cancelled) return;
syncMindmapRouteMeta(payload?.meta);
// 若用户已在本地做过编辑,则不再用远端数据覆盖,避免“插入节点后又被重置”
if (hasLocalEditsRef.current) return;
initialDataRef.current = canonicalizeMindmapData(data);
if (mindmap) {
applyingRemoteRef.current = true;
try {
@@ -1388,7 +1420,13 @@ const MindmapBlockView = ({
if (resp.ok) {
try {
const payload = (await resp.json().catch(() => null)) as any;
const updatedAt = payload && typeof payload.updated_at === "string" ? payload.updated_at : null;
syncMindmapRouteMeta(payload?.meta);
const updatedAt =
payload && typeof payload.updated_at === "string"
? payload.updated_at
: payload?.meta && typeof payload.meta.updatedAt === "string"
? payload.meta.updatedAt
: null;
if (updatedAt) {
lastLocalSavedAtRef.current = updatedAt;
// 避免 Convex 订阅回放对本端“已应用的保存”重复 setData/clearHistory 导致闪烁
@@ -1465,12 +1503,15 @@ const MindmapBlockView = ({
);
(async () => {
try {
const resp = await fetch(`/api/mindmap/${docId}/${mindmapId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data, createOnly: true }),
});
const resp = await fetch(`/api/mindmap/${docId}/${mindmapId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data, createOnly: true }),
});
if (!resp.ok) {
} else {
const payload = (await resp.json().catch(() => null)) as { meta?: MindmapRouteMeta } | null;
syncMindmapRouteMeta(payload?.meta);
}
} catch {} finally {
// 即便远端失败,也通知侧边栏刷新,保证本地文件树及时更新
@@ -3155,6 +3196,13 @@ const MindmapBlockView = ({
<div
ref={wrapperRef}
data-testid="mindmap-fullscreen"
data-page-id={pageId || undefined}
data-document-id={docId || undefined}
data-attachment-id={attachmentId}
data-mindmap-id={mindmapId}
data-workspace-id={workspaceId || undefined}
data-request-id={requestMeta?.requestId}
data-trace-id={requestMeta?.traceId}
tabIndex={0}
className="fixed inset-0 z-[9999] overflow-hidden bg-white"
>
@@ -3166,9 +3214,15 @@ const MindmapBlockView = ({
ref={containerRef}
className="h-full w-full"
data-testid="mindmap-canvas"
data-mindmap-id={mindmapId}
contentEditable={false}
/>
data-page-id={pageId || undefined}
data-document-id={docId || undefined}
data-attachment-id={attachmentId}
data-mindmap-id={mindmapId}
data-workspace-id={workspaceId || undefined}
data-request-id={requestMeta?.requestId}
data-trace-id={requestMeta?.traceId}
contentEditable={false}
/>
<MindmapSidebarTrigger
activeSidebar={activeSidebar}
@@ -3212,12 +3266,19 @@ const MindmapBlockView = ({
}
return (
<div
ref={wrapperRef}
data-testid="mindmap-embed"
tabIndex={0}
className="not-prose my-4 w-full overflow-hidden rounded-xl border border-gray-200 bg-[#f8fafc] shadow-sm"
>
<div
ref={wrapperRef}
data-testid="mindmap-embed"
data-page-id={pageId || undefined}
data-document-id={docId || undefined}
data-attachment-id={attachmentId}
data-mindmap-id={mindmapId}
data-workspace-id={workspaceId || undefined}
data-request-id={requestMeta?.requestId}
data-trace-id={requestMeta?.traceId}
tabIndex={0}
className="not-prose my-4 w-full overflow-hidden rounded-xl border border-gray-200 bg-[#f8fafc] shadow-sm"
>
<div className="flex flex-col gap-3 border-b border-gray-100 bg-white px-4 py-3">
<div className="w-full">
<MindmapToolbar {...toolbarProps} />
@@ -3254,13 +3315,19 @@ const MindmapBlockView = ({
enterLocalFullscreen();
}}
>
<div
ref={containerRef}
className="h-full w-full"
data-testid="mindmap-canvas"
data-mindmap-id={mindmapId}
contentEditable={false}
/>
<div
ref={containerRef}
className="h-full w-full"
data-testid="mindmap-canvas"
data-page-id={pageId || undefined}
data-document-id={docId || undefined}
data-attachment-id={attachmentId}
data-mindmap-id={mindmapId}
data-workspace-id={workspaceId || undefined}
data-request-id={requestMeta?.requestId}
data-trace-id={requestMeta?.traceId}
contentEditable={false}
/>
<MindmapSidebarTrigger
activeSidebar={activeSidebar}