mirror of
https://github.com/Shadowfita/docmost.git
synced 2025-11-11 04:52:00 +10:00
working page tree
This commit is contained in:
@ -22,6 +22,7 @@ import { SearchSpotlight } from "@/features/search/search-spotlight";
|
|||||||
import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom";
|
import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom";
|
||||||
import PageTree from "@/features/page/tree/page-tree";
|
import PageTree from "@/features/page/tree/page-tree";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import SpaceContent from "@/features/page/component/space-content.tsx";
|
||||||
|
|
||||||
interface PrimaryMenuItem {
|
interface PrimaryMenuItem {
|
||||||
icon: React.ElementType;
|
icon: React.ElementType;
|
||||||
@ -103,7 +104,8 @@ export function Navbar() {
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<div className={classes.pages}>
|
<div className={classes.pages}>
|
||||||
<PageTree />
|
<SpaceContent />
|
||||||
|
{/* <PageTree /> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
|
retry: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function TanstackProvider({ children }: React.PropsWithChildren) {
|
export function TanstackProvider({ children }: React.PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||||
{children}
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,20 @@
|
|||||||
import '@/features/editor/styles/index.css';
|
import "@/features/editor/styles/index.css";
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { EditorContent, useEditor } from '@tiptap/react';
|
import { EditorContent, useEditor } from "@tiptap/react";
|
||||||
import { Document } from '@tiptap/extension-document';
|
import { Document } from "@tiptap/extension-document";
|
||||||
import { Heading } from '@tiptap/extension-heading';
|
import { Heading } from "@tiptap/extension-heading";
|
||||||
import { Text } from '@tiptap/extension-text';
|
import { Text } from "@tiptap/extension-text";
|
||||||
import { Placeholder } from '@tiptap/extension-placeholder';
|
import { Placeholder } from "@tiptap/extension-placeholder";
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from "jotai";
|
||||||
import { pageEditorAtom, titleEditorAtom } from '@/features/editor/atoms/editor-atoms';
|
import {
|
||||||
import { useUpdatePageMutation } from '@/features/page/queries/page-query';
|
pageEditorAtom,
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
titleEditorAtom,
|
||||||
import { useAtom } from 'jotai';
|
} from "@/features/editor/atoms/editor-atoms";
|
||||||
import { treeDataAtom } from '@/features/page/tree/atoms/tree-data-atom';
|
import { useUpdatePageMutation } from "@/features/page/queries/page-query";
|
||||||
import { updateTreeNodeName } from '@/features/page/tree/utils';
|
import { useDebouncedValue } from "@mantine/hooks";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom";
|
||||||
|
import { updateTreeNodeName } from "@/features/page/tree/utils";
|
||||||
|
|
||||||
export interface TitleEditorProps {
|
export interface TitleEditorProps {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
@ -19,7 +22,7 @@ export interface TitleEditorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TitleEditor({ pageId, title }: TitleEditorProps) {
|
export function TitleEditor({ pageId, title }: TitleEditorProps) {
|
||||||
const [debouncedTitleState, setDebouncedTitleState] = useState('');
|
const [debouncedTitleState, setDebouncedTitleState] = useState("");
|
||||||
const [debouncedTitle] = useDebouncedValue(debouncedTitleState, 1000);
|
const [debouncedTitle] = useDebouncedValue(debouncedTitleState, 1000);
|
||||||
const updatePageMutation = useUpdatePageMutation();
|
const updatePageMutation = useUpdatePageMutation();
|
||||||
const pageEditor = useAtomValue(pageEditorAtom);
|
const pageEditor = useAtomValue(pageEditorAtom);
|
||||||
@ -29,14 +32,14 @@ export function TitleEditor({ pageId, title }: TitleEditorProps) {
|
|||||||
const titleEditor = useEditor({
|
const titleEditor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
Document.extend({
|
Document.extend({
|
||||||
content: 'heading',
|
content: "heading",
|
||||||
}),
|
}),
|
||||||
Heading.configure({
|
Heading.configure({
|
||||||
levels: [1],
|
levels: [1],
|
||||||
}),
|
}),
|
||||||
Text,
|
Text,
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder: 'Untitled',
|
placeholder: "Untitled",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
onCreate({ editor }) {
|
onCreate({ editor }) {
|
||||||
@ -53,8 +56,8 @@ export function TitleEditor({ pageId, title }: TitleEditorProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debouncedTitle !== '') {
|
if (debouncedTitle !== "") {
|
||||||
updatePageMutation.mutate({ id: pageId, title: debouncedTitle });
|
updatePageMutation.mutate({ pageId, title: debouncedTitle });
|
||||||
|
|
||||||
const newTreeData = updateTreeNodeName(treeData, pageId, debouncedTitle);
|
const newTreeData = updateTreeNodeName(treeData, pageId, debouncedTitle);
|
||||||
setTreeData(newTreeData);
|
setTreeData(newTreeData);
|
||||||
@ -69,7 +72,7 @@ export function TitleEditor({ pageId, title }: TitleEditorProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
titleEditor?.commands.focus('end');
|
titleEditor?.commands.focus("end");
|
||||||
}, 500);
|
}, 500);
|
||||||
}, [titleEditor]);
|
}, [titleEditor]);
|
||||||
|
|
||||||
@ -79,15 +82,15 @@ export function TitleEditor({ pageId, title }: TitleEditorProps) {
|
|||||||
const { key } = event;
|
const { key } = event;
|
||||||
const { $head } = titleEditor.state.selection;
|
const { $head } = titleEditor.state.selection;
|
||||||
|
|
||||||
const shouldFocusEditor = (key === 'Enter' || key === 'ArrowDown') ||
|
const shouldFocusEditor =
|
||||||
(key === 'ArrowRight' && !$head.nodeAfter);
|
key === "Enter" ||
|
||||||
|
key === "ArrowDown" ||
|
||||||
|
(key === "ArrowRight" && !$head.nodeAfter);
|
||||||
|
|
||||||
if (shouldFocusEditor) {
|
if (shouldFocusEditor) {
|
||||||
pageEditor.commands.focus('start');
|
pageEditor.commands.focus("start");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <EditorContent editor={titleEditor} onKeyDown={handleTitleKeyDown} />;
|
||||||
<EditorContent editor={titleEditor} onKeyDown={handleTitleKeyDown} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
64
apps/client/src/features/page/component/space-content.tsx
Normal file
64
apps/client/src/features/page/component/space-content.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { useSpaceQuery } from "@/features/space/queries/space-query.ts";
|
||||||
|
import { useAtom } from "jotai/index";
|
||||||
|
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionControlProps,
|
||||||
|
ActionIcon,
|
||||||
|
Center,
|
||||||
|
rem,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
|
import React from "react";
|
||||||
|
import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts";
|
||||||
|
import PageTree from "@/features/page/tree/page-tree.tsx";
|
||||||
|
|
||||||
|
export default function SpaceContent() {
|
||||||
|
const [currentUser] = useAtom(currentUserAtom);
|
||||||
|
const { data: space } = useSpaceQuery(currentUser?.workspace.defaultSpaceId);
|
||||||
|
|
||||||
|
if (!space) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Accordion
|
||||||
|
chevronPosition="left"
|
||||||
|
maw={400}
|
||||||
|
mx="auto"
|
||||||
|
defaultValue={space.id}
|
||||||
|
>
|
||||||
|
<Accordion.Item key={space.id} value={space.id}>
|
||||||
|
<AccordionControl>{space.name}</AccordionControl>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<PageTree spaceId={space.id} />
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
</Accordion>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccordionControl(props: AccordionControlProps) {
|
||||||
|
const [tree] = useAtom(treeApiAtom);
|
||||||
|
|
||||||
|
function handleCreatePage() {
|
||||||
|
tree?.create({ parentId: null, type: "internal", index: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Center>
|
||||||
|
<Accordion.Control {...props} />
|
||||||
|
{/* <ActionIcon size="lg" variant="subtle" color="gray">
|
||||||
|
<IconDots size="1rem" />
|
||||||
|
</ActionIcon> */}
|
||||||
|
<Tooltip label="Create page" withArrow position="right">
|
||||||
|
<ActionIcon variant="default" size={18} onClick={handleCreatePage}>
|
||||||
|
<IconPlus style={{ width: rem(12), height: rem(12) }} stroke={1.5} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,17 +1,14 @@
|
|||||||
import {
|
import { useMutation, useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||||
useMutation,
|
|
||||||
useQuery,
|
|
||||||
UseQueryResult,
|
|
||||||
} from "@tanstack/react-query";
|
|
||||||
import {
|
import {
|
||||||
createPage,
|
createPage,
|
||||||
deletePage,
|
deletePage,
|
||||||
getPageById,
|
getPageById,
|
||||||
getPages,
|
getPages,
|
||||||
getRecentChanges,
|
getRecentChanges,
|
||||||
|
getSpacePageOrder,
|
||||||
updatePage,
|
updatePage,
|
||||||
} from "@/features/page/services/page-service";
|
} from "@/features/page/services/page-service";
|
||||||
import { IPage } from "@/features/page/types/page.types";
|
import { IPage, IWorkspacePageOrder } from "@/features/page/types/page.types";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
const RECENT_CHANGES_KEY = ["recentChanges"];
|
const RECENT_CHANGES_KEY = ["recentChanges"];
|
||||||
@ -25,10 +22,12 @@ export function usePageQuery(pageId: string): UseQueryResult<IPage, Error> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetPagesQuery(): UseQueryResult<IPage[], Error> {
|
export function useGetPagesQuery(
|
||||||
|
spaceId: string,
|
||||||
|
): UseQueryResult<IPage[], Error> {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["pages"],
|
queryKey: ["pages", spaceId],
|
||||||
queryFn: () => getPages(),
|
queryFn: () => getPages(spaceId),
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -63,3 +62,14 @@ export function useDeletePageMutation() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function useSpacePageOrder(
|
||||||
|
spaceId: string,
|
||||||
|
): UseQueryResult<IWorkspacePageOrder> {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["page-order", spaceId],
|
||||||
|
queryFn: async () => {
|
||||||
|
return await getSpacePageOrder(spaceId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -1,28 +1,36 @@
|
|||||||
import api from '@/lib/api-client';
|
import api from "@/lib/api-client";
|
||||||
import { IMovePage, IPage, IWorkspacePageOrder } from '@/features/page/types/page.types';
|
import {
|
||||||
|
IMovePage,
|
||||||
|
IPage,
|
||||||
|
IWorkspacePageOrder,
|
||||||
|
} from "@/features/page/types/page.types";
|
||||||
|
|
||||||
export async function createPage(data: Partial<IPage>): Promise<IPage> {
|
export async function createPage(data: Partial<IPage>): Promise<IPage> {
|
||||||
const req = await api.post<IPage>('/pages/create', data);
|
const req = await api.post<IPage>("/pages/create", data);
|
||||||
return req.data as IPage;
|
return req.data as IPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPageById(id: string): Promise<IPage> {
|
export async function getPageById(pageId: string): Promise<IPage> {
|
||||||
const req = await api.post<IPage>('/pages/info', { id });
|
const req = await api.post<IPage>("/pages/info", { pageId });
|
||||||
return req.data as IPage;
|
return req.data as IPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRecentChanges(): Promise<IPage[]> {
|
export async function getRecentChanges(): Promise<IPage[]> {
|
||||||
const req = await api.post<IPage[]>('/pages/recent');
|
const req = await api.post<IPage[]>("/pages/recent");
|
||||||
return req.data as IPage[];
|
return req.data as IPage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPages(): Promise<IPage[]> {
|
export async function getPages(spaceId: string): Promise<IPage[]> {
|
||||||
const req = await api.post<IPage[]>('/pages');
|
const req = await api.post<IPage[]>("/pages", { spaceId });
|
||||||
return req.data as IPage[];
|
return req.data as IPage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWorkspacePageOrder(): Promise<IWorkspacePageOrder[]> {
|
export async function getSpacePageOrder(
|
||||||
const req = await api.post<IWorkspacePageOrder[]>('/pages/ordering');
|
spaceId: string,
|
||||||
|
): Promise<IWorkspacePageOrder[]> {
|
||||||
|
const req = await api.post<IWorkspacePageOrder[]>("/pages/ordering", {
|
||||||
|
spaceId,
|
||||||
|
});
|
||||||
return req.data as IWorkspacePageOrder[];
|
return req.data as IWorkspacePageOrder[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,9 +40,9 @@ export async function updatePage(data: Partial<IPage>): Promise<IPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function movePage(data: IMovePage): Promise<void> {
|
export async function movePage(data: IMovePage): Promise<void> {
|
||||||
await api.post<IMovePage>('/pages/move', data);
|
await api.post<IMovePage>("/pages/move", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deletePage(id: string): Promise<void> {
|
export async function deletePage(id: string): Promise<void> {
|
||||||
await api.post('/pages/delete', { id });
|
await api.post("/pages/delete", { id });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,28 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
CreateHandler,
|
CreateHandler,
|
||||||
DeleteHandler,
|
DeleteHandler,
|
||||||
MoveHandler,
|
MoveHandler,
|
||||||
RenameHandler,
|
RenameHandler,
|
||||||
SimpleTree,
|
SimpleTree,
|
||||||
} from 'react-arborist';
|
} from "react-arborist";
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from "jotai";
|
||||||
import { treeDataAtom } from '@/features/page/tree/atoms/tree-data-atom';
|
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom";
|
||||||
import { movePage } from '@/features/page/services/page-service';
|
import { movePage } from "@/features/page/services/page-service";
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { IMovePage } from '@/features/page/types/page.types';
|
import { IMovePage } from "@/features/page/types/page.types";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from "react-router-dom";
|
||||||
import { TreeNode } from '@/features/page/tree/types';
|
import { TreeNode } from "@/features/page/tree/types";
|
||||||
import { useCreatePageMutation, useDeletePageMutation, useUpdatePageMutation } from '@/features/page/queries/page-query';
|
import {
|
||||||
|
useCreatePageMutation,
|
||||||
|
useDeletePageMutation,
|
||||||
|
useUpdatePageMutation,
|
||||||
|
} from "@/features/page/queries/page-query";
|
||||||
|
|
||||||
export function usePersistence<T>() {
|
interface Props {
|
||||||
|
spaceId: string;
|
||||||
|
}
|
||||||
|
export function usePersistence<T>(spaceId: string) {
|
||||||
const [data, setData] = useAtom(treeDataAtom);
|
const [data, setData] = useAtom(treeDataAtom);
|
||||||
const createPageMutation = useCreatePageMutation();
|
const createPageMutation = useCreatePageMutation();
|
||||||
const updatePageMutation = useUpdatePageMutation();
|
const updatePageMutation = useUpdatePageMutation();
|
||||||
@ -25,7 +32,13 @@ export function usePersistence<T>() {
|
|||||||
|
|
||||||
const tree = useMemo(() => new SimpleTree<TreeNode>(data), [data]);
|
const tree = useMemo(() => new SimpleTree<TreeNode>(data), [data]);
|
||||||
|
|
||||||
const onMove: MoveHandler<T> = (args: { parentId, index, parentNode, dragNodes, dragIds }) => {
|
const onMove: MoveHandler<T> = (args: {
|
||||||
|
parentId;
|
||||||
|
index;
|
||||||
|
parentNode;
|
||||||
|
dragNodes;
|
||||||
|
dragIds;
|
||||||
|
}) => {
|
||||||
for (const id of args.dragIds) {
|
for (const id of args.dragIds) {
|
||||||
tree.move({ id, parentId: args.parentId, index: args.index });
|
tree.move({ id, parentId: args.parentId, index: args.index });
|
||||||
}
|
}
|
||||||
@ -33,25 +46,30 @@ export function usePersistence<T>() {
|
|||||||
|
|
||||||
const newDragIndex = tree.find(args.dragIds[0])?.childIndex;
|
const newDragIndex = tree.find(args.dragIds[0])?.childIndex;
|
||||||
|
|
||||||
const currentTreeData = args.parentId ? tree.find(args.parentId).children : tree.data;
|
const currentTreeData = args.parentId
|
||||||
|
? tree.find(args.parentId).children
|
||||||
|
: tree.data;
|
||||||
const afterId = currentTreeData[newDragIndex - 1]?.id || null;
|
const afterId = currentTreeData[newDragIndex - 1]?.id || null;
|
||||||
const beforeId = !afterId && currentTreeData[newDragIndex + 1]?.id || null;
|
const beforeId =
|
||||||
|
(!afterId && currentTreeData[newDragIndex + 1]?.id) || null;
|
||||||
|
|
||||||
const params: IMovePage = {
|
const params: IMovePage = {
|
||||||
id: args.dragIds[0],
|
pageId: args.dragIds[0],
|
||||||
after: afterId,
|
after: afterId,
|
||||||
before: beforeId,
|
before: beforeId,
|
||||||
parentId: args.parentId || null,
|
parentId: args.parentId || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const payload = Object.fromEntries(
|
const payload = Object.fromEntries(
|
||||||
Object.entries(params).filter(([key, value]) => value !== null && value !== undefined),
|
Object.entries(params).filter(
|
||||||
|
([key, value]) => value !== null && value !== undefined,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
movePage(payload as IMovePage);
|
movePage(payload as IMovePage);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error moving page:', error);
|
console.error("Error moving page:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -60,28 +78,32 @@ export function usePersistence<T>() {
|
|||||||
setData(tree.data);
|
setData(tree.data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
updatePageMutation.mutateAsync({ id, title: name });
|
updatePageMutation.mutateAsync({ pageId: id, title: name });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating page title:', error);
|
console.error("Error updating page title:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreate: CreateHandler<T> = async ({ parentId, index, type }) => {
|
const onCreate: CreateHandler<T> = async ({ parentId, index, type }) => {
|
||||||
const data = { id: uuidv4(), name: '' } as any;
|
const data = { id: uuidv4(), name: "" } as any;
|
||||||
data.children = [];
|
data.children = [];
|
||||||
tree.create({ parentId, index, data });
|
tree.create({ parentId, index, data });
|
||||||
setData(tree.data);
|
setData(tree.data);
|
||||||
|
|
||||||
const payload: { id: string; parentPageId?: string } = { id: data.id };
|
const payload: { pageId: string; parentPageId?: string; spaceId: string } =
|
||||||
|
{
|
||||||
|
pageId: data.id,
|
||||||
|
spaceId: spaceId,
|
||||||
|
};
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
payload.parentPageId = parentId;
|
payload.parentPageId = parentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createPageMutation.mutateAsync(payload);
|
await createPageMutation.mutateAsync(payload);
|
||||||
navigate(`/p/${payload.id}`);
|
navigate(`/p/${payload.pageId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating the page:', error);
|
console.error("Error creating the page:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
@ -93,9 +115,9 @@ export function usePersistence<T>() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await deletePageMutation.mutateAsync(args.ids[0]);
|
await deletePageMutation.mutateAsync(args.ids[0]);
|
||||||
navigate('/home');
|
navigate("/home");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting page:', error);
|
console.error("Error deleting page:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
|
||||||
import { IWorkspacePageOrder } from '@/features/page/types/page.types';
|
|
||||||
import { getWorkspacePageOrder } from '@/features/page/services/page-service';
|
|
||||||
|
|
||||||
export default function useWorkspacePageOrder(): UseQueryResult<IWorkspacePageOrder> {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["workspace-page-order"],
|
|
||||||
queryFn: async () => {
|
|
||||||
return await getWorkspacePageOrder();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -23,21 +23,25 @@ import { FillFlexParent } from "./components/fill-flex-parent";
|
|||||||
import { TreeNode } from "./types";
|
import { TreeNode } from "./types";
|
||||||
import { treeApiAtom } from "./atoms/tree-api-atom";
|
import { treeApiAtom } from "./atoms/tree-api-atom";
|
||||||
import { usePersistence } from "@/features/page/tree/hooks/use-persistence";
|
import { usePersistence } from "@/features/page/tree/hooks/use-persistence";
|
||||||
import useWorkspacePageOrder from "@/features/page/tree/hooks/use-workspace-page-order";
|
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { convertToTree, updateTreeNodeIcon } from "@/features/page/tree/utils";
|
import { convertToTree, updateTreeNodeIcon } from "@/features/page/tree/utils";
|
||||||
import {
|
import useSpacePageOrder, {
|
||||||
useGetPagesQuery,
|
useGetPagesQuery,
|
||||||
useUpdatePageMutation,
|
useUpdatePageMutation,
|
||||||
} from "@/features/page/queries/page-query";
|
} from "@/features/page/queries/page-query";
|
||||||
import EmojiPicker from "@/components/ui/emoji-picker.tsx";
|
import EmojiPicker from "@/components/ui/emoji-picker.tsx";
|
||||||
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom";
|
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom";
|
||||||
|
|
||||||
export default function PageTree() {
|
interface PageTreeProps {
|
||||||
const { data, setData, controllers } = usePersistence<TreeApi<TreeNode>>();
|
spaceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PageTree({ spaceId }: PageTreeProps) {
|
||||||
|
const { data, setData, controllers } =
|
||||||
|
usePersistence<TreeApi<TreeNode>>(spaceId);
|
||||||
const [tree, setTree] = useAtom<TreeApi<TreeNode>>(treeApiAtom);
|
const [tree, setTree] = useAtom<TreeApi<TreeNode>>(treeApiAtom);
|
||||||
const { data: pageOrderData } = useWorkspacePageOrder();
|
const { data: pageOrderData } = useSpacePageOrder(spaceId);
|
||||||
const { data: pagesData, isLoading } = useGetPagesQuery();
|
const { data: pagesData, isLoading } = useGetPagesQuery(spaceId);
|
||||||
const rootElement = useRef<HTMLDivElement>();
|
const rootElement = useRef<HTMLDivElement>();
|
||||||
const { pageId } = useParams();
|
const { pageId } = useParams();
|
||||||
|
|
||||||
@ -113,12 +117,12 @@ function Node({ node, style, dragHandle }: NodeRendererProps<any>) {
|
|||||||
|
|
||||||
const handleEmojiSelect = (emoji) => {
|
const handleEmojiSelect = (emoji) => {
|
||||||
handleUpdateNodeIcon(node.id, emoji.native);
|
handleUpdateNodeIcon(node.id, emoji.native);
|
||||||
updatePageMutation.mutateAsync({ id: node.id, icon: emoji.native });
|
updatePageMutation.mutateAsync({ pageId: node.id, icon: emoji.native });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveEmoji = () => {
|
const handleRemoveEmoji = () => {
|
||||||
handleUpdateNodeIcon(node.id, null);
|
handleUpdateNodeIcon(node.id, null);
|
||||||
updatePageMutation.mutateAsync({ id: node.id, icon: null });
|
updatePageMutation.mutateAsync({ pageId: node.id, icon: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
if (node.willReceiveDrop && node.isClosed) {
|
if (node.willReceiveDrop && node.isClosed) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export type TreeNode = {
|
export type TreeNode = {
|
||||||
id: string
|
id: string;
|
||||||
name: string
|
name: string;
|
||||||
icon?: string
|
icon?: string;
|
||||||
slug?: string
|
slug?: string;
|
||||||
children: TreeNode[]
|
children: TreeNode[];
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export interface IPage {
|
export interface IPage {
|
||||||
|
pageId: string;
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
@ -10,9 +11,10 @@ export interface IPage {
|
|||||||
shareId: string;
|
shareId: string;
|
||||||
parentPageId: string;
|
parentPageId: string;
|
||||||
creatorId: string;
|
creatorId: string;
|
||||||
|
spaceId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
children:[]
|
children: [];
|
||||||
childrenIds:[]
|
childrenIds: [];
|
||||||
isLocked: boolean;
|
isLocked: boolean;
|
||||||
status: string;
|
status: string;
|
||||||
publishedAt: Date;
|
publishedAt: Date;
|
||||||
@ -22,7 +24,7 @@ export interface IPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IMovePage {
|
export interface IMovePage {
|
||||||
id: string;
|
pageId: string;
|
||||||
after?: string;
|
after?: string;
|
||||||
before?: string;
|
before?: string;
|
||||||
parentId?: string;
|
parentId?: string;
|
||||||
|
|||||||
@ -1,10 +1,24 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import {
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
UseQueryResult,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
changeMemberRole,
|
changeMemberRole,
|
||||||
|
getWorkspace,
|
||||||
getWorkspaceMembers,
|
getWorkspaceMembers,
|
||||||
} from "@/features/workspace/services/workspace-service";
|
} from "@/features/workspace/services/workspace-service";
|
||||||
import { QueryParams } from "@/lib/types.ts";
|
import { QueryParams } from "@/lib/types.ts";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { IWorkspace } from "@/features/workspace/types/workspace.types.ts";
|
||||||
|
|
||||||
|
export function useWorkspace(): UseQueryResult<IWorkspace, Error> {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["workspace"],
|
||||||
|
queryFn: () => getWorkspace(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function useWorkspaceMembersQuery(params?: QueryParams) {
|
export function useWorkspaceMembersQuery(params?: QueryParams) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export interface IWorkspace {
|
|||||||
description: string;
|
description: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
|
defaultSpaceId: string;
|
||||||
customDomain: string;
|
customDomain: string;
|
||||||
enableInvite: boolean;
|
enableInvite: boolean;
|
||||||
inviteCode: string;
|
inviteCode: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user