mirror of
https://github.com/docmost/docmost.git
synced 2026-05-21 01:04:39 +08:00
clear inheritance
This commit is contained in:
@@ -2,7 +2,7 @@ import { Group, Menu, Text, UnstyledButton } from "@mantine/core";
|
|||||||
import {
|
import {
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconLock,
|
IconLock,
|
||||||
IconWorld,
|
IconShieldLock,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -14,75 +14,75 @@ type GeneralAccessSelectProps = {
|
|||||||
value: AccessLevel;
|
value: AccessLevel;
|
||||||
onChange: (value: AccessLevel) => void;
|
onChange: (value: AccessLevel) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isInherited?: boolean;
|
hasInheritedRestriction?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function GeneralAccessSelect({
|
export function GeneralAccessSelect({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
isInherited,
|
hasInheritedRestriction,
|
||||||
}: GeneralAccessSelectProps) {
|
}: GeneralAccessSelectProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isRestricted = value === "restricted";
|
const isDirectlyRestricted = value === "restricted";
|
||||||
|
const showInheritedState = hasInheritedRestriction && !isDirectlyRestricted;
|
||||||
|
|
||||||
|
const currentLabel = showInheritedState
|
||||||
|
? t("Restricted by parent")
|
||||||
|
: isDirectlyRestricted
|
||||||
|
? t("Restricted")
|
||||||
|
: t("Open");
|
||||||
|
|
||||||
|
const currentDescription = showInheritedState
|
||||||
|
? t("Inherits restrictions from ancestor page")
|
||||||
|
: isDirectlyRestricted
|
||||||
|
? t("Only specific people can access")
|
||||||
|
: t("Everyone in this space can access");
|
||||||
|
|
||||||
|
const CurrentIcon = showInheritedState
|
||||||
|
? IconShieldLock
|
||||||
|
: isDirectlyRestricted
|
||||||
|
? IconLock
|
||||||
|
: IconShieldLock;
|
||||||
|
|
||||||
const accessOptions = [
|
const accessOptions = [
|
||||||
{
|
{
|
||||||
value: "open" as const,
|
value: "open" as const,
|
||||||
label: t("Open"),
|
label: hasInheritedRestriction ? t("Restricted by parent") : t("Open"),
|
||||||
description: t("Everyone in this space can access"),
|
description: hasInheritedRestriction
|
||||||
icon: IconWorld,
|
? t("Use only inherited restrictions")
|
||||||
|
: t("Everyone in this space can access"),
|
||||||
|
icon: IconShieldLock,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "restricted" as const,
|
value: "restricted" as const,
|
||||||
label: t("Restricted"),
|
label: t("Restricted"),
|
||||||
description: t("Only specific people can view or edit"),
|
description: hasInheritedRestriction
|
||||||
|
? t("Add restrictions on top of inherited")
|
||||||
|
: t("Only specific people can access"),
|
||||||
icon: IconLock,
|
icon: IconLock,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const currentOption = accessOptions.find((opt) => opt.value === value);
|
|
||||||
const Icon = currentOption?.icon || IconWorld;
|
|
||||||
|
|
||||||
if (isInherited) {
|
|
||||||
return (
|
|
||||||
<Group className={classes.generalAccessBox}>
|
|
||||||
<div
|
|
||||||
className={`${classes.generalAccessIcon} ${isRestricted ? classes.generalAccessIconRestricted : ""}`}
|
|
||||||
>
|
|
||||||
<Icon size={18} stroke={1.5} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Text size="sm" fw={500}>
|
|
||||||
{currentOption?.label}
|
|
||||||
</Text>
|
|
||||||
<Text size="xs" c="dimmed">
|
|
||||||
{currentOption?.description}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu withArrow disabled={disabled}>
|
<Menu withArrow disabled={disabled}>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<UnstyledButton className={classes.generalAccessBox}>
|
<UnstyledButton className={classes.generalAccessBox} disabled={disabled}>
|
||||||
<div
|
<div
|
||||||
className={`${classes.generalAccessIcon} ${isRestricted ? classes.generalAccessIconRestricted : ""}`}
|
className={`${classes.generalAccessIcon} ${isDirectlyRestricted || showInheritedState ? classes.generalAccessIconRestricted : ""}`}
|
||||||
>
|
>
|
||||||
<Icon size={18} stroke={1.5} />
|
<CurrentIcon size={18} stroke={1.5} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Group gap={4}>
|
<Group gap={4}>
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
{currentOption?.label}
|
{currentLabel}
|
||||||
</Text>
|
</Text>
|
||||||
{!disabled && <IconChevronDown size={14} />}
|
{!disabled && <IconChevronDown size={14} />}
|
||||||
</Group>
|
</Group>
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
{currentOption?.description}
|
{currentDescription}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button, Divider, Group, Loader, Select, Stack, Text } from "@mantine/core";
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
ThemeIcon,
|
||||||
|
} from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { IconArrowRight } from "@tabler/icons-react";
|
import { IconArrowRight, IconLock, IconShieldLock } from "@tabler/icons-react";
|
||||||
import { MultiMemberSelect } from "@/features/space/components/multi-member-select";
|
import { MultiMemberSelect } from "@/features/space/components/multi-member-select";
|
||||||
import {
|
import {
|
||||||
IPageRestrictionInfo,
|
IPageRestrictionInfo,
|
||||||
@@ -39,18 +50,14 @@ export function PagePermissionTab({
|
|||||||
const unrestrictMutation = useUnrestrictPageMutation();
|
const unrestrictMutation = useUnrestrictPageMutation();
|
||||||
const addPermissionMutation = useAddPagePermissionMutation();
|
const addPermissionMutation = useAddPagePermissionMutation();
|
||||||
|
|
||||||
const isRestricted =
|
const hasInheritedRestriction = restrictionInfo.hasInheritedRestriction;
|
||||||
restrictionInfo.hasDirectRestriction ||
|
const hasDirectRestriction = restrictionInfo.hasDirectRestriction;
|
||||||
restrictionInfo.hasInheritedRestriction;
|
|
||||||
const isInherited =
|
|
||||||
restrictionInfo.hasInheritedRestriction &&
|
|
||||||
!restrictionInfo.hasDirectRestriction;
|
|
||||||
const canManage = restrictionInfo.userAccess.canManage;
|
const canManage = restrictionInfo.userAccess.canManage;
|
||||||
|
|
||||||
const handleAccessChange = async (value: "open" | "restricted") => {
|
const handleDirectAccessChange = async (value: "open" | "restricted") => {
|
||||||
if (value === "restricted" && !isRestricted) {
|
if (value === "restricted" && !hasDirectRestriction) {
|
||||||
await restrictMutation.mutateAsync(pageId);
|
await restrictMutation.mutateAsync(pageId);
|
||||||
} else if (value === "open" && isRestricted) {
|
} else if (value === "open" && hasDirectRestriction) {
|
||||||
await unrestrictMutation.mutateAsync(pageId);
|
await unrestrictMutation.mutateAsync(pageId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -81,72 +88,99 @@ export function PagePermissionTab({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Stack gap="md">
|
||||||
{isRestricted && canManage && !isInherited && (
|
{hasInheritedRestriction && (
|
||||||
<>
|
<Paper className={classes.inheritedSection} p="sm" radius="sm">
|
||||||
<Group gap="xs" align="flex-end">
|
<Group gap="sm" wrap="nowrap">
|
||||||
<div style={{ flex: 1 }}>
|
<ThemeIcon
|
||||||
<MultiMemberSelect onChange={setMemberIds} />
|
size="lg"
|
||||||
</div>
|
radius="sm"
|
||||||
<Select
|
variant="light"
|
||||||
data={pagePermissionRoleData.map((r) => ({
|
color="orange"
|
||||||
label: t(r.label),
|
|
||||||
value: r.value,
|
|
||||||
}))}
|
|
||||||
value={role}
|
|
||||||
onChange={(value) => value && setRole(value)}
|
|
||||||
allowDeselect={false}
|
|
||||||
variant="filled"
|
|
||||||
w={120}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={handleAddMembers}
|
|
||||||
disabled={memberIds.length === 0}
|
|
||||||
loading={addPermissionMutation.isPending}
|
|
||||||
>
|
>
|
||||||
{t("Add")}
|
<IconShieldLock size={18} stroke={1.5} />
|
||||||
</Button>
|
</ThemeIcon>
|
||||||
|
<Box style={{ flex: 1 }}>
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{t("Inherited restriction")}
|
||||||
|
</Text>
|
||||||
|
<Group gap={4}>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
{t("Access limited by")}
|
||||||
|
</Text>
|
||||||
|
<Link
|
||||||
|
to={buildPageUrl(
|
||||||
|
spaceSlug,
|
||||||
|
restrictionInfo.id,
|
||||||
|
restrictionInfo.title,
|
||||||
|
)}
|
||||||
|
style={{ textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
<Group gap={2}>
|
||||||
|
<Text size="xs" fw={500} c="blue">
|
||||||
|
{restrictionInfo.title || t("Untitled")}
|
||||||
|
</Text>
|
||||||
|
<IconArrowRight size={12} color="var(--mantine-color-blue-6)" />
|
||||||
|
</Group>
|
||||||
|
</Link>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
<Divider />
|
</Paper>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<Box>
|
||||||
<Text size="sm" fw={500} mb="xs">
|
<Text size="sm" fw={500} mb="xs">
|
||||||
{t("General access")}
|
{t("This page")}
|
||||||
</Text>
|
</Text>
|
||||||
<GeneralAccessSelect
|
<GeneralAccessSelect
|
||||||
value={isRestricted ? "restricted" : "open"}
|
value={hasDirectRestriction ? "restricted" : "open"}
|
||||||
onChange={handleAccessChange}
|
onChange={handleDirectAccessChange}
|
||||||
disabled={!canManage || isInherited}
|
disabled={!canManage}
|
||||||
isInherited={isInherited}
|
hasInheritedRestriction={hasInheritedRestriction}
|
||||||
/>
|
/>
|
||||||
{isInherited && (
|
{!hasDirectRestriction && !hasInheritedRestriction && (
|
||||||
<div className={classes.inheritedInfo}>
|
<Text size="xs" c="dimmed" mt={4}>
|
||||||
<Text size="xs" c="dimmed">
|
{t("Everyone in this space can access this page")}
|
||||||
{t("Inherits restrictions from")}
|
</Text>
|
||||||
</Text>
|
|
||||||
<Link
|
|
||||||
to={buildPageUrl(
|
|
||||||
spaceSlug,
|
|
||||||
restrictionInfo.id,
|
|
||||||
restrictionInfo.title,
|
|
||||||
)}
|
|
||||||
style={{ textDecoration: "none" }}
|
|
||||||
>
|
|
||||||
<Group gap={4}>
|
|
||||||
<Text size="xs" fw={500}>
|
|
||||||
{restrictionInfo.title || t("Untitled")}
|
|
||||||
</Text>
|
|
||||||
<IconArrowRight size={12} />
|
|
||||||
</Group>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
{!hasDirectRestriction && hasInheritedRestriction && (
|
||||||
|
<Text size="xs" c="dimmed" mt={4}>
|
||||||
|
{t("Add additional restrictions specific to this page")}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
{isRestricted && (
|
{hasDirectRestriction && (
|
||||||
<>
|
<>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{canManage && (
|
||||||
|
<Group gap="xs" align="flex-end">
|
||||||
|
<Box style={{ flex: 1 }}>
|
||||||
|
<MultiMemberSelect onChange={setMemberIds} />
|
||||||
|
</Box>
|
||||||
|
<Select
|
||||||
|
data={pagePermissionRoleData.map((r) => ({
|
||||||
|
label: t(r.label),
|
||||||
|
value: r.value,
|
||||||
|
}))}
|
||||||
|
value={role}
|
||||||
|
onChange={(value) => value && setRole(value)}
|
||||||
|
allowDeselect={false}
|
||||||
|
variant="filled"
|
||||||
|
w={120}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={handleAddMembers}
|
||||||
|
disabled={memberIds.length === 0}
|
||||||
|
loading={addPermissionMutation.isPending}
|
||||||
|
>
|
||||||
|
{t("Add")}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Group justify="center" py="md">
|
<Group justify="center" py="md">
|
||||||
<Loader size="sm" />
|
<Loader size="sm" />
|
||||||
@@ -155,7 +189,7 @@ export function PagePermissionTab({
|
|||||||
<PagePermissionList
|
<PagePermissionList
|
||||||
pageId={pageId}
|
pageId={pageId}
|
||||||
members={permissionsData?.items || []}
|
members={permissionsData?.items || []}
|
||||||
canManage={canManage && !isInherited}
|
canManage={canManage}
|
||||||
onRemoveAll={handleRemoveAll}
|
onRemoveAll={handleRemoveAll}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -106,3 +106,14 @@
|
|||||||
background-color: var(--mantine-color-dark-6);
|
background-color: var(--mantine-color-dark-6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inheritedSection {
|
||||||
|
@mixin light {
|
||||||
|
background-color: var(--mantine-color-orange-0);
|
||||||
|
border: 1px solid var(--mantine-color-orange-2);
|
||||||
|
}
|
||||||
|
@mixin dark {
|
||||||
|
background-color: rgba(255, 146, 43, 0.08);
|
||||||
|
border: 1px solid rgba(255, 146, 43, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { useParams } from "react-router-dom";
|
|||||||
import { extractPageSlugId } from "@/lib";
|
import { extractPageSlugId } from "@/lib";
|
||||||
import { usePageQuery } from "@/features/page/queries/page-query";
|
import { usePageQuery } from "@/features/page/queries/page-query";
|
||||||
import { usePageRestrictionInfoQuery } from "@/ee/page-permission/queries/page-permission-query";
|
import { usePageRestrictionInfoQuery } from "@/ee/page-permission/queries/page-permission-query";
|
||||||
import { PagePermissionTab } from "./page-permission-tab";
|
import { PagePermissionTab } from "@/ee/page-permission";
|
||||||
import { PublishTab } from "./publish-tab";
|
import { PublishTab } from "./publish-tab";
|
||||||
|
|
||||||
type PageShareModalProps = {
|
type PageShareModalProps = {
|
||||||
@@ -67,8 +67,7 @@ export function PageShareModal({ readOnly }: PageShareModalProps) {
|
|||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
title={t("Share")}
|
title={t("Share")}
|
||||||
size="md"
|
size={600}
|
||||||
centered
|
|
||||||
>
|
>
|
||||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||||
<Tabs.List mb="md">
|
<Tabs.List mb="md">
|
||||||
|
|||||||
Reference in New Issue
Block a user