✨(frontend) doc page when deleted
Whe the doc is deleted, the doc page is a bit different, we have to adapt the doc header to add some information and actions that are relevant for a deleted doc.
This commit is contained in:
@@ -96,7 +96,7 @@ test.describe('Doc Header', () => {
|
|||||||
page.getByRole('heading', { name: 'Delete a doc' }),
|
page.getByRole('heading', { name: 'Delete a doc' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await expect(page.getByText(`This document and any sub-`)).toBeVisible();
|
await expect(page.getByText(`This document will be`)).toBeVisible();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByRole('button', {
|
.getByRole('button', {
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
clickInEditorMenu,
|
||||||
clickInGridMenu,
|
clickInGridMenu,
|
||||||
createDoc,
|
createDoc,
|
||||||
getGridRow,
|
getGridRow,
|
||||||
verifyDocName,
|
verifyDocName,
|
||||||
} from './utils-common';
|
} from './utils-common';
|
||||||
import { addNewMember } from './utils-share';
|
import { addNewMember } from './utils-share';
|
||||||
|
import {
|
||||||
|
addChild,
|
||||||
|
createRootSubPage,
|
||||||
|
navigateToPageFromTree,
|
||||||
|
} from './utils-sub-pages';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -74,4 +80,71 @@ test.describe('Doc Trashbin', () => {
|
|||||||
await page.getByRole('link', { name: 'Trashbin' }).click();
|
await page.getByRole('link', { name: 'Trashbin' }).click();
|
||||||
await expect(row2.getByText(title2)).toBeHidden();
|
await expect(row2.getByText(title2)).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it controls UI and interaction from the doc page', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
const [topParent] = await createDoc(
|
||||||
|
page,
|
||||||
|
'my-trash-editor-doc',
|
||||||
|
browserName,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
await verifyDocName(page, topParent);
|
||||||
|
const { name: subDocName } = await createRootSubPage(
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
'my-trash-editor-subdoc',
|
||||||
|
);
|
||||||
|
|
||||||
|
const subsubDocName = await addChild({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
docParent: subDocName,
|
||||||
|
});
|
||||||
|
await verifyDocName(page, subsubDocName);
|
||||||
|
|
||||||
|
await navigateToPageFromTree({ page, title: subDocName });
|
||||||
|
await verifyDocName(page, subDocName);
|
||||||
|
|
||||||
|
await clickInEditorMenu(page, 'Delete sub-document');
|
||||||
|
await page.getByRole('button', { name: 'Delete document' }).click();
|
||||||
|
await verifyDocName(page, topParent);
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Back to homepage' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Trashbin' }).click();
|
||||||
|
const row = await getGridRow(page, subDocName);
|
||||||
|
await row.getByText(subDocName).click();
|
||||||
|
await verifyDocName(page, subDocName);
|
||||||
|
|
||||||
|
await expect(page.getByLabel('Alert deleted document')).toBeVisible();
|
||||||
|
await expect(page.getByRole('button', { name: 'Share' })).toBeDisabled();
|
||||||
|
await expect(page.locator('.bn-editor')).toHaveAttribute(
|
||||||
|
'contenteditable',
|
||||||
|
'false',
|
||||||
|
);
|
||||||
|
const docTree = page.getByTestId('doc-tree');
|
||||||
|
await expect(docTree.getByText(topParent)).toBeHidden();
|
||||||
|
await expect(
|
||||||
|
docTree.getByText(subDocName, {
|
||||||
|
exact: true,
|
||||||
|
}),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(docTree.getByText(subsubDocName)).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
docTree
|
||||||
|
.locator(".--docs-sub-page-item[aria-disabled='true']")
|
||||||
|
.getByText(subsubDocName),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Restore' }).click();
|
||||||
|
await expect(page.getByLabel('Alert deleted document')).toBeHidden();
|
||||||
|
await expect(page.locator('.bn-editor')).toHaveAttribute(
|
||||||
|
'contenteditable',
|
||||||
|
'true',
|
||||||
|
);
|
||||||
|
await expect(page.getByRole('button', { name: 'Share' })).toBeEnabled();
|
||||||
|
await expect(docTree.getByText(topParent)).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -327,6 +327,11 @@ export async function waitForLanguageSwitch(
|
|||||||
await page.getByRole('menuitem', { name: lang.label }).click();
|
await page.getByRole('menuitem', { name: lang.label }).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const clickInEditorMenu = async (page: Page, textButton: string) => {
|
||||||
|
await page.getByRole('button', { name: 'Open the document options' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: textButton }).click();
|
||||||
|
};
|
||||||
|
|
||||||
export const clickInGridMenu = async (
|
export const clickInGridMenu = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
row: Locator,
|
row: Locator,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Page, expect } from '@playwright/test';
|
import { Page, expect } from '@playwright/test';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
BrowserName,
|
||||||
randomName,
|
randomName,
|
||||||
updateDocTitle,
|
updateDocTitle,
|
||||||
verifyDocName,
|
verifyDocName,
|
||||||
@@ -9,7 +10,7 @@ import {
|
|||||||
|
|
||||||
export const createRootSubPage = async (
|
export const createRootSubPage = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
browserName: string,
|
browserName: BrowserName,
|
||||||
docName: string,
|
docName: string,
|
||||||
isMobile: boolean = false,
|
isMobile: boolean = false,
|
||||||
) => {
|
) => {
|
||||||
@@ -67,6 +68,47 @@ export const clickOnAddRootSubPage = async (page: Page) => {
|
|||||||
await rootItem.getByTestId('doc-tree-item-actions-add-child').click();
|
await rootItem.getByTestId('doc-tree-item-actions-add-child').click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const addChild = async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
docParent,
|
||||||
|
}: {
|
||||||
|
page: Page;
|
||||||
|
browserName: BrowserName;
|
||||||
|
docParent: string;
|
||||||
|
}) => {
|
||||||
|
let item = page.getByTestId('doc-tree-root-item');
|
||||||
|
|
||||||
|
const isParent = await item
|
||||||
|
.filter({
|
||||||
|
hasText: docParent,
|
||||||
|
})
|
||||||
|
.first()
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if (!isParent) {
|
||||||
|
const items = page.getByRole('treeitem');
|
||||||
|
|
||||||
|
item = items
|
||||||
|
.filter({
|
||||||
|
hasText: docParent,
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.hover();
|
||||||
|
await item.getByTestId('doc-tree-item-actions-add-child').click();
|
||||||
|
|
||||||
|
const [name] = randomName(docParent, browserName, 1);
|
||||||
|
await updateDocTitle(page, name);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const navigateToTopParentFromTree = async ({ page }: { page: Page }) => {
|
||||||
|
await page.getByRole('link', { name: /Open root document/ }).click();
|
||||||
|
};
|
||||||
|
|
||||||
export const navigateToPageFromTree = async ({
|
export const navigateToPageFromTree = async ({
|
||||||
page,
|
page,
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Box, BoxType } from '.';
|
|||||||
|
|
||||||
export const Card = ({
|
export const Card = ({
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
$css,
|
$css,
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<BoxType>) => {
|
}: PropsWithChildren<BoxType>) => {
|
||||||
@@ -14,7 +15,7 @@ export const Card = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
className={`--docs--card ${props.className || ''}`}
|
className={`--docs--card ${className || ''}`}
|
||||||
$background="white"
|
$background="white"
|
||||||
$radius="4px"
|
$radius="4px"
|
||||||
$css={css`
|
$css={css`
|
||||||
|
|||||||
36
src/frontend/apps/impress/src/components/Overlayer.tsx
Normal file
36
src/frontend/apps/impress/src/components/Overlayer.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
|
import { Box, BoxType } from '.';
|
||||||
|
|
||||||
|
type OverlayerProps = PropsWithChildren<{
|
||||||
|
isOverlay: boolean;
|
||||||
|
}> &
|
||||||
|
Partial<BoxType>;
|
||||||
|
|
||||||
|
export const Overlayer = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
$css,
|
||||||
|
isOverlay,
|
||||||
|
...props
|
||||||
|
}: OverlayerProps) => {
|
||||||
|
if (!isOverlay) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className={`--docs--overlayer ${className || ''}`}
|
||||||
|
$opacity="0.4"
|
||||||
|
$zIndex="10"
|
||||||
|
$css={css`
|
||||||
|
${$css}
|
||||||
|
pointer-events: none;
|
||||||
|
`}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ export * from './InfiniteScroll';
|
|||||||
export * from './Link';
|
export * from './Link';
|
||||||
export * from './Loading';
|
export * from './Loading';
|
||||||
export * from './modal';
|
export * from './modal';
|
||||||
|
export * from './Overlayer';
|
||||||
export * from './separators';
|
export * from './separators';
|
||||||
export * from './Text';
|
export * from './Text';
|
||||||
export * from './TextErrors';
|
export * from './TextErrors';
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
|||||||
const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
|
const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
|
||||||
const isConnectedToCollabServer = provider.isSynced;
|
const isConnectedToCollabServer = provider.isSynced;
|
||||||
const readOnly = !doc.abilities.partial_update || !isEditable || isLoading;
|
const readOnly = !doc.abilities.partial_update || !isEditable || isLoading;
|
||||||
|
const isDeletedDoc = !!doc.deleted_at;
|
||||||
|
|
||||||
useSaveDoc(doc.id, provider.document, !readOnly, isConnectedToCollabServer);
|
useSaveDoc(doc.id, provider.document, !readOnly, isConnectedToCollabServer);
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
@@ -180,7 +181,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
|||||||
<Box
|
<Box
|
||||||
$padding={{ top: 'md' }}
|
$padding={{ top: 'md' }}
|
||||||
$background="white"
|
$background="white"
|
||||||
$css={cssEditor(readOnly)}
|
$css={cssEditor(readOnly, isDeletedDoc)}
|
||||||
className="--docs--editor-container"
|
className="--docs--editor-container"
|
||||||
>
|
>
|
||||||
{errorAttachment && (
|
{errorAttachment && (
|
||||||
@@ -231,7 +232,7 @@ export const BlockNoteEditorVersion = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box $css={cssEditor(readOnly)} className="--docs--editor-container">
|
<Box $css={cssEditor(readOnly, true)} className="--docs--editor-container">
|
||||||
<BlockNoteView editor={editor} editable={!readOnly} theme="light" />
|
<BlockNoteView editor={editor} editable={!readOnly} theme="light" />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { css } from 'styled-components';
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
export const cssEditor = (readonly: boolean) => css`
|
export const cssEditor = (readonly: boolean, isDeletedDoc: boolean) => css`
|
||||||
&,
|
&,
|
||||||
& > .bn-container,
|
& > .bn-container,
|
||||||
& .ProseMirror {
|
& .ProseMirror {
|
||||||
@@ -127,6 +127,13 @@ export const cssEditor = (readonly: boolean) => css`
|
|||||||
.bn-block-outer:not([data-prev-depth-changed]):before {
|
.bn-block-outer:not([data-prev-depth-changed]):before {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${isDeletedDoc &&
|
||||||
|
`
|
||||||
|
.node-interlinkingLinkInline button {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
`}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .bn-editor {
|
& .bn-editor {
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
|
||||||
|
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
|
import { Box, BoxButton, Icon, Text } from '@/components';
|
||||||
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
import {
|
||||||
|
Doc,
|
||||||
|
KEY_DOC,
|
||||||
|
KEY_LIST_DOC,
|
||||||
|
useRestoreDoc,
|
||||||
|
} from '@/docs/doc-management';
|
||||||
|
import { KEY_LIST_DOC_TRASHBIN } from '@/docs/docs-grid';
|
||||||
|
|
||||||
|
export const AlertRestore = ({ doc }: { doc: Doc }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToastProvider();
|
||||||
|
const treeContext = useTreeContext<Doc>();
|
||||||
|
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
|
||||||
|
const { mutate: restoreDoc, error } = useRestoreDoc({
|
||||||
|
listInvalidQueries: [KEY_LIST_DOC, KEY_LIST_DOC_TRASHBIN, KEY_DOC],
|
||||||
|
options: {
|
||||||
|
onSuccess: (_data) => {
|
||||||
|
// It will force the tree to be reloaded
|
||||||
|
treeContext?.setRoot(undefined as unknown as Doc);
|
||||||
|
|
||||||
|
toast(t('The document has been restored.'), VariantType.SUCCESS, {
|
||||||
|
duration: 4000,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast(
|
||||||
|
t('An error occurred while restoring the document: {{error}}', {
|
||||||
|
error: error?.message,
|
||||||
|
}),
|
||||||
|
VariantType.ERROR,
|
||||||
|
{
|
||||||
|
duration: 4000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className="--docs--alert-restore"
|
||||||
|
aria-label={t('Alert deleted document')}
|
||||||
|
$color={colorsTokens['danger-800']}
|
||||||
|
$background={colorsTokens['danger-100']}
|
||||||
|
$radius={spacingsTokens['3xs']}
|
||||||
|
$direction="row"
|
||||||
|
$padding="xs"
|
||||||
|
$flex={1}
|
||||||
|
$align="center"
|
||||||
|
$gap={spacingsTokens['3xs']}
|
||||||
|
$css={css`
|
||||||
|
border: 1px solid var(--c--theme--colors--danger-300, #e3e3fd);
|
||||||
|
`}
|
||||||
|
$justify="space-between"
|
||||||
|
>
|
||||||
|
<Box $direction="row" $align="center" $gap={spacingsTokens['2xs']}>
|
||||||
|
<Icon
|
||||||
|
$theme="danger"
|
||||||
|
$variation="700"
|
||||||
|
data-testid="public-icon"
|
||||||
|
iconName="delete"
|
||||||
|
variant="symbols-outlined"
|
||||||
|
/>
|
||||||
|
<Text $theme="danger" $variation="700" $weight="500">
|
||||||
|
{t('Document deleted')}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<BoxButton
|
||||||
|
onClick={() =>
|
||||||
|
restoreDoc({
|
||||||
|
docId: doc.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$direction="row"
|
||||||
|
$gap="0.2rem"
|
||||||
|
$align="center"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
iconName="undo"
|
||||||
|
$theme="danger"
|
||||||
|
$variation="600"
|
||||||
|
$size="18px"
|
||||||
|
variant="symbols-outlined"
|
||||||
|
/>
|
||||||
|
<Text $theme="danger" $variation="600" $size="s">
|
||||||
|
{t('Restore')}
|
||||||
|
</Text>
|
||||||
|
</BoxButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
|
||||||
|
import { Button } from '@openfun/cunningham-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
|
import { Box, Icon } from '@/components';
|
||||||
|
import { Doc } from '@/docs/doc-management';
|
||||||
|
|
||||||
|
interface BoutonShareProps {
|
||||||
|
displayNbAccess: boolean;
|
||||||
|
doc: Doc;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isHidden?: boolean;
|
||||||
|
open: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BoutonShare = ({
|
||||||
|
displayNbAccess,
|
||||||
|
doc,
|
||||||
|
isDisabled,
|
||||||
|
isHidden,
|
||||||
|
open,
|
||||||
|
}: BoutonShareProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const treeContext = useTreeContext<Doc>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Following the change where there is no default owner when adding a sub-page,
|
||||||
|
* we need to handle both the case where the doc is the root and the case of sub-pages.
|
||||||
|
*/
|
||||||
|
const hasAccesses = useMemo(() => {
|
||||||
|
if (treeContext?.root?.id === doc.id) {
|
||||||
|
return doc.nb_accesses_direct > 1 && displayNbAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc.nb_accesses_direct >= 1 && displayNbAccess;
|
||||||
|
}, [doc.id, treeContext?.root, doc.nb_accesses_direct, displayNbAccess]);
|
||||||
|
|
||||||
|
if (isHidden) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAccesses) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
$css={css`
|
||||||
|
.c__button--medium {
|
||||||
|
height: 32px;
|
||||||
|
padding: 10px var(--c--theme--spacings--xs);
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="tertiary"
|
||||||
|
aria-label={t('Share button')}
|
||||||
|
icon={
|
||||||
|
<Icon
|
||||||
|
iconName="group"
|
||||||
|
$theme="primary"
|
||||||
|
$variation="800"
|
||||||
|
variant="filled"
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={open}
|
||||||
|
size="medium"
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
{doc.nb_accesses_direct}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
color="tertiary-text"
|
||||||
|
onClick={open}
|
||||||
|
size="medium"
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
{t('Share')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DateTime } from 'luxon';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Box, HorizontalSeparator, Text } from '@/components';
|
import { Box, HorizontalSeparator, Text } from '@/components';
|
||||||
|
import { useConfig } from '@/core';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import {
|
import {
|
||||||
Doc,
|
Doc,
|
||||||
@@ -11,10 +11,13 @@ import {
|
|||||||
useIsCollaborativeEditable,
|
useIsCollaborativeEditable,
|
||||||
useTrans,
|
useTrans,
|
||||||
} from '@/docs/doc-management';
|
} from '@/docs/doc-management';
|
||||||
|
import { useDate } from '@/hook';
|
||||||
import { useResponsiveStore } from '@/stores';
|
import { useResponsiveStore } from '@/stores';
|
||||||
|
|
||||||
import { AlertNetwork } from './AlertNetwork';
|
import { AlertNetwork } from './AlertNetwork';
|
||||||
import { AlertPublic } from './AlertPublic';
|
import { AlertPublic } from './AlertPublic';
|
||||||
|
import { AlertRestore } from './AlertRestore';
|
||||||
|
import { BoutonShare } from './BoutonShare';
|
||||||
import { DocTitle } from './DocTitle';
|
import { DocTitle } from './DocTitle';
|
||||||
import { DocToolBox } from './DocToolBox';
|
import { DocToolBox } from './DocToolBox';
|
||||||
|
|
||||||
@@ -30,6 +33,22 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
|||||||
const { isEditable } = useIsCollaborativeEditable(doc);
|
const { isEditable } = useIsCollaborativeEditable(doc);
|
||||||
const docIsPublic = getDocLinkReach(doc) === LinkReach.PUBLIC;
|
const docIsPublic = getDocLinkReach(doc) === LinkReach.PUBLIC;
|
||||||
const docIsAuth = getDocLinkReach(doc) === LinkReach.AUTHENTICATED;
|
const docIsAuth = getDocLinkReach(doc) === LinkReach.AUTHENTICATED;
|
||||||
|
const { relativeDate, calculateDaysLeft } = useDate();
|
||||||
|
const { data: config } = useConfig();
|
||||||
|
const isDeletedDoc = !!doc.deleted_at;
|
||||||
|
|
||||||
|
let dateToDisplay = t('Last update: {{update}}', {
|
||||||
|
update: relativeDate(doc.updated_at),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (config?.TRASHBIN_CUTOFF_DAYS && doc.deleted_at) {
|
||||||
|
const daysLeft = calculateDaysLeft(
|
||||||
|
doc.deleted_at,
|
||||||
|
config.TRASHBIN_CUTOFF_DAYS,
|
||||||
|
);
|
||||||
|
|
||||||
|
dateToDisplay = `${t('Days remaining:')} ${daysLeft} ${t('days', { count: daysLeft })}`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -40,6 +59,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
|||||||
aria-label={t('It is the card information about the document.')}
|
aria-label={t('It is the card information about the document.')}
|
||||||
className="--docs--doc-header"
|
className="--docs--doc-header"
|
||||||
>
|
>
|
||||||
|
{isDeletedDoc && <AlertRestore doc={doc} />}
|
||||||
{!isEditable && <AlertNetwork />}
|
{!isEditable && <AlertNetwork />}
|
||||||
{(docIsPublic || docIsAuth) && (
|
{(docIsPublic || docIsAuth) && (
|
||||||
<AlertPublic isPublicDoc={docIsPublic} />
|
<AlertPublic isPublicDoc={docIsPublic} />
|
||||||
@@ -78,20 +98,26 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
|||||||
·
|
·
|
||||||
</Text>
|
</Text>
|
||||||
<Text $variation="600" $size="s">
|
<Text $variation="600" $size="s">
|
||||||
{t('Last update: {{update}}', {
|
{dateToDisplay}
|
||||||
update: DateTime.fromISO(doc.updated_at).toRelative(),
|
|
||||||
})}
|
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!isDesktop && (
|
{!isDesktop && (
|
||||||
<Text $variation="400" $size="s">
|
<Text $variation="400" $size="s">
|
||||||
{DateTime.fromISO(doc.updated_at).toRelative()}
|
{dateToDisplay}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<DocToolBox doc={doc} />
|
{!isDeletedDoc && <DocToolBox doc={doc} />}
|
||||||
|
{isDeletedDoc && (
|
||||||
|
<BoutonShare
|
||||||
|
doc={doc}
|
||||||
|
open={() => {}}
|
||||||
|
displayNbAccess={true}
|
||||||
|
isDisabled
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<HorizontalSeparator $withPadding={false} />
|
<HorizontalSeparator $withPadding={false} />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
|
|||||||
import { Button, useModal } from '@openfun/cunningham-react';
|
import { Button, useModal } from '@openfun/cunningham-react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { css } from 'styled-components';
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
useCopyDocLink,
|
useCopyDocLink,
|
||||||
useCreateFavoriteDoc,
|
useCreateFavoriteDoc,
|
||||||
useDeleteFavoriteDoc,
|
useDeleteFavoriteDoc,
|
||||||
|
useDocUtils,
|
||||||
useDuplicateDoc,
|
useDuplicateDoc,
|
||||||
} from '@/docs/doc-management';
|
} from '@/docs/doc-management';
|
||||||
import { DocShareModal } from '@/docs/doc-share';
|
import { DocShareModal } from '@/docs/doc-share';
|
||||||
@@ -35,6 +36,8 @@ import { useResponsiveStore } from '@/stores';
|
|||||||
|
|
||||||
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
|
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
|
||||||
|
|
||||||
|
import { BoutonShare } from './BoutonShare';
|
||||||
|
|
||||||
const ModalExport = Export?.ModalExport;
|
const ModalExport = Export?.ModalExport;
|
||||||
|
|
||||||
interface DocToolBoxProps {
|
interface DocToolBoxProps {
|
||||||
@@ -44,21 +47,9 @@ interface DocToolBoxProps {
|
|||||||
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const treeContext = useTreeContext<Doc>();
|
const treeContext = useTreeContext<Doc>();
|
||||||
|
|
||||||
/**
|
|
||||||
* Following the change where there is no default owner when adding a sub-page,
|
|
||||||
* we need to handle both the case where the doc is the root and the case of sub-pages.
|
|
||||||
*/
|
|
||||||
const hasAccesses = useMemo(() => {
|
|
||||||
if (treeContext?.root?.id === doc.id) {
|
|
||||||
return doc.nb_accesses_direct > 1 && doc.abilities.accesses_view;
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc.nb_accesses_direct >= 1 && doc.abilities.accesses_view;
|
|
||||||
}, [doc, treeContext?.root]);
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { isChild } = useDocUtils(doc);
|
||||||
|
|
||||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
@@ -164,7 +155,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('Delete document'),
|
label: isChild ? t('Delete sub-document') : t('Delete document'),
|
||||||
icon: 'delete',
|
icon: 'delete',
|
||||||
disabled: !doc.abilities.destroy,
|
disabled: !doc.abilities.destroy,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
@@ -190,46 +181,12 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
$margin={{ left: 'auto' }}
|
$margin={{ left: 'auto' }}
|
||||||
$gap={spacingsTokens['2xs']}
|
$gap={spacingsTokens['2xs']}
|
||||||
>
|
>
|
||||||
{!isSmallMobile && (
|
<BoutonShare
|
||||||
<>
|
doc={doc}
|
||||||
{!hasAccesses && (
|
open={modalShare.open}
|
||||||
<Button
|
isHidden={isSmallMobile}
|
||||||
color="tertiary-text"
|
displayNbAccess={doc.abilities.accesses_view}
|
||||||
onClick={() => {
|
/>
|
||||||
modalShare.open();
|
|
||||||
}}
|
|
||||||
size={isSmallMobile ? 'small' : 'medium'}
|
|
||||||
>
|
|
||||||
{t('Share')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{hasAccesses && (
|
|
||||||
<Box
|
|
||||||
$css={css`
|
|
||||||
.c__button--medium {
|
|
||||||
height: 32px;
|
|
||||||
padding: 10px var(--c--theme--spacings--xs);
|
|
||||||
gap: 7px;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
color="tertiary"
|
|
||||||
aria-label={t('Share button')}
|
|
||||||
icon={
|
|
||||||
<Icon iconName="group" $theme="primary" $variation="800" />
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
modalShare.open();
|
|
||||||
}}
|
|
||||||
size={isSmallMobile ? 'small' : 'medium'}
|
|
||||||
>
|
|
||||||
{doc.nb_accesses_direct}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isSmallMobile && ModalExport && (
|
{!isSmallMobile && ModalExport && (
|
||||||
<Button
|
<Button
|
||||||
@@ -283,7 +240,26 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
|
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
|
||||||
)}
|
)}
|
||||||
{isModalRemoveOpen && (
|
{isModalRemoveOpen && (
|
||||||
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
|
<ModalRemoveDoc
|
||||||
|
onClose={() => setIsModalRemoveOpen(false)}
|
||||||
|
doc={doc}
|
||||||
|
onSuccess={() => {
|
||||||
|
const isTopParent = doc.id === treeContext?.root?.id;
|
||||||
|
const parentId =
|
||||||
|
treeContext?.treeData.getParentId(doc.id) ||
|
||||||
|
treeContext?.root?.id;
|
||||||
|
|
||||||
|
if (isTopParent) {
|
||||||
|
void router.push(`/`);
|
||||||
|
} else if (parentId) {
|
||||||
|
void router.push(`/docs/${parentId}`).then(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
treeContext?.treeData.deleteNode(doc.id);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{selectHistoryModal.isOpen && (
|
{selectHistoryModal.isOpen && (
|
||||||
<ModalSelectVersion
|
<ModalSelectVersion
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
VariantType,
|
VariantType,
|
||||||
useToastProvider,
|
useToastProvider,
|
||||||
} from '@openfun/cunningham-react';
|
} from '@openfun/cunningham-react';
|
||||||
import { usePathname } from 'next/navigation';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ export const ModalRemoveDoc = ({
|
|||||||
const trashBinCutoffDays = config?.TRASHBIN_CUTOFF_DAYS || 30;
|
const trashBinCutoffDays = config?.TRASHBIN_CUTOFF_DAYS || 30;
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const { hasChildren } = useDocUtils(doc);
|
const { hasChildren } = useDocUtils(doc);
|
||||||
const pathname = usePathname();
|
|
||||||
const {
|
const {
|
||||||
mutate: removeDoc,
|
mutate: removeDoc,
|
||||||
isError,
|
isError,
|
||||||
@@ -46,12 +44,12 @@ export const ModalRemoveDoc = ({
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
if (onSuccess) {
|
if (onSuccess) {
|
||||||
onSuccess(doc);
|
onSuccess(doc);
|
||||||
} else if (pathname === '/') {
|
|
||||||
onClose();
|
|
||||||
} else {
|
} else {
|
||||||
void push('/');
|
void push('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
|
||||||
toast(t('The document has been deleted.'), VariantType.SUCCESS, {
|
toast(t('The document has been deleted.'), VariantType.SUCCESS, {
|
||||||
duration: 4000,
|
duration: 4000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
<path d="M5.40918 4.69434C5.28613 4.69434 5.18359 4.65332 5.10156 4.57129C5.02409 4.48926 4.98535 4.389 4.98535 4.27051C4.98535 4.15202 5.02409 4.05404 5.10156 3.97656C5.18359 3.89453 5.28613 3.85352 5.40918 3.85352H10.5977C10.7161 3.85352 10.8141 3.89453 10.8916 3.97656C10.9736 4.05404 11.0146 4.15202 11.0146 4.27051C11.0146 4.389 10.9736 4.48926 10.8916 4.57129C10.8141 4.65332 10.7161 4.69434 10.5977 4.69434H5.40918ZM5.40918 7.08008C5.28613 7.08008 5.18359 7.03906 5.10156 6.95703C5.02409 6.875 4.98535 6.77474 4.98535 6.65625C4.98535 6.53776 5.02409 6.43978 5.10156 6.3623C5.18359 6.28027 5.28613 6.23926 5.40918 6.23926H10.5977C10.7161 6.23926 10.8141 6.28027 10.8916 6.3623C10.9736 6.43978 11.0146 6.53776 11.0146 6.65625C11.0146 6.77474 10.9736 6.875 10.8916 6.95703C10.8141 7.03906 10.7161 7.08008 10.5977 7.08008H5.40918ZM5.40918 9.46582C5.28613 9.46582 5.18359 9.42708 5.10156 9.34961C5.02409 9.26758 4.98535 9.1696 4.98535 9.05566C4.98535 8.93262 5.02409 8.83008 5.10156 8.74805C5.18359 8.66602 5.28613 8.625 5.40918 8.625H7.86328C7.98633 8.625 8.08659 8.66602 8.16406 8.74805C8.24609 8.83008 8.28711 8.93262 8.28711 9.05566C8.28711 9.1696 8.24609 9.26758 8.16406 9.34961C8.08659 9.42708 7.98633 9.46582 7.86328 9.46582H5.40918ZM2.25098 13.2529V2.88281C2.25098 2.17188 2.42643 1.63639 2.77734 1.27637C3.13281 0.916341 3.66374 0.736328 4.37012 0.736328H11.6299C12.3363 0.736328 12.8649 0.916341 13.2158 1.27637C13.5713 1.63639 13.749 2.17188 13.749 2.88281V13.2529C13.749 13.9684 13.5713 14.5039 13.2158 14.8594C12.8649 15.2148 12.3363 15.3926 11.6299 15.3926H4.37012C3.66374 15.3926 3.13281 15.2148 2.77734 14.8594C2.42643 14.5039 2.25098 13.9684 2.25098 13.2529ZM3.35156 13.2324C3.35156 13.5742 3.44043 13.8363 3.61816 14.0186C3.80046 14.2008 4.06934 14.292 4.4248 14.292H11.5752C11.9307 14.292 12.1973 14.2008 12.375 14.0186C12.5573 13.8363 12.6484 13.5742 12.6484 13.2324V2.90332C12.6484 2.56152 12.5573 2.29948 12.375 2.11719C12.1973 1.93034 11.9307 1.83691 11.5752 1.83691H4.4248C4.06934 1.83691 3.80046 1.93034 3.61816 2.11719C3.44043 2.29948 3.35156 2.56152 3.35156 2.90332V13.2324Z" fill="#8585F6"/>
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5.40918 4.69434C5.28613 4.69434 5.18359 4.65332 5.10156 4.57129C5.02409 4.48926 4.98535 4.389 4.98535 4.27051C4.98535 4.15202 5.02409 4.05404 5.10156 3.97656C5.18359 3.89453 5.28613 3.85352 5.40918 3.85352H10.5977C10.7161 3.85352 10.8141 3.89453 10.8916 3.97656C10.9736 4.05404 11.0146 4.15202 11.0146 4.27051C11.0146 4.389 10.9736 4.48926 10.8916 4.57129C10.8141 4.65332 10.7161 4.69434 10.5977 4.69434H5.40918ZM5.40918 7.08008C5.28613 7.08008 5.18359 7.03906 5.10156 6.95703C5.02409 6.875 4.98535 6.77474 4.98535 6.65625C4.98535 6.53776 5.02409 6.43978 5.10156 6.3623C5.18359 6.28027 5.28613 6.23926 5.40918 6.23926H10.5977C10.7161 6.23926 10.8141 6.28027 10.8916 6.3623C10.9736 6.43978 11.0146 6.53776 11.0146 6.65625C11.0146 6.77474 10.9736 6.875 10.8916 6.95703C10.8141 7.03906 10.7161 7.08008 10.5977 7.08008H5.40918ZM5.40918 9.46582C5.28613 9.46582 5.18359 9.42708 5.10156 9.34961C5.02409 9.26758 4.98535 9.1696 4.98535 9.05566C4.98535 8.93262 5.02409 8.83008 5.10156 8.74805C5.18359 8.66602 5.28613 8.625 5.40918 8.625H7.86328C7.98633 8.625 8.08659 8.66602 8.16406 8.74805C8.24609 8.83008 8.28711 8.93262 8.28711 9.05566C8.28711 9.1696 8.24609 9.26758 8.16406 9.34961C8.08659 9.42708 7.98633 9.46582 7.86328 9.46582H5.40918ZM2.25098 13.2529V2.88281C2.25098 2.17188 2.42643 1.63639 2.77734 1.27637C3.13281 0.916341 3.66374 0.736328 4.37012 0.736328H11.6299C12.3363 0.736328 12.8649 0.916341 13.2158 1.27637C13.5713 1.63639 13.749 2.17188 13.749 2.88281V13.2529C13.749 13.9684 13.5713 14.5039 13.2158 14.8594C12.8649 15.2148 12.3363 15.3926 11.6299 15.3926H4.37012C3.66374 15.3926 3.13281 15.2148 2.77734 14.8594C2.42643 14.5039 2.25098 13.9684 2.25098 13.2529ZM3.35156 13.2324C3.35156 13.5742 3.44043 13.8363 3.61816 14.0186C3.80046 14.2008 4.06934 14.292 4.4248 14.292H11.5752C11.9307 14.292 12.1973 14.2008 12.375 14.0186C12.5573 13.8363 12.6484 13.5742 12.6484 13.2324V2.90332C12.6484 2.56152 12.5573 2.29948 12.375 2.11719C12.1973 1.93034 11.9307 1.83691 11.5752 1.83691H4.4248C4.06934 1.83691 3.80046 1.93034 3.61816 2.11719C3.44043 2.29948 3.35156 2.56152 3.35156 2.90332V13.2324Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -39,7 +39,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
|||||||
const treeContext = useTreeContext<Doc>();
|
const treeContext = useTreeContext<Doc>();
|
||||||
const { untitledDocument } = useTrans();
|
const { untitledDocument } = useTrans();
|
||||||
const { node } = props;
|
const { node } = props;
|
||||||
const { spacingsTokens } = useCunninghamTheme();
|
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||||
const { isDesktop } = useResponsiveStore();
|
const { isDesktop } = useResponsiveStore();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -101,6 +101,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
|||||||
const isExpanded = node.isOpen;
|
const isExpanded = node.isOpen;
|
||||||
const isSelected = isSelectedNow;
|
const isSelected = isSelectedNow;
|
||||||
const ariaLabel = docTitle;
|
const ariaLabel = docTitle;
|
||||||
|
const isDisabled = !!doc.deleted_at;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -111,6 +112,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
|||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
aria-selected={isSelected}
|
aria-selected={isSelected}
|
||||||
aria-expanded={hasChildren ? isExpanded : undefined}
|
aria-expanded={hasChildren ? isExpanded : undefined}
|
||||||
|
aria-disabled={isDisabled}
|
||||||
$css={css`
|
$css={css`
|
||||||
background-color: ${menuOpen
|
background-color: ${menuOpen
|
||||||
? 'var(--c--theme--colors--greyscale-100)'
|
? 'var(--c--theme--colors--greyscale-100)'
|
||||||
@@ -164,7 +166,11 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
|||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<Box $width="16px" $height="16px">
|
<Box $width="16px" $height="16px">
|
||||||
<DocIcon emoji={emoji} defaultIcon={<SubPageIcon />} $size="sm" />
|
<DocIcon
|
||||||
|
emoji={emoji}
|
||||||
|
defaultIcon={<SubPageIcon color={colorsTokens['primary-400']} />}
|
||||||
|
$size="sm"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { css } from 'styled-components';
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
import { Box, StyledLink } from '@/components';
|
import { Box, Overlayer, StyledLink } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Doc, SimpleDocItem } from '@/docs/doc-management';
|
import { Doc, SimpleDocItem } from '@/docs/doc-management';
|
||||||
|
|
||||||
@@ -289,29 +289,31 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
|||||||
{initialOpenState &&
|
{initialOpenState &&
|
||||||
treeContext.treeData.nodes.length > 0 &&
|
treeContext.treeData.nodes.length > 0 &&
|
||||||
treeRoot && (
|
treeRoot && (
|
||||||
<TreeView
|
<Overlayer isOverlay={currentDoc.deleted_at != null} inert>
|
||||||
dndRootElement={treeRoot}
|
<TreeView
|
||||||
initialOpenState={initialOpenState}
|
dndRootElement={treeRoot}
|
||||||
afterMove={handleMove}
|
initialOpenState={initialOpenState}
|
||||||
selectedNodeId={
|
afterMove={handleMove}
|
||||||
treeContext.treeData.selectedNode?.id ??
|
selectedNodeId={
|
||||||
treeContext.initialTargetId ??
|
treeContext.treeData.selectedNode?.id ??
|
||||||
undefined
|
treeContext.initialTargetId ??
|
||||||
}
|
undefined
|
||||||
canDrop={({ parentNode }) => {
|
|
||||||
const parentDoc = parentNode?.data.value as Doc;
|
|
||||||
if (!parentDoc) {
|
|
||||||
return currentDoc.abilities.move && isDesktop;
|
|
||||||
}
|
}
|
||||||
return parentDoc.abilities.move && isDesktop;
|
canDrop={({ parentNode }) => {
|
||||||
}}
|
const parentDoc = parentNode?.data.value as Doc;
|
||||||
canDrag={(node) => {
|
if (!parentDoc) {
|
||||||
const doc = node.value as Doc;
|
return currentDoc.abilities.move && isDesktop;
|
||||||
return doc.abilities.move && isDesktop;
|
}
|
||||||
}}
|
return parentDoc.abilities.move && isDesktop;
|
||||||
rootNodeId={treeContext.root.id}
|
}}
|
||||||
renderNode={DocSubPageItem}
|
canDrag={(node) => {
|
||||||
/>
|
const doc = node.value as Doc;
|
||||||
|
return doc.abilities.move && isDesktop;
|
||||||
|
}}
|
||||||
|
rootNodeId={treeContext.root.id}
|
||||||
|
renderNode={DocSubPageItem}
|
||||||
|
/>
|
||||||
|
</Overlayer>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -126,16 +126,13 @@ export const DocTreeItemActions = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSuccessDelete = () => {
|
const onSuccessDelete = () => {
|
||||||
if (parentId) {
|
const isTopParent = doc.id === treeContext?.root?.id && !parentId;
|
||||||
void router.push(`/docs/${parentId}`).then(() => {
|
const parentIdComputed = parentId || treeContext?.root?.id;
|
||||||
setTimeout(() => {
|
|
||||||
treeContext?.treeData.deleteNode(doc.id);
|
if (isTopParent) {
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
} else if (doc.id === treeContext?.root?.id && !parentId) {
|
|
||||||
void router.push(`/`);
|
void router.push(`/`);
|
||||||
} else if (treeContext && treeContext.root) {
|
} else if (parentIdComputed) {
|
||||||
void router.push(`/docs/${treeContext.root.id}`).then(() => {
|
void router.push(`/docs/${parentIdComputed}`).then(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
treeContext?.treeData.deleteNode(doc.id);
|
treeContext?.treeData.deleteNode(doc.id);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
useDoc,
|
useDoc,
|
||||||
useDocStore,
|
useDocStore,
|
||||||
useProviderStore,
|
useProviderStore,
|
||||||
|
useTrans,
|
||||||
} from '@/docs/doc-management/';
|
} from '@/docs/doc-management/';
|
||||||
import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth';
|
import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth';
|
||||||
import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/';
|
import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/';
|
||||||
@@ -89,6 +90,7 @@ const DocPage = ({ id }: DocProps) => {
|
|||||||
useCollaboration(doc?.id, doc?.content);
|
useCollaboration(doc?.id, doc?.content);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { authenticated } = useAuth();
|
const { authenticated } = useAuth();
|
||||||
|
const { untitledDocument } = useTrans();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll to top when navigating to a new document
|
* Scroll to top when navigating to a new document
|
||||||
@@ -194,11 +196,11 @@ const DocPage = ({ id }: DocProps) => {
|
|||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
{doc.title} - {t('Docs')}
|
{doc.title || untitledDocument} - {t('Docs')}
|
||||||
</title>
|
</title>
|
||||||
<meta
|
<meta
|
||||||
property="og:title"
|
property="og:title"
|
||||||
content={`${doc.title} - ${t('Docs')}`}
|
content={`${doc.title || untitledDocument} - ${t('Docs')}`}
|
||||||
key="title"
|
key="title"
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
|
|||||||
Reference in New Issue
Block a user