import React, { useState, FC, useMemo, useEffect, } from 'react'; import { HasObjectId, pagePathUtils } from '@growi/core'; import { useTranslation } from 'next-i18next'; import { Modal, ModalHeader, ModalBody, ModalFooter, } from 'reactstrap'; import { apiPost } from '~/client/util/apiv1-client'; import { apiv3Post } from '~/client/util/apiv3-client'; import { IDeleteSinglePageApiv1Result, IDeleteManyPageApiv3Result, IPageToDeleteWithMeta, IDataWithMeta, isIPageInfoForEntity, IPageInfoForEntity, } from '~/interfaces/page'; import { usePageDeleteModal } from '~/stores/modal'; import { useSWRxPageInfoForList } from '~/stores/page-listing'; import loggerFactory from '~/utils/logger'; import ApiErrorMessageList from './PageManagement/ApiErrorMessageList'; const { isTrashPage } = pagePathUtils; const logger = loggerFactory('growi:cli:PageDeleteModal'); const deleteIconAndKey = { completely: { color: 'danger', icon: 'fire', translationKey: 'completely', }, temporary: { color: 'primary', icon: 'trash', translationKey: 'page', }, }; const PageDeleteModal: FC = () => { const { t } = useTranslation(); const { data: deleteModalData, close: closeDeleteModal } = usePageDeleteModal(); const isOpened = deleteModalData?.isOpened ?? false; const notOperatablePages: IPageToDeleteWithMeta[] = (deleteModalData?.pages ?? []) .filter(p => !isIPageInfoForEntity(p.meta)); const notOperatablePageIds = notOperatablePages.map(p => p.data._id); const { injectTo } = useSWRxPageInfoForList(notOperatablePageIds); // inject IPageInfo to operate let injectedPages: IDataWithMeta[] | null = null; if (deleteModalData?.pages != null && notOperatablePageIds.length > 0) { injectedPages = injectTo(deleteModalData?.pages); } // calculate conditions to delete const [isDeletable, isAbleToDeleteCompletely] = useMemo(() => { if (injectedPages != null && injectedPages.length > 0) { const isDeletable = injectedPages.every(pageWithMeta => pageWithMeta.meta?.isDeletable); const isAbleToDeleteCompletely = injectedPages.every(pageWithMeta => pageWithMeta.meta?.isAbleToDeleteCompletely); return [isDeletable, isAbleToDeleteCompletely]; } return [true, true]; }, [injectedPages]); // calculate condition to determine modal status const forceDeleteCompletelyMode = useMemo(() => { if (deleteModalData != null && deleteModalData.pages != null && deleteModalData.pages.length > 0) { return deleteModalData.pages.every(pageWithMeta => isTrashPage(pageWithMeta.data?.path ?? '')); } return false; }, [deleteModalData]); const [isDeleteRecursively, setIsDeleteRecursively] = useState(true); const [isDeleteCompletely, setIsDeleteCompletely] = useState(forceDeleteCompletelyMode); const deleteMode = forceDeleteCompletelyMode || isDeleteCompletely ? 'completely' : 'temporary'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const [errs, setErrs] = useState(null); // initialize when opening modal useEffect(() => { if (isOpened) { setIsDeleteRecursively(true); setIsDeleteCompletely(forceDeleteCompletelyMode); } }, [forceDeleteCompletelyMode, isOpened]); useEffect(() => { setIsDeleteCompletely(forceDeleteCompletelyMode); }, [forceDeleteCompletelyMode]); function changeIsDeleteRecursivelyHandler() { setIsDeleteRecursively(!isDeleteRecursively); } function changeIsDeleteCompletelyHandler() { if (forceDeleteCompletelyMode) { return; } setIsDeleteCompletely(!isDeleteCompletely); } async function deletePage() { if (deleteModalData == null || deleteModalData.pages == null) { return; } if (!isDeletable) { logger.error('At least one page is not deletable.'); return; } /* * When multiple pages */ if (deleteModalData.pages.length > 1) { try { const isRecursively = isDeleteRecursively === true ? true : undefined; const isCompletely = isDeleteCompletely === true ? true : undefined; const pageIdToRevisionIdMap = {}; deleteModalData.pages.forEach((p) => { pageIdToRevisionIdMap[p.data._id] = p.data.revision as string }); const { data } = await apiv3Post('/pages/delete', { pageIdToRevisionIdMap, isRecursively, isCompletely, }); const onDeleted = deleteModalData.opts?.onDeleted; if (onDeleted != null) { onDeleted(data.paths, data.isRecursively, data.isCompletely); } closeDeleteModal(); } catch (err) { setErrs([err]); } } /* * When single page */ else { try { const recursively = isDeleteRecursively === true ? true : undefined; const completely = forceDeleteCompletelyMode || isDeleteCompletely ? true : undefined; const page = deleteModalData.pages[0].data; const { path, isRecursively, isCompletely } = await apiPost('/pages.remove', { page_id: page._id, revision_id: page.revision, recursively, completely, }) as IDeleteSinglePageApiv1Result; const onDeleted = deleteModalData.opts?.onDeleted; if (onDeleted != null) { onDeleted(path, isRecursively, isCompletely); } closeDeleteModal(); } catch (err) { setErrs([err]); } } } async function deleteButtonHandler() { await deletePage(); } function renderDeleteRecursivelyForm() { return (
); } function renderDeleteCompletelyForm() { return (
{!isAbleToDeleteCompletely && (

{ t('modal_delete.delete_completely_restriction') }

)}
); } const renderPagePathsToDelete = () => { const pages = injectedPages != null && injectedPages.length > 0 ? injectedPages : deleteModalData?.pages; if (pages != null) { return pages.map(page => (

{ page.data.path } { page.meta?.isDeletable != null && !page.meta.isDeletable && (CAN NOT TO DELETE) }

)); } return <>; }; const headerContent = () => { if (!isOpened) { return <>; } return ( <> { t(`modal_delete.delete_${deleteIconAndKey[deleteMode].translationKey}`) } ); }; const bodyContent = () => { if (!isOpened) { return <>; } return ( <>

{/* Todo: change the way to show path on modal when too many pages are selected */} {renderPagePathsToDelete()}
{ isDeletable && renderDeleteRecursivelyForm()} { isDeletable && !forceDeleteCompletelyMode && renderDeleteCompletelyForm() } ); }; const footerContent = () => { if (!isOpened) { return <>; } return ( <> ); }; return ( {headerContent()} {bodyContent()} {footerContent()} ); }; export default PageDeleteModal;