Files
gitmost/apps/client/src/features/share/hooks/use-shared-page-subpages.ts
claude code agent 227 f7b99f9fb3 feat(editor): recursive tree mode for the subpages node (#150)
The `subpages` node showed only one level of direct children. Add a `recursive`
attribute that renders the FULL descendant tree of the current page — fully
expanded, unlimited depth. Default `false`, so every previously-inserted node
stays flat (backward compatible). No backend changes: `POST /pages/tree` (via the
`getSpaceTree` wrapper) already returns the whole subtree as a flat `IPage[]`
(recursive CTE, permission-filtered); the nested tree is built on the client by
`parentPageId`.

- editor-ext `subpages.ts`: `recursive` attribute (parse/render `data-recursive`),
  shared by client + server so the collab ProseMirror schema keeps the attribute.
- `getSpaceTree`: arg loosened to `{ spaceId?; pageId? }` (the endpoint accepts
  either); new `useGetPageTreeQuery(pageId)` react-query hook.
- `subpages-view.tsx`: split into `FlatSubpages` (unchanged) and
  `RecursiveSubpages`; `buildSubtree` assembles the nested tree (cycle/self-parent
  guard, `sortPositionKeys` per level, root excluded) and a recursive `TreeNode`
  renders it (16px indent per depth, soft "showing N" note past 300 — data never
  capped). Shared/public context reads the already-nested shared tree, no
  `/pages/tree` request.
- toggles: bubble-menu flat⇄tree button + a second slash-menu item "Page tree".

Review follow-ups folded in: invalidate `["page-tree"]` from the create / update /
move / delete cache helpers so an open recursive tree refreshes (no stale data);
mode icon made reactive on editor transactions; `t` threaded into `TreeNode`
(no per-node useTranslation); shared-subtree hook deduped to a thin alias.

editor-ext build + client `tsc --noEmit` both clean. Backend untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:33:07 +03:00

38 lines
1.4 KiB
TypeScript

import { useMemo } from "react";
import { useAtomValue } from "jotai";
import { sharedTreeDataAtom } from "@/features/share/atoms/shared-page-atom";
import { SharedPageTreeNode } from "@/features/share/utils";
export function useSharedPageSubpages(pageId: string | undefined) {
const treeData = useAtomValue(sharedTreeDataAtom);
return useMemo(() => {
if (!treeData || !pageId) return [];
function findSubpages(nodes: SharedPageTreeNode[]): SharedPageTreeNode[] {
for (const node of nodes) {
if (node.value === pageId || node.slugId === pageId) {
return node.children || [];
}
if (node.children && node.children.length > 0) {
const subpages = findSubpages(node.children);
if (subpages.length > 0) {
return subpages;
}
}
}
return [];
}
return findSubpages(treeData);
}, [treeData, pageId]);
}
// Recursive variant for the subpages node in a shared/public context. The shared
// tree (`sharedTreeDataAtom`) is ALREADY fully nested, so a page's `children`
// each carry their own nested `children` — exactly what the recursive renderer
// needs. The data is therefore identical to the flat hook; only the rendering
// differs (the recursive view walks `children` instead of showing one level).
// Thin alias to avoid duplicating the lookup. No `/pages/tree` request here.
export const useSharedPageSubtree = useSharedPageSubpages;