Files
mnote/wolai-frontend/convex/_utils/documentTree.ts
T
2026-04-26 04:29:23 +08:00

67 lines
1.9 KiB
TypeScript

export type ParentLinkedRow = {
id: string;
parent_id?: string | null;
};
export function buildParentById<T extends ParentLinkedRow>(rows: readonly T[]): Map<string, string | null> {
const map = new Map<string, string | null>();
for (const row of rows) {
map.set(row.id, row.parent_id ?? null);
}
return map;
}
export function isAncestorOf(
ancestorId: string,
nodeId: string,
parentById: Map<string, string | null>,
): boolean {
let current: string | null | undefined = nodeId;
while (current) {
const parentId = parentById.get(current);
if (!parentId) return false;
if (parentId === ancestorId) return true;
current = parentId;
}
return false;
}
/**
* 收集以 rootId 为根的整棵子树(包含根节点本身)。
*
* 说明:
* - 仅依赖 (id, parent_id) 字段,便于在 Convex 与前端共用同一套遍历逻辑。
* - 会做去重与环检测,避免异常数据导致死循环。
*/
export function collectSubtree<T extends ParentLinkedRow>(rows: readonly T[], rootId: string): T[] {
const byParentId = new Map<string | null, T[]>();
for (const row of rows) {
const parentId = (row.parent_id ?? null) as string | null;
const bucket = byParentId.get(parentId);
if (bucket) bucket.push(row);
else byParentId.set(parentId, [row]);
}
const root = rows.find((r) => r.id === rootId);
if (!root) return [];
const visited = new Set<string>();
const stack: T[] = [root];
const result: T[] = [];
while (stack.length > 0) {
const current = stack.pop()!;
if (visited.has(current.id)) continue;
visited.add(current.id);
result.push(current);
const children = byParentId.get(current.id) ?? [];
// 倒序入栈,保持更接近“原列表顺序”的遍历结果(不影响正确性)
for (let i = children.length - 1; i >= 0; i -= 1) {
stack.push(children[i]!);
}
}
return result;
}