mirror of
https://github.com/docmost/docmost.git
synced 2026-05-08 23:33:09 +08:00
clear inheritance
This commit is contained in:
@@ -2,7 +2,7 @@ import { Group, Menu, Text, UnstyledButton } from "@mantine/core";
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconLock,
|
||||
IconWorld,
|
||||
IconShieldLock,
|
||||
IconCheck,
|
||||
} from "@tabler/icons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -14,75 +14,75 @@ type GeneralAccessSelectProps = {
|
||||
value: AccessLevel;
|
||||
onChange: (value: AccessLevel) => void;
|
||||
disabled?: boolean;
|
||||
isInherited?: boolean;
|
||||
hasInheritedRestriction?: boolean;
|
||||
};
|
||||
|
||||
export function GeneralAccessSelect({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
isInherited,
|
||||
hasInheritedRestriction,
|
||||
}: GeneralAccessSelectProps) {
|
||||
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 = [
|
||||
{
|
||||
value: "open" as const,
|
||||
label: t("Open"),
|
||||
description: t("Everyone in this space can access"),
|
||||
icon: IconWorld,
|
||||
label: hasInheritedRestriction ? t("Restricted by parent") : t("Open"),
|
||||
description: hasInheritedRestriction
|
||||
? t("Use only inherited restrictions")
|
||||
: t("Everyone in this space can access"),
|
||||
icon: IconShieldLock,
|
||||
},
|
||||
{
|
||||
value: "restricted" as const,
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
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 (
|
||||
<Menu withArrow disabled={disabled}>
|
||||
<Menu.Target>
|
||||
<UnstyledButton className={classes.generalAccessBox}>
|
||||
<UnstyledButton className={classes.generalAccessBox} disabled={disabled}>
|
||||
<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 style={{ flex: 1 }}>
|
||||
<Group gap={4}>
|
||||
<Text size="sm" fw={500}>
|
||||
{currentOption?.label}
|
||||
{currentLabel}
|
||||
</Text>
|
||||
{!disabled && <IconChevronDown size={14} />}
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed">
|
||||
{currentOption?.description}
|
||||
{currentDescription}
|
||||
</Text>
|
||||
</div>
|
||||
</UnstyledButton>
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
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 { 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 {
|
||||
IPageRestrictionInfo,
|
||||
@@ -39,18 +50,14 @@ export function PagePermissionTab({
|
||||
const unrestrictMutation = useUnrestrictPageMutation();
|
||||
const addPermissionMutation = useAddPagePermissionMutation();
|
||||
|
||||
const isRestricted =
|
||||
restrictionInfo.hasDirectRestriction ||
|
||||
restrictionInfo.hasInheritedRestriction;
|
||||
const isInherited =
|
||||
restrictionInfo.hasInheritedRestriction &&
|
||||
!restrictionInfo.hasDirectRestriction;
|
||||
const hasInheritedRestriction = restrictionInfo.hasInheritedRestriction;
|
||||
const hasDirectRestriction = restrictionInfo.hasDirectRestriction;
|
||||
const canManage = restrictionInfo.userAccess.canManage;
|
||||
|
||||
const handleAccessChange = async (value: "open" | "restricted") => {
|
||||
if (value === "restricted" && !isRestricted) {
|
||||
const handleDirectAccessChange = async (value: "open" | "restricted") => {
|
||||
if (value === "restricted" && !hasDirectRestriction) {
|
||||
await restrictMutation.mutateAsync(pageId);
|
||||
} else if (value === "open" && isRestricted) {
|
||||
} else if (value === "open" && hasDirectRestriction) {
|
||||
await unrestrictMutation.mutateAsync(pageId);
|
||||
}
|
||||
};
|
||||
@@ -81,72 +88,99 @@ export function PagePermissionTab({
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
{isRestricted && canManage && !isInherited && (
|
||||
<>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<div style={{ flex: 1 }}>
|
||||
<MultiMemberSelect onChange={setMemberIds} />
|
||||
</div>
|
||||
<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}
|
||||
<Stack gap="md">
|
||||
{hasInheritedRestriction && (
|
||||
<Paper className={classes.inheritedSection} p="sm" radius="sm">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<ThemeIcon
|
||||
size="lg"
|
||||
radius="sm"
|
||||
variant="light"
|
||||
color="orange"
|
||||
>
|
||||
{t("Add")}
|
||||
</Button>
|
||||
<IconShieldLock size={18} stroke={1.5} />
|
||||
</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>
|
||||
<Divider />
|
||||
</>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Box>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
{t("General access")}
|
||||
{t("This page")}
|
||||
</Text>
|
||||
<GeneralAccessSelect
|
||||
value={isRestricted ? "restricted" : "open"}
|
||||
onChange={handleAccessChange}
|
||||
disabled={!canManage || isInherited}
|
||||
isInherited={isInherited}
|
||||
value={hasDirectRestriction ? "restricted" : "open"}
|
||||
onChange={handleDirectAccessChange}
|
||||
disabled={!canManage}
|
||||
hasInheritedRestriction={hasInheritedRestriction}
|
||||
/>
|
||||
{isInherited && (
|
||||
<div className={classes.inheritedInfo}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t("Inherits restrictions from")}
|
||||
</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>
|
||||
{!hasDirectRestriction && !hasInheritedRestriction && (
|
||||
<Text size="xs" c="dimmed" mt={4}>
|
||||
{t("Everyone in this space can access this page")}
|
||||
</Text>
|
||||
)}
|
||||
</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 ? (
|
||||
<Group justify="center" py="md">
|
||||
<Loader size="sm" />
|
||||
@@ -155,7 +189,7 @@ export function PagePermissionTab({
|
||||
<PagePermissionList
|
||||
pageId={pageId}
|
||||
members={permissionsData?.items || []}
|
||||
canManage={canManage && !isInherited}
|
||||
canManage={canManage}
|
||||
onRemoveAll={handleRemoveAll}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -106,3 +106,14 @@
|
||||
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 { usePageQuery } from "@/features/page/queries/page-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";
|
||||
|
||||
type PageShareModalProps = {
|
||||
@@ -67,8 +67,7 @@ export function PageShareModal({ readOnly }: PageShareModalProps) {
|
||||
opened={opened}
|
||||
onClose={close}
|
||||
title={t("Share")}
|
||||
size="md"
|
||||
centered
|
||||
size={600}
|
||||
>
|
||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||
<Tabs.List mb="md">
|
||||
|
||||
Reference in New Issue
Block a user