feat: 收口文档桥接与 OnlyOffice/Sidebar 回归
- 为 documents.save/meta/content、blocks.patch 与 sidebar.dataset.list 补齐 Rust 协议映射、共享契约与桥接执行器 - 对齐 BlockNote、Mindmap、OnlyOffice 的保存/路由元信息,并补真实浏览器回归脚本与 OnlyOffice 部署基线 - 忽略 Rust 本地构建产物与 Harness 调试状态文件,避免临时产物进入仓库历史
This commit is contained in:
@@ -1,18 +1,27 @@
|
||||
import { useMemo } from "react";
|
||||
import { useQuery, useConvexAuth } from "convex/react";
|
||||
import type { FunctionReference } from "convex/server";
|
||||
import type { SidebarInitialData } from "@/components/sidebar/types";
|
||||
import type { MediaAsset } from "@/types/media";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { buildSidebarInitialData } from "@/lib/sidebar-data";
|
||||
import {
|
||||
mapSidebarDatasetListQueryResultToInitialData,
|
||||
type SidebarDatasetListQueryResult,
|
||||
} from "@/lib/sidebar-data";
|
||||
import { toBrowserAccessibleUrl } from "@/lib/url/browser-file-url";
|
||||
|
||||
type CurrentUserRecord = {
|
||||
_id?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convex 模式下的侧边栏数据 hook
|
||||
* 使用 Convex 的 useQuery 实现实时订阅,无需手动 refetch
|
||||
const sidebarDatasetListQuery = ((api as unknown as Record<string, unknown>).sidebar as
|
||||
| Record<string, unknown>
|
||||
| undefined)?.datasetList as FunctionReference<
|
||||
"query",
|
||||
"public",
|
||||
{ workspaceId: string },
|
||||
SidebarDatasetListQueryResult
|
||||
>;
|
||||
|
||||
/**
|
||||
* Convex 模式下的侧边栏数据 hook
|
||||
* 使用 Convex 的 useQuery 实现实时订阅,无需手动 refetch
|
||||
*
|
||||
* 注意:此 hook 仅应在 Convex 模式下使用
|
||||
* 后端从 ctx.auth.getUserIdentity() 获取用户身份,前端无需传入 userId
|
||||
@@ -27,51 +36,9 @@ export function useConvexSidebarData(workspaceId: string): {
|
||||
|
||||
// 显式转为 boolean,确保类型正确
|
||||
const shouldFetch = Boolean(isAuthenticated && workspaceId);
|
||||
const currentUser = useQuery(api.users.currentUser, shouldFetch ? {} : "skip");
|
||||
const currentUserRecord =
|
||||
currentUser && typeof currentUser === "object" ? (currentUser as CurrentUserRecord) : null;
|
||||
const userId =
|
||||
currentUserRecord && typeof currentUserRecord._id === "string"
|
||||
? currentUserRecord._id
|
||||
: "";
|
||||
|
||||
const shouldFetchAuthed = Boolean(shouldFetch && userId);
|
||||
|
||||
// 使用 Convex 的 useQuery,自动订阅实时更新
|
||||
// 后端从 ctx.auth.getUserIdentity() 获取用户身份,无需前端传入 userId
|
||||
const documents = useQuery(
|
||||
api.documents.listByWorkspace,
|
||||
shouldFetch ? { workspaceId } : "skip"
|
||||
);
|
||||
|
||||
const trashedDocuments = useQuery(
|
||||
api.documents.listTrashedByWorkspace,
|
||||
shouldFetch ? { workspaceId } : "skip"
|
||||
);
|
||||
|
||||
const mindmaps = useQuery(
|
||||
api.mindmaps.listByWorkspace,
|
||||
shouldFetch ? { workspaceId, includeDeleted: true } : "skip"
|
||||
);
|
||||
|
||||
const mediaAssets = useQuery(
|
||||
api.mediaAssets.listByWorkspace,
|
||||
shouldFetchAuthed ? { userId, workspaceId, limit: 200 } : "skip",
|
||||
);
|
||||
|
||||
const trashedMediaAssets = useQuery(
|
||||
api.mediaAssets.listDeletedByWorkspace,
|
||||
shouldFetchAuthed ? { userId, workspaceId, limit: 2000 } : "skip",
|
||||
);
|
||||
|
||||
const tables = useQuery(
|
||||
api.tables.listByWorkspaceForSearch,
|
||||
shouldFetchAuthed ? { userId, workspaceId, includeArchived: true, limit: 3000 } : "skip",
|
||||
);
|
||||
|
||||
const workspacesResult = useQuery(
|
||||
api.workspaces.fetchWorkspaceSummaries,
|
||||
shouldFetch ? {} : "skip",
|
||||
const sidebarDataset = useQuery(
|
||||
sidebarDatasetListQuery,
|
||||
shouldFetch ? { workspaceId } : "skip",
|
||||
);
|
||||
|
||||
const normalizeAssetUrls = (asset: MediaAsset): MediaAsset => {
|
||||
@@ -80,54 +47,39 @@ export function useConvexSidebarData(workspaceId: string): {
|
||||
return { ...asset, file_url: fileUrl, thumbnail_url: thumbUrl };
|
||||
};
|
||||
|
||||
// 组合数据,格式与 SidebarInitialData 一致
|
||||
const data: SidebarInitialData | null = useMemo(() => {
|
||||
// 当 skip 时,返回值是 undefined
|
||||
if (currentUser === undefined ||
|
||||
documents === undefined ||
|
||||
trashedDocuments === undefined ||
|
||||
mindmaps === undefined ||
|
||||
mediaAssets === undefined ||
|
||||
trashedMediaAssets === undefined ||
|
||||
tables === undefined ||
|
||||
workspacesResult === undefined) {
|
||||
const normalizedSidebarDataset = useMemo<SidebarDatasetListQueryResult | null>(() => {
|
||||
if (sidebarDataset === undefined || !sidebarDataset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return buildSidebarInitialData({
|
||||
activeWorkspaceId: workspacesResult.activeWorkspaceId || workspaceId,
|
||||
workspaces: workspacesResult.workspaces,
|
||||
documents,
|
||||
trashedDocuments,
|
||||
mindmaps,
|
||||
mediaAssets: ((mediaAssets ?? []) as MediaAsset[]).map(normalizeAssetUrls),
|
||||
trashedMediaAssets: ((trashedMediaAssets ?? []) as MediaAsset[]).map(normalizeAssetUrls),
|
||||
tables,
|
||||
});
|
||||
}, [
|
||||
currentUser,
|
||||
documents,
|
||||
trashedDocuments,
|
||||
mindmaps,
|
||||
mediaAssets,
|
||||
trashedMediaAssets,
|
||||
tables,
|
||||
workspacesResult,
|
||||
workspaceId,
|
||||
]);
|
||||
return {
|
||||
...sidebarDataset,
|
||||
media_assets: ((sidebarDataset.media_assets ?? []) as MediaAsset[]).map(normalizeAssetUrls),
|
||||
trashed_media_assets: ((sidebarDataset.trashed_media_assets ?? []) as MediaAsset[]).map(
|
||||
normalizeAssetUrls,
|
||||
),
|
||||
mindmap_assets: ((sidebarDataset.mindmap_assets ?? []) as MediaAsset[]).map(normalizeAssetUrls),
|
||||
trashed_mindmap_assets: ((sidebarDataset.trashed_mindmap_assets ?? []) as MediaAsset[]).map(
|
||||
normalizeAssetUrls,
|
||||
),
|
||||
table_assets: ((sidebarDataset.table_assets ?? []) as MediaAsset[]).map(normalizeAssetUrls),
|
||||
trashed_table_assets: ((sidebarDataset.trashed_table_assets ?? []) as MediaAsset[]).map(
|
||||
normalizeAssetUrls,
|
||||
),
|
||||
};
|
||||
}, [sidebarDataset]);
|
||||
|
||||
// 组合数据,格式与 SidebarInitialData 一致
|
||||
const data: SidebarInitialData | null = useMemo(() => {
|
||||
if (!normalizedSidebarDataset) {
|
||||
return null;
|
||||
}
|
||||
return mapSidebarDatasetListQueryResultToInitialData(normalizedSidebarDataset);
|
||||
}, [normalizedSidebarDataset]);
|
||||
|
||||
// loading 状态:只有当 shouldFetch 为 true 且数据未加载时才算 loading
|
||||
// 使用 === undefined 判断,因为 skip 时返回 undefined
|
||||
const isLoading = shouldFetch && (
|
||||
currentUser === undefined ||
|
||||
documents === undefined ||
|
||||
trashedDocuments === undefined ||
|
||||
mindmaps === undefined ||
|
||||
mediaAssets === undefined ||
|
||||
trashedMediaAssets === undefined ||
|
||||
tables === undefined ||
|
||||
workspacesResult === undefined
|
||||
);
|
||||
const isLoading = shouldFetch && sidebarDataset === undefined;
|
||||
const error = null;
|
||||
|
||||
// Convex 模式下数据自动实时同步,refetch 是空操作
|
||||
|
||||
Reference in New Issue
Block a user