mirror of
https://github.com/docmost/docmost.git
synced 2025-11-13 04:22:37 +10:00
fix tree
* fix tree root element ref * fix tree node toggle bug * fix tree node reparenting on the backend * highlight active home menu on the sidebar
This commit is contained in:
@ -85,3 +85,8 @@
|
|||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
|
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activeButton {
|
||||||
|
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
||||||
|
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
|
||||||
|
}
|
||||||
|
|||||||
@ -20,25 +20,28 @@ import React from "react";
|
|||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { SearchSpotlight } from "@/features/search/search-spotlight";
|
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 { useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import SpaceContent from "@/features/page/tree/components/space-content.tsx";
|
import SpaceContent from "@/features/page/tree/components/space-content.tsx";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import APP_ROUTE from "@/lib/app-route.ts";
|
||||||
|
|
||||||
interface PrimaryMenuItem {
|
interface PrimaryMenuItem {
|
||||||
icon: React.ElementType;
|
icon: React.ElementType;
|
||||||
label: string;
|
label: string;
|
||||||
|
path?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryMenu: PrimaryMenuItem[] = [
|
const primaryMenu: PrimaryMenuItem[] = [
|
||||||
{ icon: IconHome, label: "Home" },
|
{ icon: IconHome, label: "Home", path: "/home" },
|
||||||
{ icon: IconSearch, label: "Search" },
|
{ icon: IconSearch, label: "Search" },
|
||||||
{ icon: IconSettings, label: "Settings" },
|
{ icon: IconSettings, label: "Settings" },
|
||||||
// { icon: IconFilePlus, label: 'New Page' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const [tree] = useAtom(treeApiAtom);
|
const [tree] = useAtom(treeApiAtom);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const handleMenuItemClick = (label: string) => {
|
const handleMenuItemClick = (label: string) => {
|
||||||
if (label === "Home") {
|
if (label === "Home") {
|
||||||
@ -61,7 +64,12 @@ export function Navbar() {
|
|||||||
const primaryMenuItems = primaryMenu.map((menuItem) => (
|
const primaryMenuItems = primaryMenu.map((menuItem) => (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
key={menuItem.label}
|
key={menuItem.label}
|
||||||
className={classes.menu}
|
className={clsx(
|
||||||
|
classes.menu,
|
||||||
|
location.pathname.toLowerCase() === menuItem?.path
|
||||||
|
? classes.activeButton
|
||||||
|
: "",
|
||||||
|
)}
|
||||||
onClick={() => handleMenuItemClick(menuItem.label)}
|
onClick={() => handleMenuItemClick(menuItem.label)}
|
||||||
>
|
>
|
||||||
<div className={classes.menuItemInner}>
|
<div className={classes.menuItemInner}>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
useUpdatePageMutation,
|
useUpdatePageMutation,
|
||||||
} from "@/features/page/queries/page-query.ts";
|
} from "@/features/page/queries/page-query.ts";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import classes from "@/features/page/tree/styles/tree.module.css";
|
import classes from "@/features/page/tree/styles/tree.module.css";
|
||||||
import { ActionIcon, Menu, rem, Text } from "@mantine/core";
|
import { ActionIcon, Menu, rem, Text } from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
@ -45,6 +45,7 @@ import { useQueryEmit } from "@/features/websocket/use-query-emit.ts";
|
|||||||
import { buildPageSlug } from "@/features/page/page.utils.ts";
|
import { buildPageSlug } from "@/features/page/page.utils.ts";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
|
import APP_ROUTE from "@/lib/app-route.ts";
|
||||||
|
|
||||||
interface SpaceTreeProps {
|
interface SpaceTreeProps {
|
||||||
spaceId: string;
|
spaceId: string;
|
||||||
@ -72,6 +73,7 @@ export default function SpaceTree({ spaceId }: SpaceTreeProps) {
|
|||||||
const mergedRef = useMergedRef(rootElement, sizeRef);
|
const mergedRef = useMergedRef(rootElement, sizeRef);
|
||||||
const isDataLoaded = useRef(false);
|
const isDataLoaded = useRef(false);
|
||||||
const { data: currentPage } = usePageQuery(slugId);
|
const { data: currentPage } = usePageQuery(slugId);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasNextPage && !isFetching) {
|
if (hasNextPage && !isFetching) {
|
||||||
@ -166,7 +168,7 @@ export default function SpaceTree({ spaceId }: SpaceTreeProps) {
|
|||||||
if (currentPage) {
|
if (currentPage) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
treeApiRef.current?.select(currentPage.id, { align: "auto" });
|
treeApiRef.current?.select(currentPage.id, { align: "auto" });
|
||||||
}, 200);
|
}, 100);
|
||||||
}
|
}
|
||||||
}, [currentPage?.id]);
|
}, [currentPage?.id]);
|
||||||
|
|
||||||
@ -175,35 +177,38 @@ export default function SpaceTree({ spaceId }: SpaceTreeProps) {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
setTreeApi(treeApiRef.current);
|
setTreeApi(treeApiRef.current);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [treeApiRef.current]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (openTreeNodes) {
|
if (location.pathname === APP_ROUTE.HOME && treeApiRef.current) {
|
||||||
treeApiRef.current.state.nodes.open.unfiltered = openTreeNodes;
|
treeApiRef.current.deselectAll();
|
||||||
}
|
}
|
||||||
}, []);
|
}, [location.pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={mergedRef} className={classes.treeContainer}>
|
<div ref={mergedRef} className={classes.treeContainer}>
|
||||||
<Tree
|
{rootElement.current && (
|
||||||
data={data}
|
<Tree
|
||||||
{...controllers}
|
data={data}
|
||||||
width={width}
|
{...controllers}
|
||||||
height={height}
|
width={width}
|
||||||
ref={treeApiRef}
|
height={height}
|
||||||
openByDefault={false}
|
ref={treeApiRef}
|
||||||
disableMultiSelection={true}
|
openByDefault={false}
|
||||||
className={classes.tree}
|
disableMultiSelection={true}
|
||||||
rowClassName={classes.row}
|
className={classes.tree}
|
||||||
rowHeight={30}
|
rowClassName={classes.row}
|
||||||
overscanCount={10}
|
rowHeight={30}
|
||||||
dndRootElement={rootElement.current}
|
overscanCount={10}
|
||||||
onToggle={() => {
|
dndRootElement={rootElement.current}
|
||||||
setOpenTreeNodes(treeApiRef.current.openState);
|
onToggle={() => {
|
||||||
}}
|
setOpenTreeNodes(treeApiRef.current.openState);
|
||||||
>
|
}}
|
||||||
{Node}
|
initialOpenState={openTreeNodes}
|
||||||
</Tree>
|
>
|
||||||
|
{Node}
|
||||||
|
</Tree>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -288,10 +293,16 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
|||||||
}, 50);
|
}, 50);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (node.willReceiveDrop && node.isClosed) {
|
if (
|
||||||
|
node.willReceiveDrop &&
|
||||||
|
node.isClosed &&
|
||||||
|
(node.children.length > 0 || node.data.hasChildren)
|
||||||
|
) {
|
||||||
handleLoadChildren(node);
|
handleLoadChildren(node);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (node.state.willReceiveDrop) node.open();
|
if (node.state.willReceiveDrop) {
|
||||||
|
node.open();
|
||||||
|
}
|
||||||
}, 650);
|
}, 650);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -124,6 +124,25 @@ export function useTreeMutation<T>(spaceId: string) {
|
|||||||
changes: { position: newPosition } as any,
|
changes: { position: newPosition } as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const previousParent = args.dragNodes[0].parent;
|
||||||
|
if (
|
||||||
|
previousParent.id !== args.parentId &&
|
||||||
|
previousParent.id !== "__REACT_ARBORIST_INTERNAL_ROOT__"
|
||||||
|
) {
|
||||||
|
// if the page was moved to another parent,
|
||||||
|
// check if the previous still has children
|
||||||
|
// if no children left, change 'hasChildren' to false, to make the page toggle arrows work properly
|
||||||
|
const childrenCount = previousParent.children.filter(
|
||||||
|
(child) => child.id !== draggedNodeId,
|
||||||
|
).length;
|
||||||
|
if (childrenCount === 0) {
|
||||||
|
tree.update({
|
||||||
|
id: previousParent.id,
|
||||||
|
changes: { ...previousParent.data, hasChildren: false } as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setData(tree.data);
|
setData(tree.data);
|
||||||
|
|
||||||
const payload: IMovePage = {
|
const payload: IMovePage = {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import Routes from "@/lib/routes";
|
import Routes from "@/lib/app-route.ts";
|
||||||
|
|
||||||
const baseUrl = import.meta.env.DEV ? "http://localhost:3000" : "";
|
const baseUrl = import.meta.env.DEV ? "http://localhost:3000" : "";
|
||||||
const api: AxiosInstance = axios.create({
|
const api: AxiosInstance = axios.create({
|
||||||
|
|||||||
9
apps/client/src/lib/app-route.ts
Normal file
9
apps/client/src/lib/app-route.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const APP_ROUTE = {
|
||||||
|
HOME: "/home",
|
||||||
|
AUTH: {
|
||||||
|
LOGIN: "/login",
|
||||||
|
SIGNUP: "/signup",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default APP_ROUTE;
|
||||||
@ -1,9 +0,0 @@
|
|||||||
const ROUTES = {
|
|
||||||
HOME: '/home',
|
|
||||||
AUTH: {
|
|
||||||
LOGIN: '/login',
|
|
||||||
SIGNUP: '/signup',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ROUTES;
|
|
||||||
@ -184,7 +184,7 @@ export class PageService {
|
|||||||
throw new BadRequestException('Invalid move position');
|
throw new BadRequestException('Invalid move position');
|
||||||
}
|
}
|
||||||
|
|
||||||
let parentPageId: string;
|
let parentPageId = null;
|
||||||
if (movedPage.parentPageId === dto.parentPageId) {
|
if (movedPage.parentPageId === dto.parentPageId) {
|
||||||
parentPageId = undefined;
|
parentPageId = undefined;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user