mirror of
https://github.com/docmost/docmost.git
synced 2026-05-18 07:24:04 +08:00
feat(ee): page-level access/permissions (#1971)
* Add page_hierarchy table * feat(ee): page-level permissions * pagination * rename migration fixes * fix * tabs * fix theme * cleanup * sync * page permissions notification * other fixes * sharing disbled * fix column nodes * toggle error handling
This commit is contained in:
@@ -39,7 +39,7 @@ import { formattedDate } from "@/lib/time.ts";
|
||||
import { PageStateSegmentedControl } from "@/features/user/components/page-state-pref.tsx";
|
||||
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
|
||||
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
|
||||
import ShareModal from "@/features/share/components/share-modal.tsx";
|
||||
import { PageShareModal } from "@/ee/page-permission";
|
||||
|
||||
interface PageHeaderMenuProps {
|
||||
readOnly?: boolean;
|
||||
@@ -75,7 +75,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
||||
|
||||
{!readOnly && <PageStateSegmentedControl size="xs" />}
|
||||
|
||||
<ShareModal readOnly={readOnly} />
|
||||
<PageShareModal readOnly={readOnly} />
|
||||
|
||||
<Tooltip label={t("Comments")} openDelay={250} withArrow>
|
||||
<ActionIcon
|
||||
|
||||
@@ -53,11 +53,7 @@ import {
|
||||
import { IPage, SidebarPagesParams } from "@/features/page/types/page.types.ts";
|
||||
import { queryClient } from "@/main.tsx";
|
||||
import { OpenMap } from "react-arborist/dist/main/state/open-slice";
|
||||
import {
|
||||
useDisclosure,
|
||||
useElementSize,
|
||||
useMergedRef,
|
||||
} from "@mantine/hooks";
|
||||
import { useDisclosure, useElementSize, useMergedRef } from "@mantine/hooks";
|
||||
import { useClipboard } from "@/hooks/use-clipboard";
|
||||
import { dfs } from "react-arborist/dist/module/utils";
|
||||
import { useQueryEmit } from "@/features/websocket/use-query-emit.ts";
|
||||
@@ -244,9 +240,19 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
|
||||
{isRootReady && rootElement.current && (
|
||||
<Tree
|
||||
data={filteredData}
|
||||
disableDrag={readOnly}
|
||||
disableDrop={readOnly}
|
||||
disableEdit={readOnly}
|
||||
disableDrag={
|
||||
readOnly
|
||||
? true
|
||||
: (data) => {
|
||||
return data.canEdit === false;
|
||||
}
|
||||
}
|
||||
disableDrop={
|
||||
readOnly
|
||||
? true
|
||||
: ({ parentNode }) => parentNode?.data?.canEdit === false
|
||||
}
|
||||
disableEdit={readOnly ? true : (data) => data.canEdit === false}
|
||||
{...controllers}
|
||||
width={width}
|
||||
height={rootElement.current.clientHeight}
|
||||
@@ -417,7 +423,9 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
||||
<IconFileDescription size="18" />
|
||||
)
|
||||
}
|
||||
readOnly={tree.props.disableEdit as boolean}
|
||||
readOnly={
|
||||
tree.props.disableEdit === true || node.data.canEdit === false
|
||||
}
|
||||
removeEmojiAction={handleRemoveEmoji}
|
||||
/>
|
||||
</div>
|
||||
@@ -427,7 +435,7 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
||||
<div className={classes.actions}>
|
||||
<NodeMenu node={node} treeApi={tree} spaceId={node.data.spaceId} />
|
||||
|
||||
{!tree.props.disableEdit && (
|
||||
{tree.props.disableEdit !== true && node.data.canEdit !== false && (
|
||||
<CreateNode
|
||||
node={node}
|
||||
treeApi={tree}
|
||||
@@ -532,6 +540,7 @@ function NodeMenu({ node, treeApi, spaceId }: NodeMenuProps) {
|
||||
parentPageId: duplicatedPage.parentPageId,
|
||||
icon: duplicatedPage.icon,
|
||||
hasChildren: duplicatedPage.hasChildren,
|
||||
canEdit: true,
|
||||
children: [],
|
||||
};
|
||||
|
||||
@@ -610,55 +619,56 @@ function NodeMenu({ node, treeApi, spaceId }: NodeMenuProps) {
|
||||
{t("Export page")}
|
||||
</Menu.Item>
|
||||
|
||||
{!(treeApi.props.disableEdit as boolean) && (
|
||||
<>
|
||||
<Menu.Item
|
||||
leftSection={<IconCopy size={16} />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleDuplicatePage();
|
||||
}}
|
||||
>
|
||||
{t("Duplicate")}
|
||||
</Menu.Item>
|
||||
{treeApi.props.disableEdit !== true &&
|
||||
node.data.canEdit !== false && (
|
||||
<>
|
||||
<Menu.Item
|
||||
leftSection={<IconCopy size={16} />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleDuplicatePage();
|
||||
}}
|
||||
>
|
||||
{t("Duplicate")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item
|
||||
leftSection={<IconArrowRight size={16} />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openMovePageModal();
|
||||
}}
|
||||
>
|
||||
{t("Move")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<IconArrowRight size={16} />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openMovePageModal();
|
||||
}}
|
||||
>
|
||||
{t("Move")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item
|
||||
leftSection={<IconCopy size={16} />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openCopyPageModal();
|
||||
}}
|
||||
>
|
||||
{t("Copy to space")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<IconCopy size={16} />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openCopyPageModal();
|
||||
}}
|
||||
>
|
||||
{t("Copy to space")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
c="red"
|
||||
leftSection={<IconTrash size={16} />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openDeleteModal({ onConfirm: () => treeApi?.delete(node) });
|
||||
}}
|
||||
>
|
||||
{t("Move to trash")}
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
c="red"
|
||||
leftSection={<IconTrash size={16} />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openDeleteModal({ onConfirm: () => treeApi?.delete(node) });
|
||||
}}
|
||||
>
|
||||
{t("Move to trash")}
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
||||
|
||||
@@ -7,5 +7,6 @@ export type SpaceTreeNode = {
|
||||
spaceId: string;
|
||||
parentPageId: string;
|
||||
hasChildren: boolean;
|
||||
canEdit?: boolean;
|
||||
children: SpaceTreeNode[];
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ export function buildTree(pages: IPage[]): SpaceTreeNode[] {
|
||||
hasChildren: page.hasChildren,
|
||||
spaceId: page.spaceId,
|
||||
parentPageId: page.parentPageId,
|
||||
canEdit: page.canEdit ?? page.permissions?.canEdit,
|
||||
children: [],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -18,10 +18,15 @@ export interface IPage {
|
||||
deletedAt: Date;
|
||||
position: string;
|
||||
hasChildren: boolean;
|
||||
canEdit?: boolean;
|
||||
creator: ICreator;
|
||||
lastUpdatedBy: ILastUpdatedBy;
|
||||
deletedBy: IDeletedBy;
|
||||
space: Partial<ISpace>;
|
||||
permissions?: {
|
||||
canEdit: boolean;
|
||||
hasRestriction: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface ICreator {
|
||||
|
||||
Reference in New Issue
Block a user