✨(frontend) subdocs can manage link reach
The subdocs can now have their own link reach properties, dissociated from the parent document.
This commit is contained in:
@@ -11,6 +11,7 @@ and this project adheres to
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- ✨(backend) allow masking documents from the list view #1171
|
- ✨(backend) allow masking documents from the list view #1171
|
||||||
|
- ✨(frontend) subdocs can manage link reach #1190
|
||||||
- ✨(frontend) add duplicate action to doc tree #1175
|
- ✨(frontend) add duplicate action to doc tree #1175
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ test.describe('Inherited share accesses', () => {
|
|||||||
|
|
||||||
await verifyDocName(page, parentTitle);
|
await verifyDocName(page, parentTitle);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
test.describe('Inherited share link', () => {
|
|
||||||
test('it checks if the link is inherited', async ({ page, browserName }) => {
|
test('it checks if the link is inherited', async ({ page, browserName }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
// Create root doc
|
// Create root doc
|
||||||
@@ -47,12 +45,50 @@ test.describe('Inherited share link', () => {
|
|||||||
// Create sub page
|
// Create sub page
|
||||||
await createRootSubPage(page, browserName, 'sub-page');
|
await createRootSubPage(page, browserName, 'sub-page');
|
||||||
|
|
||||||
// // verify share link is restricted and reader
|
// Verify share link is like the parent document
|
||||||
await page.getByRole('button', { name: 'Share' }).click();
|
await page.getByRole('button', { name: 'Share' }).click();
|
||||||
// await expect(page.getByText('Inherited share')).toBeVisible();
|
|
||||||
const docVisibilityCard = page.getByLabel('Doc visibility card');
|
const docVisibilityCard = page.getByLabel('Doc visibility card');
|
||||||
await expect(docVisibilityCard).toBeVisible();
|
|
||||||
await expect(docVisibilityCard.getByText('Connected')).toBeVisible();
|
await expect(docVisibilityCard.getByText('Connected')).toBeVisible();
|
||||||
await expect(docVisibilityCard.getByText('Reading')).toBeVisible();
|
await expect(docVisibilityCard.getByText('Reading')).toBeVisible();
|
||||||
|
|
||||||
|
// Verify inherited link
|
||||||
|
await docVisibilityCard.getByText('Connected').click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Private' }),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
// Update child link
|
||||||
|
await page.getByRole('menuitem', { name: 'Public' }).click();
|
||||||
|
|
||||||
|
await docVisibilityCard.getByText('Reading').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Editing' }).click();
|
||||||
|
|
||||||
|
await expect(docVisibilityCard.getByText('Connected')).toBeHidden();
|
||||||
|
await expect(docVisibilityCard.getByText('Reading')).toBeHidden();
|
||||||
|
await expect(
|
||||||
|
docVisibilityCard.getByText('Public', {
|
||||||
|
exact: true,
|
||||||
|
}),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(docVisibilityCard.getByText('Editing')).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
docVisibilityCard.getByText(
|
||||||
|
'The link sharing rules differ from the parent document',
|
||||||
|
),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Restore inherited link
|
||||||
|
await page.getByRole('button', { name: 'Restore' }).click();
|
||||||
|
|
||||||
|
await expect(docVisibilityCard.getByText('Connected')).toBeVisible();
|
||||||
|
await expect(docVisibilityCard.getByText('Reading')).toBeVisible();
|
||||||
|
await expect(docVisibilityCard.getByText('Public')).toBeHidden();
|
||||||
|
await expect(docVisibilityCard.getByText('Editing')).toBeHidden();
|
||||||
|
await expect(
|
||||||
|
docVisibilityCard.getByText(
|
||||||
|
'The link sharing rules differ from the parent document',
|
||||||
|
),
|
||||||
|
).toBeHidden();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
import { Doc, KEY_DOC } from '@/docs/doc-management';
|
import { Doc, KEY_DOC } from '@/docs/doc-management';
|
||||||
@@ -39,6 +41,8 @@ export function useUpdateDocLink({
|
|||||||
}: UpdateDocLinkProps = {}) {
|
}: UpdateDocLinkProps = {}) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { broadcast } = useBroadcastStore();
|
const { broadcast } = useBroadcastStore();
|
||||||
|
const { toast } = useToastProvider();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return useMutation<Doc, APIError, UpdateDocLinkParams>({
|
return useMutation<Doc, APIError, UpdateDocLinkParams>({
|
||||||
mutationFn: updateDocLink,
|
mutationFn: updateDocLink,
|
||||||
@@ -52,6 +56,14 @@ export function useUpdateDocLink({
|
|||||||
// Broadcast to every user connected to the document
|
// Broadcast to every user connected to the document
|
||||||
broadcast(`${KEY_DOC}-${variable.id}`);
|
broadcast(`${KEY_DOC}-${variable.id}`);
|
||||||
|
|
||||||
|
toast(
|
||||||
|
t('The document visibility has been updated.'),
|
||||||
|
VariantType.SUCCESS,
|
||||||
|
{
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
onSuccess?.(data);
|
onSuccess?.(data);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Button } from '@openfun/cunningham-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
|
import { Box, Text } from '@/components';
|
||||||
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
import {
|
||||||
|
Doc,
|
||||||
|
KEY_DOC,
|
||||||
|
KEY_LIST_DOC,
|
||||||
|
useUpdateDocLink,
|
||||||
|
} from '@/docs/doc-management';
|
||||||
|
|
||||||
|
import Desync from './../assets/desynchro.svg';
|
||||||
|
import Undo from './../assets/undo.svg';
|
||||||
|
|
||||||
|
interface DocDesynchronizedProps {
|
||||||
|
doc: Doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocDesynchronized = ({ doc }: DocDesynchronizedProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
|
const { mutate: updateDocLink } = useUpdateDocLink({
|
||||||
|
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
$background={colorsTokens['primary-100']}
|
||||||
|
$padding="3xs"
|
||||||
|
$direction="row"
|
||||||
|
$align="center"
|
||||||
|
$justify="space-between"
|
||||||
|
$gap={spacingsTokens['4xs']}
|
||||||
|
$color={colorsTokens['primary-800']}
|
||||||
|
$css={css`
|
||||||
|
border: 1px solid ${colorsTokens['primary-300']};
|
||||||
|
border-radius: ${spacingsTokens['2xs']};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Box $direction="row" $align="center" $gap={spacingsTokens['3xs']}>
|
||||||
|
<Desync />
|
||||||
|
<Text $size="xs" $theme="primary" $variation="800" $weight="400">
|
||||||
|
{t('The link sharing rules differ from the parent document')}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
{doc.abilities.accesses_manage && (
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
updateDocLink({
|
||||||
|
id: doc.id,
|
||||||
|
link_reach: doc.ancestors_link_reach,
|
||||||
|
link_role: doc?.ancestors_link_role || undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
color="primary-text"
|
||||||
|
icon={<Undo />}
|
||||||
|
>
|
||||||
|
{t('Restore')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -51,8 +51,18 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
|||||||
|
|
||||||
const { isDesktop } = useResponsiveStore();
|
const { isDesktop } = useResponsiveStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The modal content height is calculated based on the viewport height.
|
||||||
|
* The formula is:
|
||||||
|
* 100dvh - 2em - 12px - 34px
|
||||||
|
* - 34px is the height of the modal title in mobile
|
||||||
|
* - 2em is the padding of the modal content
|
||||||
|
* - 12px is the padding of the modal footer
|
||||||
|
* - 690px is the height of the content in desktop
|
||||||
|
* This ensures that the modal content is always visible and does not overflow.
|
||||||
|
*/
|
||||||
const modalContentHeight = isDesktop
|
const modalContentHeight = isDesktop
|
||||||
? 'min(690px, calc(100dvh - 2em - 12px - 34px))' // 100dvh - 2em - 12px is the max cunningham modal height. 690px is the height of the content in desktop ad 34px is the height of the modal title in mobile
|
? 'min(690px, calc(100dvh - 2em - 12px - 34px))'
|
||||||
: `calc(100dvh - 34px)`;
|
: `calc(100dvh - 34px)`;
|
||||||
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
||||||
const [userQuery, setUserQuery] = useState('');
|
const [userQuery, setUserQuery] = useState('');
|
||||||
@@ -230,13 +240,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box ref={handleRef}>
|
<Box ref={handleRef}>
|
||||||
{showFooter && (
|
{showFooter && <DocShareModalFooter doc={doc} onClose={onClose} />}
|
||||||
<DocShareModalFooter
|
|
||||||
doc={doc}
|
|
||||||
onClose={onClose}
|
|
||||||
canEditVisibility={canShare}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -7,17 +7,15 @@ import { Doc, useCopyDocLink } from '@/docs/doc-management';
|
|||||||
|
|
||||||
import { DocVisibility } from './DocVisibility';
|
import { DocVisibility } from './DocVisibility';
|
||||||
|
|
||||||
type Props = {
|
type DocShareModalFooterProps = {
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
canEditVisibility?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocShareModalFooter = ({
|
export const DocShareModalFooter = ({
|
||||||
doc,
|
doc,
|
||||||
onClose,
|
onClose,
|
||||||
canEditVisibility = true,
|
}: DocShareModalFooterProps) => {
|
||||||
}: Props) => {
|
|
||||||
const copyDocLink = useCopyDocLink(doc.id);
|
const copyDocLink = useCopyDocLink(doc.id);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
@@ -29,7 +27,7 @@ export const DocShareModalFooter = ({
|
|||||||
>
|
>
|
||||||
<HorizontalSeparator $withPadding={true} customPadding="12px" />
|
<HorizontalSeparator $withPadding={true} customPadding="12px" />
|
||||||
|
|
||||||
<DocVisibility doc={doc} canEdit={canEditVisibility} />
|
<DocVisibility doc={doc} />
|
||||||
<HorizontalSeparator customPadding="12px" />
|
<HorizontalSeparator customPadding="12px" />
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { useMemo } from 'react';
|
||||||
Button,
|
|
||||||
VariantType,
|
|
||||||
useToastProvider,
|
|
||||||
} from '@openfun/cunningham-react';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { css } from 'styled-components';
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
@@ -29,89 +24,54 @@ import { useResponsiveStore } from '@/stores';
|
|||||||
|
|
||||||
import { useTranslatedShareSettings } from '../hooks/';
|
import { useTranslatedShareSettings } from '../hooks/';
|
||||||
|
|
||||||
import Desync from './../assets/desynchro.svg';
|
import { DocDesynchronized } from './DocDesynchronized';
|
||||||
import Undo from './../assets/undo.svg';
|
|
||||||
|
|
||||||
interface DocVisibilityProps {
|
interface DocVisibilityProps {
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
canEdit?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
export const DocVisibility = ({ doc }: DocVisibilityProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToastProvider();
|
|
||||||
const { isDesktop } = useResponsiveStore();
|
const { isDesktop } = useResponsiveStore();
|
||||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||||
const canManage = doc.abilities.accesses_manage && canEdit;
|
const canManage = doc.abilities.accesses_manage;
|
||||||
const [linkReach, setLinkReach] = useState<LinkReach>(getDocLinkReach(doc));
|
const docLinkReach = getDocLinkReach(doc);
|
||||||
const [docLinkRole, setDocLinkRole] = useState<LinkRole>(
|
const docLinkRole = doc.computed_link_role ?? LinkRole.READER;
|
||||||
doc.computed_link_role ?? LinkRole.READER,
|
const { isDesynchronized } = useTreeUtils(doc);
|
||||||
);
|
|
||||||
const { isDesyncronized } = useTreeUtils(doc);
|
|
||||||
|
|
||||||
const { linkModeTranslations, linkReachChoices, linkReachTranslations } =
|
const { linkModeTranslations, linkReachChoices, linkReachTranslations } =
|
||||||
useTranslatedShareSettings();
|
useTranslatedShareSettings();
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
docLinkRole === LinkRole.READER
|
docLinkRole === LinkRole.READER
|
||||||
? linkReachChoices[linkReach].descriptionReadOnly
|
? linkReachChoices[docLinkReach].descriptionReadOnly
|
||||||
: linkReachChoices[linkReach].descriptionEdit;
|
: linkReachChoices[docLinkReach].descriptionEdit;
|
||||||
|
|
||||||
const api = useUpdateDocLink({
|
const { mutate: updateDocLink } = useUpdateDocLink({
|
||||||
onSuccess: () => {
|
|
||||||
toast(
|
|
||||||
t('The document visibility has been updated.'),
|
|
||||||
VariantType.SUCCESS,
|
|
||||||
{
|
|
||||||
duration: 4000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateReach = useCallback(
|
|
||||||
(link_reach: LinkReach, link_role?: LinkRole) => {
|
|
||||||
const params: {
|
|
||||||
id: string;
|
|
||||||
link_reach: LinkReach;
|
|
||||||
link_role?: LinkRole;
|
|
||||||
} = {
|
|
||||||
id: doc.id,
|
|
||||||
link_reach,
|
|
||||||
};
|
|
||||||
|
|
||||||
api.mutate(params);
|
|
||||||
setLinkReach(link_reach);
|
|
||||||
if (link_role) {
|
|
||||||
params.link_role = link_role;
|
|
||||||
setDocLinkRole(link_role);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[api, doc.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateLinkRole = useCallback(
|
|
||||||
(link_role: LinkRole) => {
|
|
||||||
api.mutate({ id: doc.id, link_role });
|
|
||||||
setDocLinkRole(link_role);
|
|
||||||
},
|
|
||||||
[api, doc.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
const linkReachOptions: DropdownMenuOption[] = useMemo(() => {
|
const linkReachOptions: DropdownMenuOption[] = useMemo(() => {
|
||||||
return Object.values(LinkReach).map((key) => {
|
return Object.values(LinkReach).map((key) => {
|
||||||
const isDisabled =
|
const isDisabled = doc.abilities.link_select_options[key] === undefined;
|
||||||
doc.abilities.link_select_options[key as LinkReach] === undefined;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: linkReachTranslations[key as LinkReach],
|
label: linkReachTranslations[key],
|
||||||
callback: () => updateReach(key as LinkReach),
|
callback: () =>
|
||||||
isSelected: linkReach === (key as LinkReach),
|
updateDocLink({
|
||||||
|
id: doc.id,
|
||||||
|
link_reach: key,
|
||||||
|
}),
|
||||||
|
isSelected: docLinkReach === key,
|
||||||
disabled: isDisabled,
|
disabled: isDisabled,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [doc, linkReach, linkReachTranslations, updateReach]);
|
}, [
|
||||||
|
doc.abilities.link_select_options,
|
||||||
|
doc.id,
|
||||||
|
docLinkReach,
|
||||||
|
linkReachTranslations,
|
||||||
|
updateDocLink,
|
||||||
|
]);
|
||||||
|
|
||||||
const haveDisabledOptions = linkReachOptions.some(
|
const haveDisabledOptions = linkReachOptions.some(
|
||||||
(option) => option.disabled,
|
(option) => option.disabled,
|
||||||
@@ -120,41 +80,29 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
|||||||
const showLinkRoleOptions = doc.computed_link_reach !== LinkReach.RESTRICTED;
|
const showLinkRoleOptions = doc.computed_link_reach !== LinkReach.RESTRICTED;
|
||||||
|
|
||||||
const linkRoleOptions: DropdownMenuOption[] = useMemo(() => {
|
const linkRoleOptions: DropdownMenuOption[] = useMemo(() => {
|
||||||
const options = doc.abilities.link_select_options[linkReach] ?? [];
|
const options = doc.abilities.link_select_options[docLinkReach] ?? [];
|
||||||
return Object.values(LinkRole).map((key) => {
|
return Object.values(LinkRole).map((key) => {
|
||||||
const isDisabled = !options.includes(key);
|
const isDisabled = !options.includes(key);
|
||||||
return {
|
return {
|
||||||
label: linkModeTranslations[key],
|
label: linkModeTranslations[key],
|
||||||
callback: () => updateLinkRole(key),
|
callback: () => updateDocLink({ id: doc.id, link_role: key }),
|
||||||
isSelected: docLinkRole === key,
|
isSelected: docLinkRole === key,
|
||||||
disabled: isDisabled,
|
disabled: isDisabled,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [doc, docLinkRole, linkModeTranslations, updateLinkRole, linkReach]);
|
}, [
|
||||||
|
doc.abilities.link_select_options,
|
||||||
|
doc.id,
|
||||||
|
docLinkReach,
|
||||||
|
docLinkRole,
|
||||||
|
linkModeTranslations,
|
||||||
|
updateDocLink,
|
||||||
|
]);
|
||||||
|
|
||||||
const haveDisabledLinkRoleOptions = linkRoleOptions.some(
|
const haveDisabledLinkRoleOptions = linkRoleOptions.some(
|
||||||
(option) => option.disabled,
|
(option) => option.disabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
const undoDesync = () => {
|
|
||||||
const params: {
|
|
||||||
id: string;
|
|
||||||
link_reach: LinkReach;
|
|
||||||
link_role?: LinkRole;
|
|
||||||
} = {
|
|
||||||
id: doc.id,
|
|
||||||
link_reach: doc.ancestors_link_reach,
|
|
||||||
};
|
|
||||||
if (doc.ancestors_link_role) {
|
|
||||||
params.link_role = doc.ancestors_link_role;
|
|
||||||
}
|
|
||||||
api.mutate(params);
|
|
||||||
setLinkReach(doc.ancestors_link_reach);
|
|
||||||
if (doc.ancestors_link_role) {
|
|
||||||
setDocLinkRole(doc.ancestors_link_role);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
$padding={{ horizontal: 'base' }}
|
$padding={{ horizontal: 'base' }}
|
||||||
@@ -163,40 +111,9 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
|||||||
className="--docs--doc-visibility"
|
className="--docs--doc-visibility"
|
||||||
>
|
>
|
||||||
<Text $weight="700" $size="sm" $variation="700">
|
<Text $weight="700" $size="sm" $variation="700">
|
||||||
{t('Link parameters')}
|
{t('Link settings')}
|
||||||
</Text>
|
</Text>
|
||||||
{isDesyncronized && (
|
{isDesynchronized && <DocDesynchronized doc={doc} />}
|
||||||
<Box
|
|
||||||
$background={colorsTokens['primary-100']}
|
|
||||||
$padding="3xs"
|
|
||||||
$direction="row"
|
|
||||||
$align="center"
|
|
||||||
$justify="space-between"
|
|
||||||
$gap={spacingsTokens['4xs']}
|
|
||||||
$color={colorsTokens['primary-800']}
|
|
||||||
$css={css`
|
|
||||||
border: 1px solid ${colorsTokens['primary-300']};
|
|
||||||
border-radius: ${spacingsTokens['2xs']};
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<Box $direction="row" $align="center" $gap={spacingsTokens['3xs']}>
|
|
||||||
<Desync />
|
|
||||||
<Text $size="xs" $theme="primary" $variation="800" $weight="400">
|
|
||||||
{t('Sharing rules differ from the parent page')}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
{doc.abilities.accesses_manage && (
|
|
||||||
<Button
|
|
||||||
onClick={undoDesync}
|
|
||||||
size="small"
|
|
||||||
color="primary-text"
|
|
||||||
icon={<Undo />}
|
|
||||||
>
|
|
||||||
{t('Restore')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Box
|
<Box
|
||||||
$direction="row"
|
$direction="row"
|
||||||
$align="center"
|
$align="center"
|
||||||
@@ -231,7 +148,7 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
|||||||
<Icon
|
<Icon
|
||||||
$theme={canManage ? 'primary' : 'greyscale'}
|
$theme={canManage ? 'primary' : 'greyscale'}
|
||||||
$variation={canManage ? '800' : '600'}
|
$variation={canManage ? '800' : '600'}
|
||||||
iconName={linkReachChoices[linkReach].icon}
|
iconName={linkReachChoices[docLinkReach].icon}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
$theme={canManage ? 'primary' : 'greyscale'}
|
$theme={canManage ? 'primary' : 'greyscale'}
|
||||||
@@ -239,7 +156,7 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
|||||||
$weight="500"
|
$weight="500"
|
||||||
$size="md"
|
$size="md"
|
||||||
>
|
>
|
||||||
{linkReachChoices[linkReach].label}
|
{linkReachChoices[docLinkReach].label}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
@@ -251,7 +168,7 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
{showLinkRoleOptions && (
|
{showLinkRoleOptions && (
|
||||||
<Box $direction="row" $align="center" $gap={spacingsTokens['3xs']}>
|
<Box $direction="row" $align="center" $gap={spacingsTokens['3xs']}>
|
||||||
{linkReach !== LinkReach.RESTRICTED && (
|
{docLinkReach !== LinkReach.RESTRICTED && (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
disabled={!canManage}
|
disabled={!canManage}
|
||||||
showArrow={true}
|
showArrow={true}
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ export const useTreeUtils = (doc: Doc) => {
|
|||||||
isParent: doc.nb_accesses_ancestors <= 1, // it is a parent
|
isParent: doc.nb_accesses_ancestors <= 1, // it is a parent
|
||||||
isChild: doc.nb_accesses_ancestors > 1, // it is a child
|
isChild: doc.nb_accesses_ancestors > 1, // it is a child
|
||||||
isCurrentParent: treeContext?.root?.id === doc.id || doc.depth === 1, // it can be a child but not for the current user
|
isCurrentParent: treeContext?.root?.id === doc.id || doc.depth === 1, // it can be a child but not for the current user
|
||||||
isDesyncronized: !!(
|
isDesynchronized: !!(
|
||||||
doc.ancestors_link_reach &&
|
doc.ancestors_link_reach &&
|
||||||
doc.ancestors_link_role &&
|
|
||||||
(doc.computed_link_reach !== doc.ancestors_link_reach ||
|
(doc.computed_link_reach !== doc.ancestors_link_reach ||
|
||||||
doc.computed_link_role !== doc.ancestors_link_role)
|
doc.computed_link_role !== doc.ancestors_link_role)
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user