refactor(tree): borrow cleanups from the sibling expand-all impl

- extract collectAllIds / collectBranchIds into tree/utils and use them in
  space-tree.tsx instead of inline closures
- drop the duplicate SidebarPageTreeDto, reuse the existing SidebarPageDto
  for the /pages/tree endpoint
- type the getSpaceTree client call as api.post<{ items: IPage[] }>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
vvzvlad
2026-06-20 17:39:34 +03:00
parent b38b71eb51
commit 234ae759f5
5 changed files with 37 additions and 31 deletions

View File

@@ -96,7 +96,7 @@ export async function getSpaceTree(params: {
spaceId: string; spaceId: string;
pageId?: string; pageId?: string;
}): Promise<IPage[]> { }): Promise<IPage[]> {
const req = await api.post("/pages/tree", params); const req = await api.post<{ items: IPage[] }>("/pages/tree", params);
return req.data.items; return req.data.items;
} }

View File

@@ -25,6 +25,8 @@ import {
buildTree, buildTree,
buildTreeWithChildren, buildTreeWithChildren,
mergeRootTrees, mergeRootTrees,
collectAllIds,
collectBranchIds,
} from "@/features/page/tree/utils/utils.ts"; } from "@/features/page/tree/utils/utils.ts";
import { SpaceTreeNode } from "@/features/page/tree/types.ts"; import { SpaceTreeNode } from "@/features/page/tree/types.ts";
import { treeModel } from "@/features/page/tree/model/tree-model"; import { treeModel } from "@/features/page/tree/model/tree-model";
@@ -226,16 +228,7 @@ const SpaceTree = forwardRef<SpaceTreeApi, SpaceTreeProps>(function SpaceTree(
}); });
// Open every branch node (node with children) of the current space only. // Open every branch node (node with children) of the current space only.
const branchIds: string[] = []; const branchIds = collectBranchIds(fullTree);
const collectBranchIds = (nodes: SpaceTreeNode[]) => {
for (const n of nodes) {
if (n.children && n.children.length > 0) {
branchIds.push(n.id);
collectBranchIds(n.children);
}
}
};
collectBranchIds(fullTree);
setOpenTreeNodes((prev) => { setOpenTreeNodes((prev) => {
const next = { ...prev }; const next = { ...prev };
@@ -260,14 +253,7 @@ const SpaceTree = forwardRef<SpaceTreeApi, SpaceTreeProps>(function SpaceTree(
const collapseAll = useCallback(() => { const collapseAll = useCallback(() => {
// The open-map is shared across spaces; collapse only current-space ids so // The open-map is shared across spaces; collapse only current-space ids so
// other spaces' expanded state is left intact. // other spaces' expanded state is left intact.
const ids = new Set<string>(); const ids = collectAllIds(filteredData);
const walk = (nodes: SpaceTreeNode[]) => {
for (const n of nodes) {
ids.add(n.id);
if (n.children?.length) walk(n.children);
}
};
walk(filteredData);
setOpenTreeNodes((prev) => { setOpenTreeNodes((prev) => {
const next = { ...prev }; const next = { ...prev };

View File

@@ -216,3 +216,33 @@ export function mergeRootTrees(
return sortPositionKeys(merged); return sortPositionKeys(merged);
} }
// Collect every node id in the tree (roots, branches, leaves). Used by
// collapseAll to clear the open-state map for all current-space nodes.
export function collectAllIds(nodes: SpaceTreeNode[]): string[] {
const ids: string[] = [];
const walk = (list: SpaceTreeNode[]) => {
for (const n of list) {
ids.push(n.id);
if (n.children?.length) walk(n.children);
}
};
walk(nodes);
return ids;
}
// Collect ids of branch nodes (nodes that have children). Used by expandAll to
// open every branch in the open-state map; leaves need no entry.
export function collectBranchIds(nodes: SpaceTreeNode[]): string[] {
const ids: string[] = [];
const walk = (list: SpaceTreeNode[]) => {
for (const n of list) {
if (n.children?.length) {
ids.push(n.id);
walk(n.children);
}
}
};
walk(nodes);
return ids;
}

View File

@@ -9,13 +9,3 @@ export class SidebarPageDto {
@IsString() @IsString()
pageId: string; pageId: string;
} }
export class SidebarPageTreeDto {
@IsOptional()
@IsUUID()
spaceId?: string;
@IsOptional()
@IsString()
pageId?: string;
}

View File

@@ -32,7 +32,7 @@ import {
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
import { Page, User, Workspace } from '@docmost/db/types/entity.types'; import { Page, User, Workspace } from '@docmost/db/types/entity.types';
import { SidebarPageDto, SidebarPageTreeDto } from './dto/sidebar-page.dto'; import { SidebarPageDto } from './dto/sidebar-page.dto';
import { import {
SpaceCaslAction, SpaceCaslAction,
SpaceCaslSubject, SpaceCaslSubject,
@@ -581,7 +581,7 @@ export class PageController {
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post('/tree') @Post('/tree')
async getPagesTree( async getPagesTree(
@Body() dto: SidebarPageTreeDto, @Body() dto: SidebarPageDto,
@AuthUser() user: User, @AuthUser() user: User,
) { ) {
if (!dto.spaceId && !dto.pageId) { if (!dto.spaceId && !dto.pageId) {