feat: 收口文档桥接与 OnlyOffice/Sidebar 回归
- 为 documents.save/meta/content、blocks.patch 与 sidebar.dataset.list 补齐 Rust 协议映射、共享契约与桥接执行器 - 对齐 BlockNote、Mindmap、OnlyOffice 的保存/路由元信息,并补真实浏览器回归脚本与 OnlyOffice 部署基线 - 忽略 Rust 本地构建产物与 Harness 调试状态文件,避免临时产物进入仓库历史
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user