feat: continue tree rust family cutover
- add rust renderer/state-family scaffolds and inline compat host thinning for page tree, file tree, and picker - route tree/filetree preflight, file projection, resource artifact, and stream delta contracts through rust plans - preserve canonical move-order validation, file-tree search projection, and related frontend/runtime regression coverage
This commit is contained in:
@@ -4,6 +4,10 @@ import { v } from "convex/values";
|
||||
import { requireUserId } from "./_utils/auth";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import { buildParentById, collectSubtree, isAncestorOf } from "./_utils/documentTree";
|
||||
import {
|
||||
assertDocumentMoveOrderPlanMatches,
|
||||
buildDocumentMoveOrderPlanFromDocuments,
|
||||
} from "./_utils/documentMoveOrder";
|
||||
import { enqueueIngestDocumentJob } from "./_utils/ingestJobs";
|
||||
import { extractTextFromDocumentContent } from "./_utils/text";
|
||||
import {
|
||||
@@ -216,6 +220,21 @@ async function createDocumentRecord(
|
||||
};
|
||||
}
|
||||
|
||||
function toDocumentDeltaRecord(doc: any) {
|
||||
return {
|
||||
id: doc.id,
|
||||
workspace_id: doc.workspace_id,
|
||||
title: doc.title ?? null,
|
||||
parent_id: doc.parent_id ?? null,
|
||||
sort_order: doc.sort_order ?? null,
|
||||
is_starred: doc.is_starred ?? false,
|
||||
access_scope: (doc.access_scope ?? "private") as "private" | "shared" | "public",
|
||||
is_template: Boolean(doc.is_template),
|
||||
created_at: doc.created_at ?? nowIso(),
|
||||
updated_at: doc.updated_at ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async function updateDocumentContentRecord(
|
||||
ctx: any,
|
||||
args: {
|
||||
@@ -1198,7 +1217,7 @@ export const create = mutation({
|
||||
deleted_by: null,
|
||||
});
|
||||
|
||||
return {
|
||||
const document = {
|
||||
id: args.id,
|
||||
title,
|
||||
parent_id: args.parentId,
|
||||
@@ -1210,6 +1229,10 @@ export const create = mutation({
|
||||
access_scope: args.accessScope,
|
||||
is_template: false,
|
||||
};
|
||||
return {
|
||||
...document,
|
||||
document: toDocumentDeltaRecord(document),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1376,6 +1399,7 @@ export const move = mutation({
|
||||
id: v.string(),
|
||||
parentId: v.union(v.string(), v.null()),
|
||||
sortOrder: v.number(),
|
||||
normalizedMove: v.optional(v.any()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
@@ -1386,6 +1410,14 @@ export const move = mutation({
|
||||
if (doc.user_id !== userId) throw new Error("无权限");
|
||||
|
||||
const toParentId = args.parentId;
|
||||
const workspaceDocs = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_workspace", (q) => q.eq("workspace_id", doc.workspace_id))
|
||||
.collect();
|
||||
const canonicalWorkspaceDocs = pickCanonicalDocumentRecordsByBusinessId(workspaceDocs)
|
||||
.filter((row) => row.user_id === userId)
|
||||
.filter((row) => row.deleted_at == null);
|
||||
|
||||
if (toParentId === doc.id) {
|
||||
throw new Error("不能把页面移动到自身下面");
|
||||
}
|
||||
@@ -1398,19 +1430,24 @@ export const move = mutation({
|
||||
throw new Error("暂不支持跨工作空间移动页面");
|
||||
}
|
||||
|
||||
const workspaceDocs = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_workspace", (q) => q.eq("workspace_id", doc.workspace_id))
|
||||
.collect();
|
||||
const canonicalWorkspaceDocs = pickCanonicalDocumentRecordsByBusinessId(workspaceDocs)
|
||||
.filter((row) => row.user_id === userId)
|
||||
.filter((row) => row.deleted_at == null);
|
||||
const parentById = buildParentById(canonicalWorkspaceDocs);
|
||||
if (isAncestorOf(doc.id, toParentId, parentById)) {
|
||||
throw new Error("不能把页面移动到自己的后代下面");
|
||||
}
|
||||
}
|
||||
|
||||
if (args.normalizedMove != null) {
|
||||
assertDocumentMoveOrderPlanMatches(
|
||||
args.normalizedMove,
|
||||
buildDocumentMoveOrderPlanFromDocuments({
|
||||
documents: canonicalWorkspaceDocs,
|
||||
documentId: doc.id,
|
||||
parentId: toParentId,
|
||||
sortOrder: args.sortOrder,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 说明:仅更新当前节点的 sort_order 会导致兄弟节点出现重复 sort_order,
|
||||
// 前端会按 sort_order + created_at 排序,结果常常表现为“拖拽无效/拖完又回弹”。
|
||||
// 这里统一对目标父节点(以及跨父移动时的原父节点)做重新编号,保证 sort_order 唯一且连续。
|
||||
@@ -1565,7 +1602,18 @@ export const restore = mutation({
|
||||
await ctx.db.patch(item._id, { deleted_at: null, deleted_by: null, updated_at: ts });
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
return {
|
||||
ok: true,
|
||||
updated_at: ts,
|
||||
document: toDocumentDeltaRecord({
|
||||
...doc,
|
||||
deleted_at: null,
|
||||
deleted_by: null,
|
||||
parent_id: null,
|
||||
access_scope: "private",
|
||||
updated_at: ts,
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1801,8 +1849,10 @@ export const duplicate = mutation({
|
||||
title,
|
||||
parent_id: source.parent_id ?? null,
|
||||
sort_order: sortOrder,
|
||||
is_starred: false,
|
||||
workspace_id: source.workspace_id,
|
||||
access_scope: source.access_scope,
|
||||
is_template: false,
|
||||
created_at: ts,
|
||||
updated_at: ts,
|
||||
};
|
||||
@@ -1921,7 +1971,12 @@ export const copyTree = mutation({
|
||||
throw new Error("没有可复制的页面");
|
||||
}
|
||||
|
||||
const insertedDocs: Array<{ oldId: string; newId: string; title: string }> = [];
|
||||
const insertedDocs: Array<{
|
||||
oldId: string;
|
||||
newId: string;
|
||||
title: string;
|
||||
document: ReturnType<typeof toDocumentDeltaRecord>;
|
||||
}> = [];
|
||||
|
||||
for (const item of copyQueue) {
|
||||
const newId = newIdByOldId.get(item.old.id)!;
|
||||
@@ -1935,7 +1990,7 @@ export const copyTree = mutation({
|
||||
|
||||
const newTitle = makeUniqueTitle(normalizeTitle(item.old.title), titleSet);
|
||||
|
||||
await createDocumentRecord(ctx, {
|
||||
const created = await createDocumentRecord(ctx, {
|
||||
id: newId,
|
||||
workspaceId,
|
||||
parentId,
|
||||
@@ -1945,7 +2000,12 @@ export const copyTree = mutation({
|
||||
});
|
||||
|
||||
await copyMindmapsForDocument(ctx, item.old.id, newId);
|
||||
insertedDocs.push({ oldId: item.old.id, newId, title: newTitle });
|
||||
insertedDocs.push({
|
||||
oldId: item.old.id,
|
||||
newId,
|
||||
title: newTitle,
|
||||
document: toDocumentDeltaRecord(created),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user