mirror of
https://github.com/docmost/docmost.git
synced 2025-11-16 02:11:10 +10:00
Merge branch 'main' into feat/new-imports
This commit is contained in:
@ -103,7 +103,7 @@ export function TitleEditor({
|
|||||||
spaceId: page.spaceId,
|
spaceId: page.spaceId,
|
||||||
entity: ["pages"],
|
entity: ["pages"],
|
||||||
id: page.id,
|
id: page.id,
|
||||||
payload: { title: page.title, slugId: page.slugId },
|
payload: { title: page.title, slugId: page.slugId, parentPageId: page.parentPageId, icon: page.icon },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (page.title !== titleEditor.getText()) return;
|
if (page.title !== titleEditor.getText()) return;
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
|
InfiniteData,
|
||||||
|
QueryKey,
|
||||||
useInfiniteQuery,
|
useInfiniteQuery,
|
||||||
|
UseInfiniteQueryResult,
|
||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
@ -14,6 +17,7 @@ import {
|
|||||||
movePage,
|
movePage,
|
||||||
getPageBreadcrumbs,
|
getPageBreadcrumbs,
|
||||||
getRecentChanges,
|
getRecentChanges,
|
||||||
|
getAllSidebarPages,
|
||||||
} from "@/features/page/services/page-service";
|
} from "@/features/page/services/page-service";
|
||||||
import {
|
import {
|
||||||
IMovePage,
|
IMovePage,
|
||||||
@ -56,7 +60,9 @@ export function useCreatePageMutation() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return useMutation<IPage, Error, Partial<IPageInput>>({
|
return useMutation<IPage, Error, Partial<IPageInput>>({
|
||||||
mutationFn: (data) => createPage(data),
|
mutationFn: (data) => createPage(data),
|
||||||
onSuccess: (data) => {},
|
onSuccess: (data) => {
|
||||||
|
invalidateOnCreatePage(data);
|
||||||
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({ message: t("Failed to create page"), color: "red" });
|
notifications.show({ message: t("Failed to create page"), color: "red" });
|
||||||
},
|
},
|
||||||
@ -80,6 +86,8 @@ export function updatePageData(data: IPage) {
|
|||||||
if (pageById) {
|
if (pageById) {
|
||||||
queryClient.setQueryData(["pages", data.id], { ...pageById, ...data });
|
queryClient.setQueryData(["pages", data.id], { ...pageById, ...data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invalidateOnUpdatePage(data.spaceId, data.parentPageId, data.id, data.title, data.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUpdateTitlePageMutation() {
|
export function useUpdateTitlePageMutation() {
|
||||||
@ -93,6 +101,8 @@ export function useUpdatePageMutation() {
|
|||||||
mutationFn: (data) => updatePage(data),
|
mutationFn: (data) => updatePage(data),
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
updatePage(data);
|
updatePage(data);
|
||||||
|
|
||||||
|
invalidateOnUpdatePage(data.spaceId, data.parentPageId, data.id, data.title, data.icon);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -101,8 +111,9 @@ export function useDeletePageMutation() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (pageId: string) => deletePage(pageId),
|
mutationFn: (pageId: string) => deletePage(pageId),
|
||||||
onSuccess: () => {
|
onSuccess: (data, pageId) => {
|
||||||
notifications.show({ message: t("Page deleted successfully") });
|
notifications.show({ message: t("Page deleted successfully") });
|
||||||
|
invalidateOnDeletePage(pageId);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({ message: t("Failed to delete page"), color: "red" });
|
notifications.show({ message: t("Failed to delete page"), color: "red" });
|
||||||
@ -113,15 +124,21 @@ export function useDeletePageMutation() {
|
|||||||
export function useMovePageMutation() {
|
export function useMovePageMutation() {
|
||||||
return useMutation<void, Error, IMovePage>({
|
return useMutation<void, Error, IMovePage>({
|
||||||
mutationFn: (data) => movePage(data),
|
mutationFn: (data) => movePage(data),
|
||||||
|
onSuccess: () => {
|
||||||
|
invalidateOnMovePage();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetSidebarPagesQuery(
|
export function useGetSidebarPagesQuery(data: SidebarPagesParams|null): UseInfiniteQueryResult<InfiniteData<IPagination<IPage>, unknown>> {
|
||||||
data: SidebarPagesParams,
|
return useInfiniteQuery({
|
||||||
): UseQueryResult<IPagination<IPage>, Error> {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["sidebar-pages", data],
|
queryKey: ["sidebar-pages", data],
|
||||||
queryFn: () => getSidebarPages(data),
|
queryFn: ({ pageParam }) => getSidebarPages({ ...data, page: pageParam }),
|
||||||
|
initialPageParam: 1,
|
||||||
|
getPreviousPageParam: (firstPage) =>
|
||||||
|
firstPage.meta.hasPrevPage ? firstPage.meta.page - 1 : undefined,
|
||||||
|
getNextPageParam: (lastPage) =>
|
||||||
|
lastPage.meta.hasNextPage ? lastPage.meta.page + 1 : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,14 +166,16 @@ export function usePageBreadcrumbsQuery(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAncestorChildren(params: SidebarPagesParams) {
|
export async function fetchAllAncestorChildren(params: SidebarPagesParams) {
|
||||||
// not using a hook here, so we can call it inside a useEffect hook
|
// not using a hook here, so we can call it inside a useEffect hook
|
||||||
const response = await queryClient.fetchQuery({
|
const response = await queryClient.fetchQuery({
|
||||||
queryKey: ["sidebar-pages", params],
|
queryKey: ["sidebar-pages", params],
|
||||||
queryFn: () => getSidebarPages(params),
|
queryFn: () => getAllSidebarPages(params),
|
||||||
staleTime: 30 * 60 * 1000,
|
staleTime: 30 * 60 * 1000,
|
||||||
});
|
});
|
||||||
return buildTree(response.items);
|
|
||||||
|
const allItems = response.pages.flatMap((page) => page.items);
|
||||||
|
return buildTree(allItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRecentChangesQuery(
|
export function useRecentChangesQuery(
|
||||||
@ -168,3 +187,157 @@ export function useRecentChangesQuery(
|
|||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function invalidateOnCreatePage(data: Partial<IPage>) {
|
||||||
|
const newPage: Partial<IPage> = {
|
||||||
|
creatorId: data.creatorId,
|
||||||
|
hasChildren: data.hasChildren,
|
||||||
|
icon: data.icon,
|
||||||
|
id: data.id,
|
||||||
|
parentPageId: data.parentPageId,
|
||||||
|
position: data.position,
|
||||||
|
slugId: data.slugId,
|
||||||
|
spaceId: data.spaceId,
|
||||||
|
title: data.title,
|
||||||
|
};
|
||||||
|
|
||||||
|
let queryKey: QueryKey = null;
|
||||||
|
if (data.parentPageId===null) {
|
||||||
|
queryKey = ['root-sidebar-pages', data.spaceId];
|
||||||
|
}else{
|
||||||
|
queryKey = ['sidebar-pages', {pageId: data.parentPageId, spaceId: data.spaceId}]
|
||||||
|
}
|
||||||
|
|
||||||
|
//update all sidebar pages
|
||||||
|
queryClient.setQueryData<InfiniteData<IPagination<Partial<IPage>>>>(queryKey, (old) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
pages: old.pages.map((page,index) => {
|
||||||
|
if (index === old.pages.length - 1) {
|
||||||
|
return {
|
||||||
|
...page,
|
||||||
|
items: [...page.items, newPage],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//update sidebar haschildren
|
||||||
|
if (data.parentPageId!==null){
|
||||||
|
//update sub sidebar pages haschildern
|
||||||
|
const subSideBarMatches = queryClient.getQueriesData({
|
||||||
|
queryKey: ['sidebar-pages'],
|
||||||
|
exact: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
subSideBarMatches.forEach(([key, d]) => {
|
||||||
|
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(key, (old) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
pages: old.pages.map((page) => ({
|
||||||
|
...page,
|
||||||
|
items: page.items.map((sidebarPage: IPage) =>
|
||||||
|
sidebarPage.id === data.parentPageId ? { ...sidebarPage, hasChildren: true } : sidebarPage
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//update root sidebar pages haschildern
|
||||||
|
const rootSideBarMatches = queryClient.getQueriesData({
|
||||||
|
queryKey: ['root-sidebar-pages', data.spaceId],
|
||||||
|
exact: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
rootSideBarMatches.forEach(([key, d]) => {
|
||||||
|
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(key, (old) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
pages: old.pages.map((page) => ({
|
||||||
|
...page,
|
||||||
|
items: page.items.map((sidebarPage: IPage) =>
|
||||||
|
sidebarPage.id === data.parentPageId ? { ...sidebarPage, hasChildren: true } : sidebarPage
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//update recent changes
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["recent-changes", data.spaceId],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invalidateOnUpdatePage(spaceId: string, parentPageId: string, id: string, title: string, icon: string) {
|
||||||
|
let queryKey: QueryKey = null;
|
||||||
|
if(parentPageId===null){
|
||||||
|
queryKey = ['root-sidebar-pages', spaceId];
|
||||||
|
}else{
|
||||||
|
queryKey = ['sidebar-pages', {pageId: parentPageId, spaceId: spaceId}]
|
||||||
|
}
|
||||||
|
//update all sidebar pages
|
||||||
|
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(queryKey, (old) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
pages: old.pages.map((page) => ({
|
||||||
|
...page,
|
||||||
|
items: page.items.map((sidebarPage: IPage) =>
|
||||||
|
sidebarPage.id === id ? { ...sidebarPage, title: title, icon: icon } : sidebarPage
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//update recent changes
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["recent-changes", spaceId],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invalidateOnMovePage() {
|
||||||
|
//for move invalidate all sidebars for now (how to do???)
|
||||||
|
//invalidate all root sidebar pages
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["root-sidebar-pages"],
|
||||||
|
});
|
||||||
|
//invalidate all sub sidebar pages
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['sidebar-pages'],
|
||||||
|
});
|
||||||
|
// ---
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invalidateOnDeletePage(pageId: string) {
|
||||||
|
//update all sidebar pages
|
||||||
|
const allSideBarMatches = queryClient.getQueriesData({
|
||||||
|
predicate: (query) =>
|
||||||
|
query.queryKey[0] === 'root-sidebar-pages' || query.queryKey[0] === 'sidebar-pages',
|
||||||
|
});
|
||||||
|
|
||||||
|
allSideBarMatches.forEach(([key, d]) => {
|
||||||
|
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(key, (old) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
pages: old.pages.map((page) => ({
|
||||||
|
...page,
|
||||||
|
items: page.items.filter((sidebarPage: IPage) => sidebarPage.id !== pageId),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//update recent changes
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["recent-changes"],
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
} from '@/features/page/types/page.types';
|
} from '@/features/page/types/page.types';
|
||||||
import { IAttachment, IPagination } from "@/lib/types.ts";
|
import { IAttachment, IPagination } from "@/lib/types.ts";
|
||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
|
import { InfiniteData } from "@tanstack/react-query";
|
||||||
import { IFileTask } from '@/features/file-task/types/file-task.types.ts';
|
import { IFileTask } from '@/features/file-task/types/file-task.types.ts';
|
||||||
|
|
||||||
export async function createPage(data: Partial<IPage>): Promise<IPage> {
|
export async function createPage(data: Partial<IPage>): Promise<IPage> {
|
||||||
@ -53,6 +54,32 @@ export async function getSidebarPages(
|
|||||||
return req.data;
|
return req.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAllSidebarPages(
|
||||||
|
params: SidebarPagesParams,
|
||||||
|
): Promise<InfiniteData<IPagination<IPage>, unknown>> {
|
||||||
|
let page = 1;
|
||||||
|
let hasNextPage = false;
|
||||||
|
const pages: IPagination<IPage>[] = [];
|
||||||
|
const pageParams: number[] = [];
|
||||||
|
|
||||||
|
do {
|
||||||
|
const req = await api.post("/pages/sidebar-pages", { ...params, page: page });
|
||||||
|
|
||||||
|
const data: IPagination<IPage> = req.data;
|
||||||
|
pages.push(data);
|
||||||
|
pageParams.push(page);
|
||||||
|
|
||||||
|
hasNextPage = data.meta.hasNextPage;
|
||||||
|
|
||||||
|
page += 1;
|
||||||
|
} while (hasNextPage);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pageParams,
|
||||||
|
pages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPageBreadcrumbs(
|
export async function getPageBreadcrumbs(
|
||||||
pageId: string,
|
pageId: string,
|
||||||
): Promise<Partial<IPage[]>> {
|
): Promise<Partial<IPage[]>> {
|
||||||
|
|||||||
@ -1,4 +1,19 @@
|
|||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
import { SpaceTreeNode } from "@/features/page/tree/types";
|
import { SpaceTreeNode } from "@/features/page/tree/types";
|
||||||
|
import { appendNodeChildren } from "../utils";
|
||||||
|
|
||||||
export const treeDataAtom = atom<SpaceTreeNode[]>([]);
|
export const treeDataAtom = atom<SpaceTreeNode[]>([]);
|
||||||
|
|
||||||
|
// Atom
|
||||||
|
export const appendNodeChildrenAtom = atom(
|
||||||
|
null,
|
||||||
|
(
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
{ parentId, children }: { parentId: string; children: SpaceTreeNode[] }
|
||||||
|
) => {
|
||||||
|
const currentTree = get(treeDataAtom);
|
||||||
|
const updatedTree = appendNodeChildren(currentTree, parentId, children);
|
||||||
|
set(treeDataAtom, updatedTree);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { NodeApi, NodeRendererProps, Tree, TreeApi } from "react-arborist";
|
|||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts";
|
import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts";
|
||||||
import {
|
import {
|
||||||
fetchAncestorChildren,
|
fetchAllAncestorChildren,
|
||||||
useGetRootSidebarPagesQuery,
|
useGetRootSidebarPagesQuery,
|
||||||
usePageQuery,
|
usePageQuery,
|
||||||
useUpdatePageMutation,
|
useUpdatePageMutation,
|
||||||
@ -24,7 +24,7 @@ import {
|
|||||||
IconPointFilled,
|
IconPointFilled,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts";
|
import { appendNodeChildrenAtom, treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import EmojiPicker from "@/components/ui/emoji-picker.tsx";
|
import EmojiPicker from "@/components/ui/emoji-picker.tsx";
|
||||||
import { useTreeMutation } from "@/features/page/tree/hooks/use-tree-mutation.ts";
|
import { useTreeMutation } from "@/features/page/tree/hooks/use-tree-mutation.ts";
|
||||||
@ -140,7 +140,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
|
|||||||
if (ancestor.id === currentPage.id) {
|
if (ancestor.id === currentPage.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const children = await fetchAncestorChildren({
|
const children = await fetchAllAncestorChildren({
|
||||||
pageId: ancestor.id,
|
pageId: ancestor.id,
|
||||||
spaceId: ancestor.spaceId,
|
spaceId: ancestor.spaceId,
|
||||||
});
|
});
|
||||||
@ -237,6 +237,7 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const updatePageMutation = useUpdatePageMutation();
|
const updatePageMutation = useUpdatePageMutation();
|
||||||
const [treeData, setTreeData] = useAtom(treeDataAtom);
|
const [treeData, setTreeData] = useAtom(treeDataAtom);
|
||||||
|
const [, appendChildren] = useAtom(appendNodeChildrenAtom);
|
||||||
const emit = useQueryEmit();
|
const emit = useQueryEmit();
|
||||||
const { spaceSlug } = useParams();
|
const { spaceSlug } = useParams();
|
||||||
const timerRef = useRef(null);
|
const timerRef = useRef(null);
|
||||||
@ -262,9 +263,10 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
|||||||
|
|
||||||
async function handleLoadChildren(node: NodeApi<SpaceTreeNode>) {
|
async function handleLoadChildren(node: NodeApi<SpaceTreeNode>) {
|
||||||
if (!node.data.hasChildren) return;
|
if (!node.data.hasChildren) return;
|
||||||
if (node.data.children && node.data.children.length > 0) {
|
// in conflict with use-query-subscription.ts => case "addTreeNode","moveTreeNode" etc with websocket
|
||||||
return;
|
// if (node.data.children && node.data.children.length > 0) {
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const params: SidebarPagesParams = {
|
const params: SidebarPagesParams = {
|
||||||
@ -272,21 +274,12 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
|||||||
spaceId: node.data.spaceId,
|
spaceId: node.data.spaceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const newChildren = await queryClient.fetchQuery({
|
const childrenTree = await fetchAllAncestorChildren(params);
|
||||||
queryKey: ["sidebar-pages", params],
|
|
||||||
queryFn: () => getSidebarPages(params),
|
appendChildren({
|
||||||
staleTime: 10 * 60 * 1000,
|
parentId: node.data.id,
|
||||||
|
children: childrenTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const childrenTree = buildTree(newChildren.items);
|
|
||||||
|
|
||||||
const updatedTreeData = appendNodeChildren(
|
|
||||||
treeData,
|
|
||||||
node.data.id,
|
|
||||||
childrenTree,
|
|
||||||
);
|
|
||||||
|
|
||||||
setTreeData(updatedTreeData);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch children:", error);
|
console.error("Failed to fetch children:", error);
|
||||||
}
|
}
|
||||||
@ -304,17 +297,17 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
|||||||
|
|
||||||
const handleEmojiSelect = (emoji: { native: string }) => {
|
const handleEmojiSelect = (emoji: { native: string }) => {
|
||||||
handleUpdateNodeIcon(node.id, emoji.native);
|
handleUpdateNodeIcon(node.id, emoji.native);
|
||||||
updatePageMutation.mutateAsync({ pageId: node.id, icon: emoji.native });
|
updatePageMutation.mutateAsync({ pageId: node.id, icon: emoji.native }).then((data) => {
|
||||||
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
emit({
|
||||||
emit({
|
operation: "updateOne",
|
||||||
operation: "updateOne",
|
spaceId: node.data.spaceId,
|
||||||
spaceId: node.data.spaceId,
|
entity: ["pages"],
|
||||||
entity: ["pages"],
|
id: node.id,
|
||||||
id: node.id,
|
payload: { icon: emoji.native, parentPageId: data.parentPageId},
|
||||||
payload: { icon: emoji.native },
|
});
|
||||||
});
|
}, 50);
|
||||||
}, 50);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveEmoji = () => {
|
const handleRemoveEmoji = () => {
|
||||||
@ -576,6 +569,12 @@ interface PageArrowProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PageArrow({ node, onExpandTree }: PageArrowProps) {
|
function PageArrow({ node, onExpandTree }: PageArrowProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if(node.isOpen){
|
||||||
|
onExpandTree();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size={20}
|
size={20}
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export function useTreeMutation<T>(spaceId: string) {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMove: MoveHandler<T> = (args: {
|
const onMove: MoveHandler<T> = async (args: {
|
||||||
dragIds: string[];
|
dragIds: string[];
|
||||||
dragNodes: NodeApi<T>[];
|
dragNodes: NodeApi<T>[];
|
||||||
parentId: string | null;
|
parentId: string | null;
|
||||||
@ -176,7 +176,7 @@ export function useTreeMutation<T>(spaceId: string) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
movePageMutation.mutateAsync(payload);
|
await movePageMutation.mutateAsync(payload);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
emit({
|
emit({
|
||||||
@ -206,6 +206,23 @@ export function useTreeMutation<T>(spaceId: string) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPageInNode = (
|
||||||
|
node: { data: SpaceTreeNode; children?: any[] },
|
||||||
|
pageSlug: string
|
||||||
|
): boolean => {
|
||||||
|
if (node.data.slugId === pageSlug) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const item of node.children) {
|
||||||
|
if (item.data.slugId === pageSlug) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return isPageInNode(item, pageSlug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const onDelete: DeleteHandler<T> = async (args: { ids: string[] }) => {
|
const onDelete: DeleteHandler<T> = async (args: { ids: string[] }) => {
|
||||||
try {
|
try {
|
||||||
await deletePageMutation.mutateAsync(args.ids[0]);
|
await deletePageMutation.mutateAsync(args.ids[0]);
|
||||||
@ -218,8 +235,7 @@ export function useTreeMutation<T>(spaceId: string) {
|
|||||||
tree.drop({ id: args.ids[0] });
|
tree.drop({ id: args.ids[0] });
|
||||||
setData(tree.data);
|
setData(tree.data);
|
||||||
|
|
||||||
// navigate only if the current url is same as the deleted page
|
if (pageSlug && isPageInNode(node, pageSlug.split("-")[1])) {
|
||||||
if (pageSlug && node.data.slugId === pageSlug.split("-")[1]) {
|
|
||||||
navigate(getSpaceUrl(spaceSlug));
|
navigate(getSpaceUrl(spaceSlug));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -164,16 +164,35 @@ export function appendNodeChildren(
|
|||||||
nodeId: string,
|
nodeId: string,
|
||||||
children: SpaceTreeNode[],
|
children: SpaceTreeNode[],
|
||||||
) {
|
) {
|
||||||
return treeItems.map((nodeItem) => {
|
// Preserve deeper children if they exist and remove node if deleted
|
||||||
if (nodeItem.id === nodeId) {
|
return treeItems.map((node) => {
|
||||||
return { ...nodeItem, children };
|
if (node.id === nodeId) {
|
||||||
}
|
const newIds = new Set(children.map(c => c.id));
|
||||||
if (nodeItem.children) {
|
|
||||||
|
const existingMap = new Map(
|
||||||
|
(node.children ?? []).filter(c => newIds.has(c.id)).map(c => [c.id, c])
|
||||||
|
);
|
||||||
|
|
||||||
|
const merged = children.map((newChild) => {
|
||||||
|
const existing = existingMap.get(newChild.id);
|
||||||
|
return existing && existing.children
|
||||||
|
? { ...newChild, children: existing.children }
|
||||||
|
: newChild;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...nodeItem,
|
...node,
|
||||||
children: appendNodeChildren(nodeItem.children, nodeId, children),
|
children: merged,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return nodeItem;
|
|
||||||
|
if (node.children) {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
children: appendNodeChildren(node.children, nodeId, children),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
||||||
|
import { IPage } from "@/features/page/types/page.types";
|
||||||
|
|
||||||
export type InvalidateEvent = {
|
export type InvalidateEvent = {
|
||||||
operation: "invalidate";
|
operation: "invalidate";
|
||||||
@ -17,7 +18,7 @@ export type UpdateEvent = {
|
|||||||
spaceId: string;
|
spaceId: string;
|
||||||
entity: Array<string>;
|
entity: Array<string>;
|
||||||
id: string;
|
id: string;
|
||||||
payload: Partial<any>;
|
payload: Partial<IPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeleteEvent = {
|
export type DeleteEvent = {
|
||||||
@ -25,7 +26,7 @@ export type DeleteEvent = {
|
|||||||
spaceId: string;
|
spaceId: string;
|
||||||
entity: Array<string>;
|
entity: Array<string>;
|
||||||
id: string;
|
id: string;
|
||||||
payload?: Partial<any>;
|
payload?: Partial<IPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AddTreeNodeEvent = {
|
export type AddTreeNodeEvent = {
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { socketAtom } from "@/features/websocket/atoms/socket-atom.ts";
|
import { socketAtom } from "@/features/websocket/atoms/socket-atom.ts";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { InfiniteData, useQueryClient } from "@tanstack/react-query";
|
||||||
import { WebSocketEvent } from "@/features/websocket/types";
|
import { WebSocketEvent } from "@/features/websocket/types";
|
||||||
|
import { IPage } from "../page/types/page.types";
|
||||||
|
import { IPagination } from "@/lib/types";
|
||||||
|
import {
|
||||||
|
invalidateOnCreatePage,
|
||||||
|
invalidateOnDeletePage,
|
||||||
|
invalidateOnMovePage,
|
||||||
|
invalidateOnUpdatePage,
|
||||||
|
} from "../page/queries/page-query";
|
||||||
import { RQ_KEY } from "../comment/queries/comment-query";
|
import { RQ_KEY } from "../comment/queries/comment-query";
|
||||||
|
|
||||||
export const useQuerySubscription = () => {
|
export const useQuerySubscription = () => {
|
||||||
@ -27,6 +35,15 @@ export const useQuerySubscription = () => {
|
|||||||
queryKey: RQ_KEY(data.pageId),
|
queryKey: RQ_KEY(data.pageId),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "addTreeNode":
|
||||||
|
invalidateOnCreatePage(data.payload.data);
|
||||||
|
break;
|
||||||
|
case "moveTreeNode":
|
||||||
|
invalidateOnMovePage();
|
||||||
|
break;
|
||||||
|
case "deleteTreeNode":
|
||||||
|
invalidateOnDeletePage(data.payload.node.id);
|
||||||
|
break;
|
||||||
case "updateOne":
|
case "updateOne":
|
||||||
entity = data.entity[0];
|
entity = data.entity[0];
|
||||||
if (entity === "pages") {
|
if (entity === "pages") {
|
||||||
@ -37,13 +54,23 @@ export const useQuerySubscription = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only update if data was already in cache
|
// only update if data was already in cache
|
||||||
if(queryClient.getQueryData([...data.entity, queryKeyId])){
|
if (queryClient.getQueryData([...data.entity, queryKeyId])) {
|
||||||
queryClient.setQueryData([...data.entity, queryKeyId], {
|
queryClient.setQueryData([...data.entity, queryKeyId], {
|
||||||
...queryClient.getQueryData([...data.entity, queryKeyId]),
|
...queryClient.getQueryData([...data.entity, queryKeyId]),
|
||||||
...data.payload,
|
...data.payload,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity === "pages") {
|
||||||
|
invalidateOnUpdatePage(
|
||||||
|
data.spaceId,
|
||||||
|
data.payload.parentPageId,
|
||||||
|
data.id,
|
||||||
|
data.payload.title,
|
||||||
|
data.payload.icon,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
queryClient.setQueriesData(
|
queryClient.setQueriesData(
|
||||||
{ queryKey: [data.entity, data.id] },
|
{ queryKey: [data.entity, data.id] },
|
||||||
|
|||||||
Reference in New Issue
Block a user