[feature][editor] Рекурсивная нода subpages: дерево всех страниц-детей текущей страницы #150
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Кратко
Нужна нода редактора, которую можно вставить на страницу, и она рекурсивно показывает всё дерево потомков текущей страницы (а не один уровень).
Решения по дизайну (согласованы):
subpages(а не плодим новую), добавляя режим «дерево»;Текущее поведение
Нода
subpagesуже существует и показывает только один уровень прямых детей:packages/editor-ext/src/lib/subpages/subpages.ts—atom/block/draggable/isolating, без атрибутов (SubpagesAttributes {}), командаinsertSubpages,ReactNodeViewRenderer(this.options.view).apps/client/src/features/editor/components/subpages/subpages-view.tsx— берётeditor.storage.pageId, тянет прямых детей черезuseGetSidebarPagesQuery({ pageId })(эндпоинт/pages/sidebar-pages, плоский список) и рисует плоский<Stack>ссылок. Для шаринга —useSharedPageSubpages(pageId)(один уровень изsharedTreeDataAtom).apps/client/src/features/editor/extensions/extensions.ts:389(Subpages.configure({ view: SubpagesView })), слэш-менюapps/client/src/features/editor/components/slash-menu/menu-items.ts:512, тулбарapps/client/src/features/editor/components/fixed-toolbar/groups/more-inserts-group.tsx:91, bubble-меню удаленияapps/client/src/features/editor/components/subpages/subpages-menu.tsx(подключено вapps/client/src/features/editor/page-editor.tsx:507).apps/server/src/collaboration/collaboration.util.ts:37,115— нужно, чтобы ProseMirror-схема на сервере не вырезала ноду.Ключевая находка: бэкенд уже умеет отдавать всё поддерево
POST /pages/tree→apps/server/src/core/page/page.controller.ts:582→pageService.getSidebarPagesTree(spaceId, userId, spaceCanEdit, pageId). При переданномpageIdон зовётgetPageAndDescendants(pageId)— рекурсивный SQL CTE (apps/server/src/database/repos/page/page.repo.ts:629), возвращающий плоский список «страница + все потомки» с учётом прав (рестриктнутые поддеревья отсекаются) и полямиhasChildren/canEdit(apps/server/src/core/page/services/sidebar-pages-tree.util.ts).getSpaceTree({ spaceId, pageId })→apps/client/src/features/page/services/page-service.ts:95(POST /pages/tree, возвращаетIPage[]). Сейчас используется только для «expand all» в сайдбаре; react-query-хука для неё пока нет.Вывод: рекурсивный режим строится поверх готовой инфраструктуры — изменений бэкенда не требуется. Один запрос
/pages/treeсpageIdотдаёт всё поддерево без N+1; вложенное дерево из плоского списка собираем на клиенте поparentPageId.Объём изменений
1. Схема ноды — добавить атрибут
recursivepackages/editor-ext/src/lib/subpages/subpages.ts. ДобавитьaddAttributesи прокинуть атрибут вparseHTML/renderHTML, чтобы он персистился в документе и переживал HTML-раунд-трип:Команда
insertSubpagesуже принимаетattributes— вызовinsertSubpages({ recursive: true })работает без изменений сигнатуры. Нода импортируется из общего пакета@docmost/editor-extи на клиенте, и на сервере — одно объявление атрибута покрывает обе стороны.2. Хук данных — react-query для поддерева
В
apps/client/src/features/page/queries/page-query.tsдобавить хук поверхgetSpaceTree:Тип
getSpaceTreeсейчас требуетspaceId(apps/client/src/features/page/services/page-service.ts:95), хотя эндпоинт принимает либоspaceId, либоpageId. Нужно ослабить тип до{ spaceId?: string; pageId?: string }(минимальная правка).3. View — ветвление flat/recursive + рекурсивный рендер
apps/client/src/features/editor/components/subpages/subpages-view.tsx:recursive === false→ текущий код без изменений.recursive === true→useGetPageTreeQuery(currentPageId), из плоскогоIPage[]собрать вложенное дерево поparentPageId, найти детей текущей страницы и рендерить рекурсивным под-компонентом. Сама корневая страница (первый элемент ответа) в вывод не попадает — показываем только её потомков.Сортировка на каждом уровне — через существующий
sortPositionKeys(apps/client/src/features/page/tree/utils/utils.ts:4). Разметка ссылки/иконки и состоянияisLoading/error/«No subpages» переиспользуются из текущего view.4. Шаринг / публичные страницы
apps/client/src/features/share/hooks/use-shared-page-subpages.tsсейчас возвращает только прямых детей изsharedTreeDataAtom. Для рекурсива добавить вариант, возвращающий узел целиком с егоchildren(вsharedTreeDataAtomдерево уже вложенное), и в recursive-ветке рендерить его тем жеTreeNode. В публичном контексте поддерево берётся из уже загруженного shared-дерева, без обращения к/pages/tree.5. Переключатель режима
subpages-menu.tsx): кнопка-тоггл «плоский список ⇄ дерево»:editor.commands.updateAttributes("subpages", { recursive: !current }).menu-items.ts:511): оставить текущий пункт (вставляет плоский,recursive:false) и добавить второй для обнаружимости — «Page tree (child pages, recursive)», командаinsertSubpages({ recursive: true }). Аналогично можно добавить пункт в тулбар.Что НЕ требуется
/pages/treeуже всё умеет).Subpagesуже там; добавление атрибута схему не ломает).Краевые случаи и риски
recursiveпо умолчаниюfalse→ все ранее вставленные ноды остаются плоскими. Поведение не меняется без явного включения.> Nузлов показывать счётчик/пометку, не ограничивая данные.parentPageId), но сборку на клиенте делаем с защитой (visited/p.id !== rootId)./pages/treeуже отсекает рестриктнутые поддеревья на сервере — пользователь не увидит недоступное (даже строже плоского/pages/sidebar-pages).subpages-нода динамическая и в экспорт не сериализуется (серверного сериализатора нет). Рекурсивный режим унаследует это: в экспортированном файле дерева не будет. Если экспорт дерева нужен — отдельная задача.["page-tree", pageId]в соответствующих мутациях, иначе нода покажет устаревший список до рефетча.pageId. View опирается наeditor.storage.pageId(apps/client/src/features/editor/page-editor.tsx:341) — нода привязана к текущей странице, что и требуется.Оценка объёма
~6 файлов на клиенте + 1 атрибут в общем пакете
editor-ext; бэкенд без изменений.