diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f783d4e29..76270aece3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to - ✨(frontend) subdocs can manage link reach #1190 - ✨(frontend) add duplicate action to doc tree #1175 - ✨(frontend) add multi columns support for editor #1219 +- ✨(frontend) Can mask a document from the list view #1233 ### Changed diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py index 83afc260d9..b8fb80b73b 100644 --- a/src/backend/core/api/serializers.py +++ b/src/backend/core/api/serializers.py @@ -66,6 +66,7 @@ class ListDocumentSerializer(serializers.ModelSerializer): """Serialize documents with limited fields for display in lists.""" is_favorite = serializers.BooleanField(read_only=True) + is_masked = serializers.BooleanField(read_only=True) nb_accesses_ancestors = serializers.IntegerField(read_only=True) nb_accesses_direct = serializers.IntegerField(read_only=True) user_role = serializers.SerializerMethodField(read_only=True) @@ -85,6 +86,7 @@ class Meta: "depth", "excerpt", "is_favorite", + "is_masked", "link_role", "link_reach", "nb_accesses_ancestors", @@ -107,6 +109,7 @@ class Meta: "depth", "excerpt", "is_favorite", + "is_masked", "link_role", "link_reach", "nb_accesses_ancestors", @@ -176,6 +179,7 @@ class Meta: "depth", "excerpt", "is_favorite", + "is_masked", "link_role", "link_reach", "nb_accesses_ancestors", diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index ee0c594eb1..918c483178 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -405,6 +405,7 @@ def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) user = self.request.user queryset = queryset.annotate_is_favorite(user) + queryset = queryset.annotate_is_masked(user) queryset = queryset.annotate_user_roles(user) return queryset @@ -453,8 +454,9 @@ def list(self, request, *args, **kwargs): ) queryset = queryset.filter(path__in=root_paths) - # Annotate favorite status and filter if applicable as late as possible + # Annotate favorite and masked status and filter if applicable as late as possible queryset = queryset.annotate_is_favorite(user) + queryset = queryset.annotate_is_masked(user) for field in ["is_favorite", "is_masked"]: queryset = filterset.filters[field].filter(queryset, filter_data[field]) diff --git a/src/backend/core/models.py b/src/backend/core/models.py index a1182964da..d4c02d270b 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -326,6 +326,18 @@ def annotate_is_favorite(self, user): return self.annotate(is_favorite=models.Value(False)) + def annotate_is_masked(self, user): + """ + Annotate document queryset with the masked status for the current user. + """ + if user.is_authenticated: + masked_exists_subquery = LinkTrace.objects.filter( + document_id=models.OuterRef("pk"), user=user, is_masked=True + ) + return self.annotate(is_masked=models.Exists(masked_exists_subquery)) + + return self.annotate(is_masked=models.Value(False)) + def annotate_user_roles(self, user): """ Annotate document queryset with the roles of the current user diff --git a/src/backend/core/tests/documents/test_api_documents_children_list.py b/src/backend/core/tests/documents/test_api_documents_children_list.py index 19bcfd1920..20098d04d9 100644 --- a/src/backend/core/tests/documents/test_api_documents_children_list.py +++ b/src/backend/core/tests/documents/test_api_documents_children_list.py @@ -45,6 +45,7 @@ def test_api_documents_children_list_anonymous_public_standalone( "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -67,6 +68,7 @@ def test_api_documents_children_list_anonymous_public_standalone( "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -119,6 +121,7 @@ def test_api_documents_children_list_anonymous_public_parent(django_assert_num_q "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -141,6 +144,7 @@ def test_api_documents_children_list_anonymous_public_parent(django_assert_num_q "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -212,6 +216,7 @@ def test_api_documents_children_list_authenticated_unrelated_public_or_authentic "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -234,6 +239,7 @@ def test_api_documents_children_list_authenticated_unrelated_public_or_authentic "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -291,6 +297,7 @@ def test_api_documents_children_list_authenticated_public_or_authenticated_paren "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -313,6 +320,7 @@ def test_api_documents_children_list_authenticated_public_or_authenticated_paren "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -397,6 +405,7 @@ def test_api_documents_children_list_authenticated_related_direct( "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -419,6 +428,7 @@ def test_api_documents_children_list_authenticated_related_direct( "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -479,6 +489,7 @@ def test_api_documents_children_list_authenticated_related_parent( "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -501,6 +512,7 @@ def test_api_documents_children_list_authenticated_related_parent( "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -613,6 +625,7 @@ def test_api_documents_children_list_authenticated_related_team_members( "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -635,6 +648,7 @@ def test_api_documents_children_list_authenticated_related_team_members( "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, diff --git a/src/backend/core/tests/documents/test_api_documents_descendants.py b/src/backend/core/tests/documents/test_api_documents_descendants.py index bd2785a7f6..4fa08f68e2 100644 --- a/src/backend/core/tests/documents/test_api_documents_descendants.py +++ b/src/backend/core/tests/documents/test_api_documents_descendants.py @@ -42,6 +42,7 @@ def test_api_documents_descendants_list_anonymous_public_standalone(): "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 1, @@ -66,6 +67,7 @@ def test_api_documents_descendants_list_anonymous_public_standalone(): "excerpt": grand_child.excerpt, "id": str(grand_child.id), "is_favorite": False, + "is_masked": False, "link_reach": grand_child.link_reach, "link_role": grand_child.link_role, "numchild": 0, @@ -88,6 +90,7 @@ def test_api_documents_descendants_list_anonymous_public_standalone(): "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -139,6 +142,7 @@ def test_api_documents_descendants_list_anonymous_public_parent(): "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 1, @@ -161,6 +165,7 @@ def test_api_documents_descendants_list_anonymous_public_parent(): "excerpt": grand_child.excerpt, "id": str(grand_child.id), "is_favorite": False, + "is_masked": False, "link_reach": grand_child.link_reach, "link_role": grand_child.link_role, "numchild": 0, @@ -183,6 +188,7 @@ def test_api_documents_descendants_list_anonymous_public_parent(): "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -255,6 +261,7 @@ def test_api_documents_descendants_list_authenticated_unrelated_public_or_authen "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 1, @@ -277,6 +284,7 @@ def test_api_documents_descendants_list_authenticated_unrelated_public_or_authen "excerpt": grand_child.excerpt, "id": str(grand_child.id), "is_favorite": False, + "is_masked": False, "link_reach": grand_child.link_reach, "link_role": grand_child.link_role, "numchild": 0, @@ -299,6 +307,7 @@ def test_api_documents_descendants_list_authenticated_unrelated_public_or_authen "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -356,6 +365,7 @@ def test_api_documents_descendants_list_authenticated_public_or_authenticated_pa "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 1, @@ -378,6 +388,7 @@ def test_api_documents_descendants_list_authenticated_public_or_authenticated_pa "excerpt": grand_child.excerpt, "id": str(grand_child.id), "is_favorite": False, + "is_masked": False, "link_reach": grand_child.link_reach, "link_role": grand_child.link_role, "numchild": 0, @@ -400,6 +411,7 @@ def test_api_documents_descendants_list_authenticated_public_or_authenticated_pa "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -478,6 +490,7 @@ def test_api_documents_descendants_list_authenticated_related_direct(): "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 1, @@ -500,6 +513,7 @@ def test_api_documents_descendants_list_authenticated_related_direct(): "excerpt": grand_child.excerpt, "id": str(grand_child.id), "is_favorite": False, + "is_masked": False, "link_reach": grand_child.link_reach, "link_role": grand_child.link_role, "numchild": 0, @@ -522,6 +536,7 @@ def test_api_documents_descendants_list_authenticated_related_direct(): "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -580,6 +595,7 @@ def test_api_documents_descendants_list_authenticated_related_parent(): "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 1, @@ -602,6 +618,7 @@ def test_api_documents_descendants_list_authenticated_related_parent(): "excerpt": grand_child.excerpt, "id": str(grand_child.id), "is_favorite": False, + "is_masked": False, "link_reach": grand_child.link_reach, "link_role": grand_child.link_role, "numchild": 0, @@ -624,6 +641,7 @@ def test_api_documents_descendants_list_authenticated_related_parent(): "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -728,6 +746,7 @@ def test_api_documents_descendants_list_authenticated_related_team_members( "excerpt": child1.excerpt, "id": str(child1.id), "is_favorite": False, + "is_masked": False, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 1, @@ -750,6 +769,7 @@ def test_api_documents_descendants_list_authenticated_related_team_members( "excerpt": grand_child.excerpt, "id": str(grand_child.id), "is_favorite": False, + "is_masked": False, "link_reach": grand_child.link_reach, "link_role": grand_child.link_role, "numchild": 0, @@ -772,6 +792,7 @@ def test_api_documents_descendants_list_authenticated_related_team_members( "excerpt": child2.excerpt, "id": str(child2.id), "is_favorite": False, + "is_masked": False, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, diff --git a/src/backend/core/tests/documents/test_api_documents_list.py b/src/backend/core/tests/documents/test_api_documents_list.py index cfaa3e0a1e..2fc9e85ef7 100644 --- a/src/backend/core/tests/documents/test_api_documents_list.py +++ b/src/backend/core/tests/documents/test_api_documents_list.py @@ -72,6 +72,7 @@ def test_api_documents_list_format(): "depth": 1, "excerpt": document.excerpt, "is_favorite": True, + "is_masked": False, "link_reach": document.link_reach, "link_role": document.link_role, "nb_accesses_ancestors": 3, @@ -408,6 +409,7 @@ def test_api_documents_list_favorites_no_extra_queries(django_assert_num_queries assert len(results) == 5 assert all(result["is_favorite"] is False for result in results) + assert all(result["is_masked"] is False for result in results) # Mark documents as favorite and check results again for document in special_documents: @@ -427,3 +429,5 @@ def test_api_documents_list_favorites_no_extra_queries(django_assert_num_queries assert result["is_favorite"] is True else: assert result["is_favorite"] is False + # All documents should be unmasked in this test + assert result["is_masked"] is False diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx index b84321c17d..cb3075cf32 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx @@ -24,6 +24,7 @@ import { useCreateFavoriteDoc, useDeleteFavoriteDoc, useDuplicateDoc, + useMaskDocOption, } from '@/docs/doc-management'; import { DocShareModal } from '@/docs/doc-share'; import { @@ -81,6 +82,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { const makeFavoriteDoc = useCreateFavoriteDoc({ listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], }); + const maskDocOption = useMaskDocOption(doc); useEffect(() => { if (selectHistoryModal.isOpen) { @@ -126,6 +128,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { } }, testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`, + showSeparator: true, }, { label: t('Version history'), @@ -162,17 +165,23 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { canSave: doc.abilities.partial_update, }); }, - }, - { - label: t('Delete document'), - icon: 'delete', - disabled: !doc.abilities.destroy, - callback: () => { - setIsModalRemoveOpen(true); - }, + showSeparator: true, }, ]; + const leaveDocOption: DropdownMenuOption = doc.abilities.destroy + ? { + label: t('Delete document'), + icon: 'delete', + disabled: !doc.abilities.destroy, + callback: () => { + setIsModalRemoveOpen(true); + }, + } + : maskDocOption; + + options.push(leaveDocOption); + const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard(); return ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts b/src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts index 3a0f3437b5..a026de2b2d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts @@ -4,7 +4,8 @@ export * from './useDeleteFavoriteDoc'; export * from './useDoc'; export * from './useDocOptions'; export * from './useDocs'; -export * from './useSubDocs'; export * from './useDuplicateDoc'; +export * from './useMaskDoc'; +export * from './useSubDocs'; export * from './useUpdateDoc'; export * from './useUpdateDocLink'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocs.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocs.tsx index 88f385df5f..bc587dde45 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocs.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocs.tsx @@ -16,6 +16,7 @@ export type DocsParams = { is_creator_me?: boolean; title?: string; is_favorite?: boolean; + is_masked?: boolean; }; export const constructParams = (params: DocsParams): URLSearchParams => { @@ -36,6 +37,9 @@ export const constructParams = (params: DocsParams): URLSearchParams => { if (params.is_favorite !== undefined) { searchParams.set('is_favorite', params.is_favorite.toString()); } + if (params.is_masked !== undefined) { + searchParams.set('is_masked', params.is_masked.toString()); + } return searchParams; }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useMaskDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useMaskDoc.tsx new file mode 100644 index 0000000000..ea881bb3ff --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useMaskDoc.tsx @@ -0,0 +1,77 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; +import { Doc } from '@/docs/doc-management'; + +export type MaskDocParams = Pick; + +export const maskDoc = async ({ id }: MaskDocParams) => { + const response = await fetchAPI(`documents/${id}/mask/`, { + method: 'POST', + }); + + if (!response.ok) { + throw new APIError( + 'Failed to make the doc as masked', + await errorCauses(response), + ); + } +}; + +interface MaskDocProps { + onSuccess?: () => void; + listInvalideQueries?: string[]; +} + +export function useMaskDoc({ onSuccess, listInvalideQueries }: MaskDocProps) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: maskDoc, + onSuccess: () => { + listInvalideQueries?.forEach((queryKey) => { + void queryClient.invalidateQueries({ + queryKey: [queryKey], + }); + }); + onSuccess?.(); + }, + }); +} + +export type DeleteMaskDocParams = Pick; + +export const deleteMaskDoc = async ({ id }: DeleteMaskDocParams) => { + const response = await fetchAPI(`documents/${id}/mask/`, { + method: 'DELETE', + }); + + if (!response.ok) { + throw new APIError( + 'Failed to remove the doc as masked', + await errorCauses(response), + ); + } +}; + +interface DeleteMaskDocProps { + onSuccess?: () => void; + listInvalideQueries?: string[]; +} + +export function useDeleteMaskDoc({ + onSuccess, + listInvalideQueries, +}: DeleteMaskDocProps) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: deleteMaskDoc, + onSuccess: () => { + listInvalideQueries?.forEach((queryKey) => { + void queryClient.invalidateQueries({ + queryKey: [queryKey], + }); + }); + onSuccess?.(); + }, + }); +} diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts index adf2d777ee..f5714e4143 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts @@ -2,4 +2,5 @@ export * from './useCollaboration'; export * from './useCopyDocLink'; export * from './useDocUtils'; export * from './useIsCollaborativeEditable'; +export * from './useMaskDocOption'; export * from './useTrans'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useMaskDocOption.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useMaskDocOption.tsx new file mode 100644 index 0000000000..3e89b62689 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useMaskDocOption.tsx @@ -0,0 +1,42 @@ +import { useTranslation } from 'react-i18next'; + +import { DropdownMenuOption } from '@/components'; + +import { KEY_DOC, KEY_LIST_DOC, useDeleteMaskDoc, useMaskDoc } from '../api'; +import { Doc } from '../types'; + +export const useMaskDocOption = (doc: Doc) => { + const { t } = useTranslation(); + const maskDoc = useMaskDoc({ + listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], + }); + const deleteMaskDoc = useDeleteMaskDoc({ + listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], + }); + + const leaveDocOption: DropdownMenuOption = doc.is_masked + ? { + label: t('Join the doc'), + icon: 'login', + callback: () => { + deleteMaskDoc.mutate({ + id: doc.id, + }); + }, + disabled: !doc.abilities.mask, + testId: `docs-grid-actions-mask-${doc.id}`, + } + : { + label: t('Leave doc'), + icon: 'logout', + callback: () => { + maskDoc.mutate({ + id: doc.id, + }); + }, + disabled: !doc.abilities.mask, + testId: `docs-grid-actions-mask-${doc.id}`, + }; + + return leaveDocOption; +}; 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 7c8c2ea8e9..1bd20ddcf0 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 @@ -59,6 +59,7 @@ export interface Doc { depth: number; path: string; is_favorite: boolean; + is_masked: boolean; link_reach: LinkReach; link_role: LinkRole; nb_accesses_direct: number; @@ -84,6 +85,7 @@ export interface Doc { favorite: boolean; invite_owner: boolean; link_configuration: boolean; + mask: boolean; media_auth: boolean; move: boolean; partial_update: boolean; 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 fc0dba2ff2..1b96407268 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 @@ -32,10 +32,14 @@ export const DocsGrid = ({ hasNextPage, } = useInfiniteDocs({ page: 1, - ...(target && - target !== DocDefaultFilter.ALL_DOCS && { - is_creator_me: target === DocDefaultFilter.MY_DOCS, - }), + is_masked: + !target || target === DocDefaultFilter.ALL_DOCS ? false : undefined, + is_creator_me: + target === DocDefaultFilter.MY_DOCS + ? true + : target === DocDefaultFilter.SHARED_WITH_ME + ? false + : undefined, }); const docs = data?.pages.flatMap((page) => page.results) ?? []; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx index f2fbc04c0a..eded7674b4 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx @@ -9,6 +9,7 @@ import { useCreateFavoriteDoc, useDeleteFavoriteDoc, useDuplicateDoc, + useMaskDocOption, } from '@/docs/doc-management'; interface DocsGridActionsProps { @@ -31,6 +32,7 @@ export const DocsGridActions = ({ const makeFavoriteDoc = useCreateFavoriteDoc({ listInvalideQueries: [KEY_LIST_DOC], }); + const maskDocOption = useMaskDocOption(doc); const options: DropdownMenuOption[] = [ { @@ -44,6 +46,7 @@ export const DocsGridActions = ({ } }, testId: `docs-grid-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`, + showSeparator: true, }, { label: t('Share'), @@ -65,16 +68,22 @@ export const DocsGridActions = ({ canSave: false, }); }, - }, - { - label: t('Remove'), - icon: 'delete', - callback: () => deleteModal.open(), - disabled: !doc.abilities.destroy, - testId: `docs-grid-actions-remove-${doc.id}`, + showSeparator: true, }, ]; + const leaveDocOption: DropdownMenuOption = doc.abilities.destroy + ? { + label: t('Delete document'), + icon: 'delete', + callback: () => deleteModal.open(), + disabled: !doc.abilities.destroy, + testId: `docs-grid-actions-remove-${doc.id}`, + } + : maskDocOption; + + options.push(leaveDocOption); + return ( <> 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 f050e98ee1..a771563bda 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 @@ -174,6 +174,7 @@ export class ApiPlugin implements WorkboxPlugin { creator: 'dummy-id', depth: 1, is_favorite: false, + is_masked: false, nb_accesses_direct: 1, nb_accesses_ancestors: 1, numchild: 0, @@ -192,6 +193,7 @@ export class ApiPlugin implements WorkboxPlugin { favorite: true, invite_owner: true, link_configuration: true, + mask: true, media_auth: true, move: true, partial_update: true,