From 3646d7dc4227688fad1d5d683e274f1ae3692c78 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Mon, 3 Aug 2026 16:03:53 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=9A(frontend)=20add=20doc=20move=20to?= =?UTF-8?q?=20doc=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to harmonize the doc options, we are adding the "Move to my docs" and "Move into a doc" options to the doc options menu. This allows users to move documents directly from the options menu, providing a more streamlined experience. --- CHANGELOG.md | 1 + .../src/assets/icons/ui-kit/doc-move-out.svg | 22 +++++-- .../docs/doc-management/api/useMoveDoc.tsx | 8 ++- .../components/DocMoveModal.tsx | 14 +++-- .../doc-management/components/DocToolBox.tsx | 63 ++++++++++++++++++- .../components/ModalConfimationMoveDoc.tsx | 0 .../docs/doc-tree/assets/doc-extract-bold.svg | 10 --- .../doc-tree/components/DocSubPageItem.tsx | 10 +-- .../components/DocTreeItemActions.tsx | 15 +++-- .../components/DocGridContentList.tsx | 8 ++- .../docs-grid/components/DocsGridActions.tsx | 12 +++- 11 files changed, 114 insertions(+), 49 deletions(-) rename src/frontend/apps/impress/src/features/docs/{docs-grid => doc-management}/components/DocMoveModal.tsx (98%) rename src/frontend/apps/impress/src/features/docs/{docs-grid => doc-management}/components/ModalConfimationMoveDoc.tsx (100%) delete mode 100644 src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-extract-bold.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 0572d8d2f8..7b69c7c211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to - ✨(frontend) reset side panel state between documents #2583 - ♿️(frontend) announce search loading state for screen readers #2526 - ♻️(frontend) change favorite to star #2539 +- 🚚(frontend) add doc move to doc options #2555 ### Fixed diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/doc-move-out.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/doc-move-out.svg index 2a2055079f..3f23292b2e 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/doc-move-out.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/doc-move-out.svg @@ -1,6 +1,18 @@ - - - - - + + + + + diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useMoveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useMoveDoc.tsx index fe57004171..77b35bb3ff 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useMoveDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useMoveDoc.tsx @@ -2,7 +2,9 @@ import { TreeViewMoveModeEnum } from '@gouvfr-lasuite/ui-components'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { APIError, errorCauses, fetchAPI } from '@/api'; +import { KEY_DOC_TREE } from '@/docs/doc-tree/api/useDocTree'; +import { KEY_DOC } from './useDoc'; import { KEY_LIST_DOC } from './useDocs'; export type MoveDocParam = { @@ -37,9 +39,9 @@ export function useMoveDoc() { return useMutation({ mutationFn: moveDoc, onSuccess() { - void queryClient.invalidateQueries({ - queryKey: [KEY_LIST_DOC], - }); + void queryClient.invalidateQueries({ queryKey: [KEY_LIST_DOC] }); + void queryClient.invalidateQueries({ queryKey: [KEY_DOC] }); + void queryClient.invalidateQueries({ queryKey: [KEY_DOC_TREE] }); }, }); } diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocMoveModal.tsx similarity index 98% rename from src/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx rename to src/frontend/apps/impress/src/features/docs/doc-management/components/DocMoveModal.tsx index 82747f1f85..03e67437ac 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocMoveModal.tsx @@ -17,10 +17,12 @@ import { Box, ButtonCloseModal, Text } from '@/components'; import { QuickSearch } from '@/components/quick-search'; import { Doc, useMoveDoc, useTrans } from '@/docs/doc-management'; import { DocSearchContent } from '@/docs/doc-search'; +import { + DocsGridItemDate, + DocsGridItemTitle, +} from '@/docs/docs-grid/components/DocsGridItem'; import { useResponsiveStore } from '@/stores'; -import { DocsGridItemDate, DocsGridItemTitle } from './DocsGridItem'; - const AlertModalRequestAccess = dynamic( () => import('@/docs/doc-share/components/AlertModalRequestAccess').then( @@ -83,12 +85,14 @@ type DocMoveModalGlobalProps = { doc: Doc; isOpen: boolean; onClose: () => void; + onAfterMove?: () => void; }; export const DocMoveModal = ({ doc, isOpen, onClose, + onAfterMove, }: DocMoveModalGlobalProps) => { const { t } = useTranslation(); const [loading, setLoading] = useState(false); @@ -120,10 +124,8 @@ export const DocMoveModal = ({ position: TreeViewMoveModeEnum.FIRST_CHILD, }) .then(() => { - toast( - t(`The document has been moved successfully.`), - VariantType.SUCCESS, - ); + onClose(); + onAfterMove?.(); }) .catch(() => { toast( diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/DocToolBox.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocToolBox.tsx index 635be7202e..2ad394f3a9 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/DocToolBox.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocToolBox.tsx @@ -16,8 +16,11 @@ import { useEditorStore } from '@/docs/doc-editor/stores/useEditorStore'; import { getWordCount } from '@/docs/doc-editor/utils'; import { printDocumentWithStyles } from '@/docs/doc-export/utils_print'; import { usePresenterStore } from '@/docs/doc-presenter/stores'; +import { useDetachDoc } from '@/docs/doc-tree/api/useDetach'; import { useAuth } from '@/features/auth'; import ContentCopyIcon from '@/icons/copy.svg'; +import DocMoveInIcon from '@/icons/doc-move-in.svg'; +import DocMoveOutIcon from '@/icons/doc-move-out.svg'; import DownloadSVG from '@/icons/download.svg'; import HistorySVG from '@/icons/history.svg'; import LeaveSVG from '@/icons/leave.svg'; @@ -39,7 +42,15 @@ import { useDuplicateDoc, } from '../api'; import { useCopyDocLink } from '../hooks'; -import { Doc } from '../types'; +import { Doc, Role } from '../types'; + +const DocMoveModal = dynamic( + () => + import('@/docs/doc-management/components/DocMoveModal').then((mod) => ({ + default: mod.DocMoveModal, + })), + { ssr: false }, +); const ModalRemoveDoc = dynamic( () => @@ -89,7 +100,7 @@ interface DocToolBoxProps { export const DocToolBox = ({ doc }: DocToolBoxProps) => { const { t } = useTranslation(); - const treeContext = useTreeContext(); + const treeContext = useTreeContext(); const router = useRouter(); const isTopParent = doc.id === treeContext?.root?.id; // it can be a child but not for the current user const { authenticated } = useAuth(); @@ -99,6 +110,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { const [isModalShareOpen, setIsModalShareOpen] = useState(false); const [isModalHistoryOpen, setIsModalHistoryOpen] = useState(false); const [isModalLeaveOpen, setIsModalLeaveOpen] = useState(false); + const [isModalMoveOpen, setIsModalMoveOpen] = useState(false); const { editor } = useEditorStore(); const wordCountLabel = useMemo(() => { @@ -117,6 +129,8 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { } }, [wordCountLabel]); + const { mutate: detachDoc } = useDetachDoc(); + const { restoreFocus, addLastFocus } = useFocusStore(); const { isMobile } = useResponsiveStore(); const copyDocLink = useCopyDocLink(doc.id); @@ -217,6 +231,40 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { }, isHidden: !doc.abilities.duplicate, }, + { + label: t('Move to my docs'), + isHidden: isTopParent || doc.user_role !== Role.OWNER, + icon: , + callback: () => { + if (!treeContext?.root) { + return; + } + + detachDoc( + { documentId: doc.id, rootId: treeContext.root.id }, + { + onSuccess: () => { + if (treeContext.root) { + treeContext.treeData.setSelectedNode(treeContext.root); + void router.push(`/docs/${treeContext.root.id}`).then(() => { + setTimeout(() => { + treeContext?.treeData.deleteNode(doc.id); + }, 100); + }); + } + }, + }, + ); + }, + }, + { + label: t('Move into a doc'), + icon: , + callback: () => { + setIsModalMoveOpen(true); + }, + isHidden: !doc.abilities.move, + }, { type: 'separator' }, { label: t('History', { @@ -347,6 +395,17 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { doc={doc} /> )} + {isModalMoveOpen && ( + { + setIsModalMoveOpen(false); + restoreFocus(); + }} + isOpen={isModalMoveOpen} + onAfterMove={() => treeContext?.setRoot(null)} + /> + )} ); }; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/ModalConfimationMoveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalConfimationMoveDoc.tsx similarity index 100% rename from src/frontend/apps/impress/src/features/docs/docs-grid/components/ModalConfimationMoveDoc.tsx rename to src/frontend/apps/impress/src/features/docs/doc-management/components/ModalConfimationMoveDoc.tsx diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-extract-bold.svg b/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-extract-bold.svg deleted file mode 100644 index 47d4fa1a9e..0000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-extract-bold.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx index 858d62d3da..c7e6c1753e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx @@ -11,7 +11,7 @@ import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; -import { Box, Icon, StyledLink, Text } from '@/components'; +import { Box, StyledLink, Text } from '@/components'; import { Doc, DocIcon, @@ -294,14 +294,6 @@ const DocSubPageItemContent = (props: TreeViewNodeProps) => { {displayTitle} - {doc.nb_accesses_direct >= 1 && ( - - )} - - + ), callback: handleDetachDoc, }, 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 index 1ed27613ae..291c845911 100644 --- 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 @@ -24,9 +24,11 @@ import { DocsGridItem } from './DocsGridItem'; const ModalConfirmationMoveDoc = dynamic( () => - import('./ModalConfimationMoveDoc').then((mod) => ({ - default: mod.ModalConfirmationMoveDoc, - })), + import('@/docs/doc-management/components/ModalConfimationMoveDoc').then( + (mod) => ({ + default: mod.ModalConfirmationMoveDoc, + }), + ), { ssr: false }, ); 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 156e5cfcfc..9180c412ce 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 @@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'; import { Icon } from '@/components/Icon'; import { - Doc, + type Doc, KEY_LIST_DOC, KEY_LIST_FAVORITE_DOC, useCreateFavoriteDoc, @@ -31,9 +31,15 @@ import StarIcon from '@/icons/star.svg'; import { focusMainContentStart } from '@/layouts/utils'; import { useFocusStore } from '@/stores'; -import { KEY_LIST_DOC_TRASHBIN } from '../api/useDocsTrashbin'; +import { KEY_LIST_DOC_TRASHBIN } from '../api'; -import { DocMoveModal } from './DocMoveModal'; +const DocMoveModal = dynamic( + () => + import('@/docs/doc-management/components/DocMoveModal').then((mod) => ({ + default: mod.DocMoveModal, + })), + { ssr: false }, +); const DocShareModal = dynamic( () =>