From cc691d11389ffa2011369320196c5854878e94ad Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 19 May 2026 02:37:14 +0100 Subject: [PATCH] fix tree --- .../template-picker-modal.module.css | 12 +++- .../src/ee/template/queries/template-query.ts | 70 +++++++++++++++++-- .../ee/template/services/template-service.ts | 5 +- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/apps/client/src/ee/template/components/template-picker-modal.module.css b/apps/client/src/ee/template/components/template-picker-modal.module.css index fc30933dc..687feb831 100644 --- a/apps/client/src/ee/template/components/template-picker-modal.module.css +++ b/apps/client/src/ee/template/components/template-picker-modal.module.css @@ -1,4 +1,5 @@ .row { + position: relative; display: flex; align-items: center; gap: var(--mantine-spacing-sm); @@ -38,12 +39,21 @@ color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2)); font-size: var(--mantine-font-size-xs); flex-shrink: 0; + transition: opacity 100ms ease; + + .row:hover &, + .row:focus-within & { + opacity: 0; + } } .useButton { + position: absolute; + top: 50%; + right: var(--mantine-spacing-sm); + transform: translateY(-50%); opacity: 0; transition: opacity 100ms ease; - flex-shrink: 0; .row:hover &, .row:focus-within &, diff --git a/apps/client/src/ee/template/queries/template-query.ts b/apps/client/src/ee/template/queries/template-query.ts index cbefb5c88..237ca94d8 100644 --- a/apps/client/src/ee/template/queries/template-query.ts +++ b/apps/client/src/ee/template/queries/template-query.ts @@ -6,6 +6,7 @@ import { UseQueryResult, InfiniteData, } from "@tanstack/react-query"; +import { useAtom, useStore } from "jotai"; import { getTemplates, getTemplateById, @@ -18,6 +19,12 @@ import { ITemplate } from "@/ee/template/types/template.types"; import { IPagination } from "@/lib/types.ts"; import { notifications } from "@mantine/notifications"; import { useTranslation } from "react-i18next"; +import { invalidateOnCreatePage } from "@/features/page/queries/page-query.ts"; +import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; +import { treeModel } from "@/features/page/tree/model/tree-model"; +import { SpaceTreeNode } from "@/features/page/tree/types.ts"; +import { IPage } from "@/features/page/types/page.types.ts"; +import { useQueryEmit } from "@/features/websocket/use-query-emit.ts"; export function useGetTemplatesQuery(params?: { spaceId?: string }) { const { spaceId } = params ?? {}; @@ -149,13 +156,64 @@ export function useDeleteTemplateMutation() { export function useUseTemplateMutation() { const { t } = useTranslation(); + const [, setTreeData] = useAtom(treeDataAtom); + const store = useStore(); + const emit = useQueryEmit(); - return useMutation({ - mutationFn: (data: { - templateId: string; - spaceId: string; - parentPageId?: string; - }) => useTemplate(data), + return useMutation< + IPage, + Error, + { templateId: string; spaceId: string; parentPageId?: string } + >({ + mutationFn: (data) => useTemplate(data), + onSuccess: (page) => { + // React Query sidebar-pages cache update (same path useCreatePageMutation takes). + invalidateOnCreatePage(page); + + const parentId = page.parentPageId ?? null; + const newNode: SpaceTreeNode = { + id: page.id, + slugId: page.slugId, + name: page.title, + icon: page.icon, + position: page.position, + spaceId: page.spaceId, + parentPageId: page.parentPageId, + hasChildren: false, + children: [], + }; + + // Only mutate the tree atom and broadcast if it currently represents + // this space. Cross-space template-use (e.g., from the gallery picking + // a different space) lets the target space's clients pick up the new + // page on their next React Query refetch (focus, navigation, etc.). + // Without this guard we'd both pollute the local tree and send a wrong + // `index` to remote clients in the target space. + const current = store.get(treeDataAtom); + const treeIsForThisSpace = current[0]?.spaceId === page.spaceId; + if (!treeIsForThisSpace) return; + + const lastIndex = + parentId === null + ? current.length + : (treeModel.find(current, parentId)?.children?.length ?? 0); + + setTreeData((prev) => + treeModel.insert(prev, parentId, newNode, lastIndex), + ); + + setTimeout(() => { + emit({ + operation: "addTreeNode", + spaceId: page.spaceId, + payload: { + parentId, + index: lastIndex, + data: newNode, + }, + }); + }, 50); + }, onError: (error) => { const errorMessage = error["response"]?.data?.message; notifications.show({ diff --git a/apps/client/src/ee/template/services/template-service.ts b/apps/client/src/ee/template/services/template-service.ts index 86bbf61d9..5bb9554e1 100644 --- a/apps/client/src/ee/template/services/template-service.ts +++ b/apps/client/src/ee/template/services/template-service.ts @@ -1,5 +1,6 @@ import api from "@/lib/api-client"; import { ITemplate } from "@/ee/template/types/template.types"; +import { IPage } from "@/features/page/types/page.types"; import { IPagination } from "@/lib/types.ts"; export async function getTemplates(params?: { @@ -40,7 +41,7 @@ export async function useTemplate(data: { templateId: string; spaceId: string; parentPageId?: string; -}): Promise { - const req = await api.post("/templates/use", data); +}): Promise { + const req = await api.post("/templates/use", data); return req.data; }