diff --git a/src/frontend/apps/e2e/__tests__/app-impress/common.ts b/src/frontend/apps/e2e/__tests__/app-impress/common.ts index fede1a9b..36ee1448 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/common.ts @@ -239,6 +239,7 @@ export const mockedDocument = async (page: Page, json: object) => { }, link_reach: 'restricted', created_at: '2021-09-01T09:00:00Z', + user_roles: ['owner'], ...json, }, }); @@ -248,6 +249,22 @@ export const mockedDocument = async (page: Page, json: object) => { }); }; +export const mockedListDocs = async (page: Page, data: object[] = []) => { + await page.route('**/documents/**/', async (route) => { + const request = route.request(); + if (request.method().includes('GET') && request.url().includes('page=')) { + await route.fulfill({ + json: { + count: data.length, + next: null, + previous: null, + results: data, + }, + }); + } + }); +}; + export const mockedInvitations = async (page: Page, json?: object) => { await page.route('**/invitations/**/', async (route) => { const request = route.request(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-dnd.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-dnd.spec.ts new file mode 100644 index 00000000..e3e41bf9 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-dnd.spec.ts @@ -0,0 +1,311 @@ +import { expect, test } from '@playwright/test'; + +import { createDoc, mockedListDocs } from './common'; + +test.describe('Doc grid dnd', () => { + test('it creates a doc', async ({ page, browserName }) => { + await page.goto('/'); + const header = page.locator('header').first(); + await createDoc(page, 'Draggable doc', browserName, 1); + await header.locator('h2').getByText('Docs').click(); + await createDoc(page, 'Droppable doc', browserName, 1); + await header.locator('h2').getByText('Docs').click(); + + const response = await page.waitForResponse( + (response) => + response.url().endsWith('documents/?page=1') && + response.status() === 200, + ); + const responseJson = await response.json(); + + const items = responseJson.results; + + const docsGrid = page.getByTestId('docs-grid'); + await expect(docsGrid).toBeVisible(); + await expect(page.getByTestId('grid-loader')).toBeHidden(); + const draggableElement = page.getByTestId(`draggable-doc-${items[1].id}`); + const dropZone = page.getByTestId(`droppable-doc-${items[0].id}`); + await expect(draggableElement).toBeVisible(); + await expect(dropZone).toBeVisible(); + + // Obtenir les positions des éléments + const draggableBoundingBox = await draggableElement.boundingBox(); + const dropZoneBoundingBox = await dropZone.boundingBox(); + + expect(draggableBoundingBox).toBeDefined(); + expect(dropZoneBoundingBox).toBeDefined(); + + // eslint-disable-next-line playwright/no-conditional-in-test + if (!draggableBoundingBox || !dropZoneBoundingBox) { + throw new Error('Impossible de déterminer la position des éléments'); + } + + await page.mouse.move( + draggableBoundingBox.x + draggableBoundingBox.width / 2, + draggableBoundingBox.y + draggableBoundingBox.height / 2, + ); + await page.mouse.down(); + + // Déplacer vers la zone cible + await page.mouse.move( + dropZoneBoundingBox.x + dropZoneBoundingBox.width / 2, + dropZoneBoundingBox.y + dropZoneBoundingBox.height / 2, + { steps: 10 }, // Rendre le mouvement plus fluide + ); + + const dragOverlay = page.getByTestId('drag-doc-overlay'); + + await expect(dragOverlay).toBeVisible(); + await expect(dragOverlay).toHaveText(items[1].title as string); + await page.mouse.up(); + + await expect(dragOverlay).toBeHidden(); + }); + + test('it checks cant drop when we have not the minimum role', async ({ + page, + }) => { + await mockedListDocs(page, data); + await page.goto('/'); + const docsGrid = page.getByTestId('docs-grid'); + await expect(docsGrid).toBeVisible(); + await expect(page.getByTestId('grid-loader')).toBeHidden(); + + const canDropAndDrag = page.getByTestId('droppable-doc-can-drop-and-drag'); + + const noDropAndNoDrag = page.getByTestId( + 'droppable-doc-no-drop-and-no-drag', + ); + + await expect(canDropAndDrag).toBeVisible(); + + await expect(noDropAndNoDrag).toBeVisible(); + + const canDropAndDragBoundigBox = await canDropAndDrag.boundingBox(); + + const noDropAndNoDragBoundigBox = await noDropAndNoDrag.boundingBox(); + + // eslint-disable-next-line playwright/no-conditional-in-test + if (!canDropAndDragBoundigBox || !noDropAndNoDragBoundigBox) { + throw new Error('Impossible de déterminer la position des éléments'); + } + + await page.mouse.move( + canDropAndDragBoundigBox.x + canDropAndDragBoundigBox.width / 2, + canDropAndDragBoundigBox.y + canDropAndDragBoundigBox.height / 2, + ); + + await page.mouse.down(); + + await page.mouse.move( + noDropAndNoDragBoundigBox.x + noDropAndNoDragBoundigBox.width / 2, + noDropAndNoDragBoundigBox.y + noDropAndNoDragBoundigBox.height / 2, + { steps: 10 }, + ); + + const dragOverlay = page.getByTestId('drag-doc-overlay'); + + await expect(dragOverlay).toBeVisible(); + await expect(dragOverlay).toHaveText( + 'You must be at least the editor of the target document', + ); + + await page.mouse.up(); + }); + + test('it checks cant drag when we have not the minimum role', async ({ + page, + }) => { + await mockedListDocs(page, data); + await page.goto('/'); + const docsGrid = page.getByTestId('docs-grid'); + await expect(docsGrid).toBeVisible(); + await expect(page.getByTestId('grid-loader')).toBeHidden(); + + const canDropAndDrag = page.getByTestId('droppable-doc-can-drop-and-drag'); + + const noDropAndNoDrag = page.getByTestId( + 'droppable-doc-no-drop-and-no-drag', + ); + + await expect(canDropAndDrag).toBeVisible(); + + await expect(noDropAndNoDrag).toBeVisible(); + + const canDropAndDragBoundigBox = await canDropAndDrag.boundingBox(); + + const noDropAndNoDragBoundigBox = await noDropAndNoDrag.boundingBox(); + + // eslint-disable-next-line playwright/no-conditional-in-test + if (!canDropAndDragBoundigBox || !noDropAndNoDragBoundigBox) { + throw new Error('Impossible de déterminer la position des éléments'); + } + + await page.mouse.move( + noDropAndNoDragBoundigBox.x + noDropAndNoDragBoundigBox.width / 2, + noDropAndNoDragBoundigBox.y + noDropAndNoDragBoundigBox.height / 2, + ); + + await page.mouse.down(); + + await page.mouse.move( + canDropAndDragBoundigBox.x + canDropAndDragBoundigBox.width / 2, + canDropAndDragBoundigBox.y + canDropAndDragBoundigBox.height / 2, + { steps: 10 }, + ); + + const dragOverlay = page.getByTestId('drag-doc-overlay'); + + await expect(dragOverlay).toBeVisible(); + await expect(dragOverlay).toHaveText( + 'You must have admin rights to move the document', + ); + + await page.mouse.up(); + }); +}); + +const data = [ + { + id: 'can-drop-and-drag', + abilities: { + accesses_manage: true, + accesses_view: true, + ai_transform: true, + ai_translate: true, + attachment_upload: true, + children_list: true, + children_create: true, + collaboration_auth: true, + descendants: true, + destroy: true, + favorite: true, + link_configuration: true, + invite_owner: true, + move: true, + partial_update: true, + restore: true, + retrieve: true, + media_auth: true, + link_select_options: { + restricted: ['reader', 'editor'], + authenticated: ['reader', 'editor'], + public: ['reader', 'editor'], + }, + tree: true, + update: true, + versions_destroy: true, + versions_list: true, + versions_retrieve: true, + }, + created_at: '2025-03-14T14:45:22.527221Z', + creator: 'bc6895e0-8f6d-4b00-827d-c143aa6b2ecb', + depth: 1, + excerpt: null, + is_favorite: false, + link_role: 'reader', + link_reach: 'restricted', + nb_accesses_ancestors: 1, + nb_accesses_direct: 1, + numchild: 5, + path: '000000o', + title: 'Can drop and drag', + updated_at: '2025-03-14T14:45:27.699542Z', + user_roles: ['owner'], + }, + { + id: 'can-only-drop', + title: 'Can only drop', + abilities: { + accesses_manage: true, + accesses_view: true, + ai_transform: true, + ai_translate: true, + attachment_upload: true, + children_list: true, + children_create: true, + collaboration_auth: true, + descendants: true, + destroy: true, + favorite: true, + link_configuration: true, + invite_owner: true, + move: true, + partial_update: true, + restore: true, + retrieve: true, + media_auth: true, + link_select_options: { + restricted: ['reader', 'editor'], + authenticated: ['reader', 'editor'], + public: ['reader', 'editor'], + }, + tree: true, + update: true, + versions_destroy: true, + versions_list: true, + versions_retrieve: true, + }, + created_at: '2025-03-14T14:45:22.527221Z', + creator: 'bc6895e0-8f6d-4b00-827d-c143aa6b2ecb', + depth: 1, + excerpt: null, + is_favorite: false, + link_role: 'reader', + link_reach: 'restricted', + nb_accesses_ancestors: 1, + nb_accesses_direct: 1, + numchild: 5, + path: '000000o', + + updated_at: '2025-03-14T14:45:27.699542Z', + user_roles: ['editor'], + }, + { + id: 'no-drop-and-no-drag', + abilities: { + accesses_manage: false, + accesses_view: true, + ai_transform: false, + ai_translate: false, + attachment_upload: false, + children_list: true, + children_create: false, + collaboration_auth: true, + descendants: true, + destroy: false, + favorite: true, + link_configuration: false, + invite_owner: false, + move: false, + partial_update: false, + restore: false, + retrieve: true, + media_auth: true, + link_select_options: { + restricted: ['reader', 'editor'], + authenticated: ['reader', 'editor'], + public: ['reader', 'editor'], + }, + tree: true, + update: false, + versions_destroy: false, + versions_list: true, + versions_retrieve: true, + }, + created_at: '2025-03-14T14:44:16.032773Z', + creator: '9264f420-f018-4bd6-96ae-4788f41af56d', + depth: 1, + excerpt: null, + is_favorite: false, + link_role: 'reader', + link_reach: 'restricted', + nb_accesses_ancestors: 14, + nb_accesses_direct: 14, + numchild: 0, + path: '000000l', + title: 'No drop and no drag', + updated_at: '2025-03-14T14:44:16.032774Z', + user_roles: ['reader'], + }, +]; diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts index 12be84ce..4c5eac3c 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts @@ -59,6 +59,7 @@ test.describe('Documents Grid mobile', () => { link_reach: 'public', created_at: '2024-10-07T13:02:41.085298Z', updated_at: '2024-10-07T13:30:21.829690Z', + user_roles: ['owner'], }, ], }, @@ -168,6 +169,7 @@ test.describe('Document grid item options', () => { }, link_reach: 'restricted', created_at: '2021-09-01T09:00:00Z', + user_roles: ['editor'], }, ], }, diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts index feaf18a6..b33923fa 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts @@ -70,7 +70,9 @@ test.describe('Doc Header', () => { ).toBeVisible(); await expect( - page.getByText(`Are you sure you want to delete this document ?`), + page.getByText( + `Are you sure you want to delete the document "${randomDoc}"?`, + ), ).toBeVisible(); await page diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx index 29680401..bd8c4849 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx @@ -42,10 +42,14 @@ export interface Doc { is_favorite: boolean; link_reach: LinkReach; link_role: LinkRole; - nb_accesses_ancestors: number; - nb_accesses_direct: number; + user_roles: Role[]; created_at: string; updated_at: string; + nb_accesses_direct: number; + nb_accesses_ancestors: number; + children?: Doc[]; + childrenCount?: number; + numchild: number; abilities: { accesses_manage: boolean; accesses_view: boolean; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/api/useMove.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/api/useMove.tsx new file mode 100644 index 00000000..1ba87df4 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/api/useMove.tsx @@ -0,0 +1,36 @@ +import { TreeViewMoveModeEnum } from '@gouvfr-lasuite/ui-kit'; +import { useMutation } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +export type MoveDocParam = { + sourceDocumentId: string; + targetDocumentId: string; + position: TreeViewMoveModeEnum; +}; + +export const moveDoc = async ({ + sourceDocumentId, + targetDocumentId, + position, +}: MoveDocParam): Promise => { + const response = await fetchAPI(`documents/${sourceDocumentId}/move/`, { + method: 'POST', + body: JSON.stringify({ + target_document_id: targetDocumentId, + position, + }), + }); + + if (!response.ok) { + throw new APIError('Failed to move the doc', await errorCauses(response)); + } + + return response.json() as Promise; +}; + +export function useMoveDoc() { + return useMutation({ + mutationFn: moveDoc, + }); +} diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocGridContentList.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocGridContentList.tsx new file mode 100644 index 00000000..fb62a421 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocGridContentList.tsx @@ -0,0 +1,170 @@ +import { DndContext, DragOverlay, Modifier } from '@dnd-kit/core'; +import { getEventCoordinates } from '@dnd-kit/utilities'; +import { TreeViewMoveModeEnum } from '@gouvfr-lasuite/ui-kit'; +import { useQueryClient } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Box, Text } from '@/components'; +import { Doc, KEY_LIST_DOC } from '@/docs/doc-management'; +import { useMoveDoc } from '@/docs/doc-tree/api/useMove'; + +import { useDragAndDrop } from '../hooks/useDragAndDrop'; + +import { DocsGridItem } from './DocsGridItem'; +import { Draggable } from './Draggable'; +import { Droppable } from './Droppable'; + +const snapToTopLeft: Modifier = ({ + activatorEvent, + draggingNodeRect, + transform, +}) => { + if (draggingNodeRect && activatorEvent) { + const activatorCoordinates = getEventCoordinates(activatorEvent); + + if (!activatorCoordinates) { + return transform; + } + + const offsetX = activatorCoordinates.x - draggingNodeRect.left; + const offsetY = activatorCoordinates.y - draggingNodeRect.top; + + return { + ...transform, + x: transform.x + offsetX - 3, + y: transform.y + offsetY - 3, + }; + } + + return transform; +}; + +type DocGridContentListProps = { + docs: Doc[]; +}; + +export const DocGridContentList = ({ docs }: DocGridContentListProps) => { + const { mutate: handleMove, isError } = useMoveDoc(); + const queryClient = useQueryClient(); + const onDrag = (sourceDocumentId: string, targetDocumentId: string) => + handleMove( + { + sourceDocumentId, + targetDocumentId, + position: TreeViewMoveModeEnum.FIRST_CHILD, + }, + { + onSuccess: () => { + void queryClient.invalidateQueries({ + queryKey: [KEY_LIST_DOC], + }); + }, + }, + ); + + const { + selectedDoc, + canDrag, + canDrop, + sensors, + handleDragStart, + handleDragEnd, + updateCanDrop, + } = useDragAndDrop(onDrag); + + const { t } = useTranslation(); + + const overlayText = useMemo(() => { + if (!canDrag) { + return t('You must have admin rights to move the document'); + } + if (!canDrop) { + return t('You must be at least the editor of the target document'); + } + + return selectedDoc?.title || t('Unnamed document'); + }, [canDrag, canDrop, selectedDoc, t]); + + const overlayBgColor = useMemo(() => { + if (!canDrag) { + return 'var(--c--theme--colors--danger-600)'; + } + if (canDrop !== undefined && !canDrop) { + return 'var(--c--theme--colors--danger-600)'; + } + if (isError) { + return 'var(--c--theme--colors--danger-600)'; + } + + return '#5858D3'; + }, [canDrag, canDrop, isError]); + + if (docs.length === 0) { + return null; + } + + return ( + + {docs.map((doc) => ( + + ))} + + + + {overlayText} + + + + + ); +}; + +interface DocGridItemProps { + doc: Doc; + dragMode: boolean; + canDrag: boolean; + updateCanDrop: (canDrop: boolean, isOver: boolean) => void; +} + +export const DraggableDocGridItem = ({ + doc, + dragMode, + canDrag, + updateCanDrop, +}: DocGridItemProps) => { + const canDrop = doc.abilities.move; + + return ( + updateCanDrop(canDrop, isOver)} + id={doc.id} + data={doc} + > + + + + + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx index 53425210..fc0dba2f 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx @@ -9,7 +9,7 @@ import { useResponsiveStore } from '@/stores'; import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid'; -import { DocsGridItem } from './DocsGridItem'; +import { DocGridContentList } from './DocGridContentList'; import { DocsGridLoader } from './DocsGridLoader'; type DocsGridProps = { @@ -37,6 +37,9 @@ export const DocsGrid = ({ is_creator_me: target === DocDefaultFilter.MY_DOCS, }), }); + + const docs = data?.pages.flatMap((page) => page.results) ?? []; + const loading = isFetching || isLoading; const hasDocs = data?.pages.some((page) => page.results.length > 0); const loadMore = (inView: boolean) => { @@ -115,11 +118,7 @@ export const DocsGrid = ({ )} - {data?.pages.map((currentPage) => { - return currentPage.results.map((doc) => ( - - )); - })} + {hasNextPage && !loading && ( { +export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => { const { t } = useTranslation(); const { isDesktop } = useResponsiveStore(); const { flexLeft, flexRight } = useResponsiveDocGrid(); @@ -45,7 +46,9 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => { cursor: pointer; border-radius: 4px; &:hover { - background-color: var(--c--theme--colors--greyscale-100); + background-color: ${dragMode + ? 'none' + : 'var(--c--theme--colors--greyscale-100)'}; } `} className="--docs--doc-grid-item" @@ -79,25 +82,35 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => { : undefined } > - - {isPublic - ? t('Accessible to anyone') - : t('Accessible to authenticated users')} - - } - placement="top" - > -
- -
-
+ {dragMode && ( + + )} + {!dragMode && ( + + {isPublic + ? t('Accessible to anyone') + : t('Accessible to authenticated users')} + + } + placement="top" + > +
+ +
+
+ )} )} diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/Draggable.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/Draggable.tsx new file mode 100644 index 00000000..bafacf0b --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/Draggable.tsx @@ -0,0 +1,26 @@ +import { Data, useDraggable } from '@dnd-kit/core'; + +type DraggableProps = { + id: string; + data?: Data; + children: React.ReactNode; +}; + +export const Draggable = (props: DraggableProps) => { + const { attributes, listeners, setNodeRef } = useDraggable({ + id: props.id, + data: props.data, + }); + + return ( +
+ {props.children} +
+ ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/Droppable.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/Droppable.tsx new file mode 100644 index 00000000..851bf6f6 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/Droppable.tsx @@ -0,0 +1,53 @@ +import { Data, useDroppable } from '@dnd-kit/core'; +import { PropsWithChildren, useEffect } from 'react'; +import { css } from 'styled-components'; + +import { Box } from '@/components'; +import { Doc } from '@/docs/doc-management'; + +type DroppableProps = { + id: string; + onOver?: (isOver: boolean, data?: Data) => void; + data?: Data; + enabledDrop?: boolean; + canDrop?: boolean; +}; + +export const Droppable = ({ + onOver, + canDrop, + data, + children, + id, +}: PropsWithChildren) => { + const { isOver, setNodeRef } = useDroppable({ + id, + data, + }); + + const enableHover = canDrop && isOver; + + useEffect(() => { + onOver?.(isOver, data); + }, [isOver, data, onOver]); + + return ( + + {children} + + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/SimpleDocItem.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/SimpleDocItem.tsx index e0b17350..5fa63bc6 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/SimpleDocItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/SimpleDocItem.tsx @@ -41,6 +41,7 @@ export const SimpleDocItem = ({ $direction="row" $gap={spacingsTokens.sm} $overflow="auto" + $width="100%" className="--docs--simple-doc-item" > void, +) { + const [selectedDoc, setSelectedDoc] = useState(); + const [canDrop, setCanDrop] = useState(); + + const canDrag = selectedDoc?.abilities.move; + + const mouseSensor = useSensor(MouseSensor, { activationConstraint }); + const touchSensor = useSensor(TouchSensor, { activationConstraint }); + const keyboardSensor = useSensor(KeyboardSensor, {}); + const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor); + + const handleDragStart = (e: DragStartEvent) => { + document.body.style.cursor = 'grabbing'; + if (e.active.data.current) { + setSelectedDoc(e.active.data.current as Doc); + } + }; + + const handleDragEnd = (e: DragEndEvent) => { + setSelectedDoc(undefined); + setCanDrop(undefined); + document.body.style.cursor = 'default'; + if (!canDrag || !canDrop) { + return; + } + + const { active, over } = e; + + if (!over?.id || active.id === over?.id) { + return; + } + + onDrag(active.id as string, over.id as string); + }; + + const updateCanDrop = (docCanDrop: boolean, isOver: boolean) => { + if (isOver) { + setCanDrop(docCanDrop); + } + }; + + return { + selectedDoc, + canDrag, + canDrop, + sensors, + handleDragStart, + handleDragEnd, + updateCanDrop, + }; +} diff --git a/src/frontend/apps/impress/src/features/service-worker/plugins/ApiPlugin.ts b/src/frontend/apps/impress/src/features/service-worker/plugins/ApiPlugin.ts index 661f01a1..856585b1 100644 --- a/src/frontend/apps/impress/src/features/service-worker/plugins/ApiPlugin.ts +++ b/src/frontend/apps/impress/src/features/service-worker/plugins/ApiPlugin.ts @@ -175,6 +175,7 @@ export class ApiPlugin implements WorkboxPlugin { is_favorite: false, nb_accesses_direct: 1, nb_accesses_ancestors: 1, + numchild: 0, updated_at: new Date().toISOString(), abilities: { accesses_manage: true, @@ -202,6 +203,7 @@ export class ApiPlugin implements WorkboxPlugin { }, link_reach: LinkReach.RESTRICTED, link_role: LinkRole.READER, + user_roles: [], }; await DocsDB.cacheResponse(