Ver Fonte

imprv: can open global putback page modal

yohei0125 há 4 anos atrás
pai
commit
c8bff5309b

+ 2 - 0
packages/app/src/client/base.jsx

@@ -12,6 +12,7 @@ import PageDuplicateModal from '../components/PageDuplicateModal';
 import PageRenameModal from '../components/PageRenameModal';
 import PageRenameModal from '../components/PageRenameModal';
 import PagePresentationModal from '../components/PagePresentationModal';
 import PagePresentationModal from '../components/PagePresentationModal';
 import PageAccessoriesModal from '../components/PageAccessoriesModal';
 import PageAccessoriesModal from '../components/PageAccessoriesModal';
+import PutbackPageModal from '~/components/PutbackPageModal';
 
 
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import SocketIoContainer from '~/client/services/SocketIoContainer';
 import SocketIoContainer from '~/client/services/SocketIoContainer';
@@ -52,6 +53,7 @@ const componentMappings = {
   'page-presentation-modal': <PagePresentationModal />,
   'page-presentation-modal': <PagePresentationModal />,
   'page-accessories-modal': <PageAccessoriesModal />,
   'page-accessories-modal': <PageAccessoriesModal />,
   'descendants-page-list-modal': <DescendantsPageListModal />,
   'descendants-page-list-modal': <DescendantsPageListModal />,
+  'page-put-back-modal': <PutbackPageModal />,
 
 
   'grw-hotkeys-manager': <HotkeysManager />,
   'grw-hotkeys-manager': <HotkeysManager />,
 
 

+ 19 - 1
packages/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -20,6 +20,7 @@ export const MenuItemType = {
   DUPLICATE: 'duplicate',
   DUPLICATE: 'duplicate',
   RENAME: 'rename',
   RENAME: 'rename',
   DELETE: 'delete',
   DELETE: 'delete',
+  REVERT: 'revert',
 } as const;
 } as const;
 export type MenuItemType = typeof MenuItemType[keyof typeof MenuItemType];
 export type MenuItemType = typeof MenuItemType[keyof typeof MenuItemType];
 
 
@@ -36,6 +37,7 @@ type CommonProps = {
   onClickDuplicateMenuItem?: (pageId: string) => Promise<void> | void,
   onClickDuplicateMenuItem?: (pageId: string) => Promise<void> | void,
   onClickRenameMenuItem?: (pageId: string) => Promise<void> | void,
   onClickRenameMenuItem?: (pageId: string) => Promise<void> | void,
   onClickDeleteMenuItem?: (pageId: string) => Promise<void> | void,
   onClickDeleteMenuItem?: (pageId: string) => Promise<void> | void,
+  onClickRevertMenuItem?: (pageId: string) => Promise<void> | void,
 
 
   additionalMenuItemRenderer?: React.FunctionComponent<AdditionalMenuItemsRendererProps>,
   additionalMenuItemRenderer?: React.FunctionComponent<AdditionalMenuItemsRendererProps>,
 }
 }
@@ -52,7 +54,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
   const {
   const {
     pageId, isLoading,
     pageId, isLoading,
     pageInfo, isEnableActions, forceHideMenuItems,
     pageInfo, isEnableActions, forceHideMenuItems,
-    onClickBookmarkMenuItem, onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem,
+    onClickBookmarkMenuItem, onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem, onClickRevertMenuItem,
     additionalMenuItemRenderer: AdditionalMenuItems,
     additionalMenuItemRenderer: AdditionalMenuItems,
   } = props;
   } = props;
 
 
@@ -81,6 +83,14 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
     await onClickRenameMenuItem(pageId);
     await onClickRenameMenuItem(pageId);
   }, [onClickRenameMenuItem, pageId]);
   }, [onClickRenameMenuItem, pageId]);
 
 
+  const revertItemClickedHandler = useCallback(async() => {
+    if (onClickRevertMenuItem == null) {
+      return;
+    }
+    await onClickRevertMenuItem(pageId);
+  }, [onClickRevertMenuItem]);
+
+
   // eslint-disable-next-line react-hooks/rules-of-hooks
   // eslint-disable-next-line react-hooks/rules-of-hooks
   const deleteItemClickedHandler = useCallback(async() => {
   const deleteItemClickedHandler = useCallback(async() => {
     if (pageInfo == null || onClickDeleteMenuItem == null) {
     if (pageInfo == null || onClickDeleteMenuItem == null) {
@@ -141,6 +151,14 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
           </DropdownItem>
           </DropdownItem>
         ) }
         ) }
 
 
+        {/* Revert */}
+        { !forceHideMenuItems?.includes(MenuItemType.REVERT) && isEnableActions && pageInfo.isRevertible && (
+          <DropdownItem onClick={revertItemClickedHandler}>
+            <i className="icon-fw  icon-action-undo"></i>
+            {t('modal_putback.label.Put Back Page')}
+          </DropdownItem>
+        ) }
+
         { AdditionalMenuItems && (
         { AdditionalMenuItems && (
           <>
           <>
             { showDeviderBeforeAdditionalMenuItems && <DropdownItem divider /> }
             { showDeviderBeforeAdditionalMenuItems && <DropdownItem divider /> }

+ 4 - 14
packages/app/src/components/Page/TrashPageAlert.jsx

@@ -7,11 +7,11 @@ import { UserPicture } from '@growi/ui';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import PageContainer from '~/client/services/PageContainer';
 import PageContainer from '~/client/services/PageContainer';
-import PutbackPageModal from '../PutbackPageModal';
+
 import EmptyTrashModal from '../EmptyTrashModal';
 import EmptyTrashModal from '../EmptyTrashModal';
 
 
 import { useCurrentUpdatedAt, useShareLinkId } from '~/stores/context';
 import { useCurrentUpdatedAt, useShareLinkId } from '~/stores/context';
-import { usePageDeleteModal } from '~/stores/modal';
+import { usePageDeleteModal, usePutBackPageMOdal } from '~/stores/modal';
 import { useSWRxPageInfo } from '~/stores/page';
 import { useSWRxPageInfo } from '~/stores/page';
 
 
 const TrashPageAlert = (props) => {
 const TrashPageAlert = (props) => {
@@ -30,7 +30,6 @@ const TrashPageAlert = (props) => {
 
 
   const { data: updatedAt } = useCurrentUpdatedAt();
   const { data: updatedAt } = useCurrentUpdatedAt();
   const [isEmptyTrashModalShown, setIsEmptyTrashModalShown] = useState(false);
   const [isEmptyTrashModalShown, setIsEmptyTrashModalShown] = useState(false);
-  const [isPutbackPageModalShown, setIsPutbackPageModalShown] = useState(false);
   const [isAbleToDeleteCompletely, setIsAbleToDeleteCompletely] = useState(false);
   const [isAbleToDeleteCompletely, setIsAbleToDeleteCompletely] = useState(false);
 
 
   useEffect(() => {
   useEffect(() => {
@@ -40,6 +39,7 @@ const TrashPageAlert = (props) => {
   }, [pageInfo]);
   }, [pageInfo]);
 
 
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
+  const { open: openPutBackPageModal } = usePutBackPageMOdal();
 
 
   function openEmptyTrashModalHandler() {
   function openEmptyTrashModalHandler() {
     setIsEmptyTrashModalShown(true);
     setIsEmptyTrashModalShown(true);
@@ -50,11 +50,7 @@ const TrashPageAlert = (props) => {
   }
   }
 
 
   function openPutbackPageModalHandler() {
   function openPutbackPageModalHandler() {
-    setIsPutbackPageModalShown(true);
-  }
-
-  function closePutbackPageModalHandler() {
-    setIsPutbackPageModalShown(false);
+    openPutBackPageModal(pageId, path);
   }
   }
 
 
   const onDeletedHandler = useCallback((pathOrPathsToDelete, isRecursively, isCompletely) => {
   const onDeletedHandler = useCallback((pathOrPathsToDelete, isRecursively, isCompletely) => {
@@ -120,12 +116,6 @@ const TrashPageAlert = (props) => {
           isOpen={isEmptyTrashModalShown}
           isOpen={isEmptyTrashModalShown}
           onClose={closeEmptyTrashModalHandler}
           onClose={closeEmptyTrashModalHandler}
         />
         />
-        <PutbackPageModal
-          isOpen={isPutbackPageModalShown}
-          onClose={closePutbackPageModalHandler}
-          pageId={pageId}
-          path={path}
-        />
       </>
       </>
     );
     );
   }
   }

+ 10 - 1
packages/app/src/components/PageList/PageListItemL.tsx

@@ -12,7 +12,9 @@ import urljoin from 'url-join';
 import { UserPicture, PageListMeta } from '@growi/ui';
 import { UserPicture, PageListMeta } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
 import { DevidedPagePath } from '@growi/core';
 import { useIsDeviceSmallerThanLg } from '~/stores/ui';
 import { useIsDeviceSmallerThanLg } from '~/stores/ui';
-import { usePageRenameModal, usePageDuplicateModal, usePageDeleteModal } from '~/stores/modal';
+import {
+  usePageRenameModal, usePageDuplicateModal, usePageDeleteModal, usePutBackPageMOdal,
+} from '~/stores/modal';
 import {
 import {
   IPageInfoAll, IPageWithMeta, isIPageInfoForEntity, isIPageInfoForListing,
   IPageInfoAll, IPageWithMeta, isIPageInfoForEntity, isIPageInfoForListing,
 } from '~/interfaces/page';
 } from '~/interfaces/page';
@@ -64,6 +66,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
+  const { open: openPutBackPageModal } = usePutBackPageMOdal();
 
 
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null;
   const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null;
@@ -101,6 +104,11 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
     openDeleteModal([{ pageId, revisionId: revisionId as string, path }]);
     openDeleteModal([{ pageId, revisionId: revisionId as string, path }]);
   }, [openDeleteModal, pageData]);
   }, [openDeleteModal, pageData]);
 
 
+  const revertMenuItemClickHandler = useCallback(() => {
+    const { _id: pageId, path } = pageData;
+    openPutBackPageModal(pageId, path);
+  }, [openPutBackPageModal, pageData]);
+
   const styleListGroupItem = (!isDeviceSmallerThanLg && onClickItem != null) ? 'list-group-item-action' : '';
   const styleListGroupItem = (!isDeviceSmallerThanLg && onClickItem != null) ? 'list-group-item-action' : '';
   // background color of list item changes when class "active" exists under 'list-group-item'
   // background color of list item changes when class "active" exists under 'list-group-item'
   const styleActive = !isDeviceSmallerThanLg && isSelected ? 'active' : '';
   const styleActive = !isDeviceSmallerThanLg && isSelected ? 'active' : '';
@@ -168,6 +176,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
                   onClickDeleteMenuItem={deleteMenuItemClickHandler}
                   onClickDeleteMenuItem={deleteMenuItemClickHandler}
                   onClickRenameMenuItem={renameMenuItemClickHandler}
                   onClickRenameMenuItem={renameMenuItemClickHandler}
                   onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
                   onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
+                  onClickRevertMenuItem={revertMenuItemClickHandler}
                 />
                 />
               </div>
               </div>
             </div>
             </div>

+ 1 - 1
packages/app/src/components/PrivateLegacyPages.tsx

@@ -290,7 +290,7 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
         appContainer={appContainer}
         appContainer={appContainer}
         pages={data?.data}
         pages={data?.data}
         onSelectedPagesByCheckboxesChanged={selectedPagesByCheckboxesChangedHandler}
         onSelectedPagesByCheckboxesChanged={selectedPagesByCheckboxesChangedHandler}
-        forceHideMenuItems={[MenuItemType.BOOKMARK, MenuItemType.RENAME, MenuItemType.DUPLICATE]}
+        forceHideMenuItems={[MenuItemType.BOOKMARK, MenuItemType.RENAME, MenuItemType.DUPLICATE, MenuItemType.REVERT]}
         // Components
         // Components
         searchControl={searchControl}
         searchControl={searchControl}
         searchResultListHead={searchResultListHead}
         searchResultListHead={searchResultListHead}

+ 7 - 9
packages/app/src/components/PutbackPageModal.jsx

@@ -7,15 +7,19 @@ import {
 
 
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
+import { usePutBackPageMOdal } from '~/stores/modal';
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiPost } from '~/client/util/apiv1-client';
 
 
 import ApiErrorMessageList from './PageManagement/ApiErrorMessageList';
 import ApiErrorMessageList from './PageManagement/ApiErrorMessageList';
 
 
 const PutBackPageModal = (props) => {
 const PutBackPageModal = (props) => {
   const {
   const {
-    t, isOpen, onClose, pageId, path,
+    t,
   } = props;
   } = props;
 
 
+  const { data: pageDataToRevert, close: closePutBackPageModal } = usePutBackPageMOdal();
+  const { isOpened, pageId, path } = pageDataToRevert;
+
   const [errs, setErrs] = useState(null);
   const [errs, setErrs] = useState(null);
 
 
   const [isPutbackRecursively, setIsPutbackRecursively] = useState(true);
   const [isPutbackRecursively, setIsPutbackRecursively] = useState(true);
@@ -50,8 +54,8 @@ const PutBackPageModal = (props) => {
   }
   }
 
 
   return (
   return (
-    <Modal isOpen={isOpen} toggle={onClose} className="grw-create-page">
-      <ModalHeader tag="h4" toggle={onClose} className="bg-info text-light">
+    <Modal isOpen={isOpened} toggle={closePutBackPageModal} className="grw-create-page">
+      <ModalHeader tag="h4" toggle={closePutBackPageModal} className="bg-info text-light">
         <i className="icon-action-undo mr-2" aria-hidden="true"></i> { t('modal_putback.label.Put Back Page') }
         <i className="icon-action-undo mr-2" aria-hidden="true"></i> { t('modal_putback.label.Put Back Page') }
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
@@ -88,12 +92,6 @@ const PutBackPageModal = (props) => {
 
 
 PutBackPageModal.propTypes = {
 PutBackPageModal.propTypes = {
   t: PropTypes.func.isRequired, //  i18next
   t: PropTypes.func.isRequired, //  i18next
-
-  isOpen: PropTypes.bool.isRequired,
-  onClose: PropTypes.func.isRequired,
-
-  pageId: PropTypes.string.isRequired,
-  path: PropTypes.string.isRequired,
 };
 };
 
 
 
 

+ 1 - 0
packages/app/src/interfaces/page.ts

@@ -41,6 +41,7 @@ export type IPageInfo = {
   isMovable: boolean,
   isMovable: boolean,
   isDeletable: boolean,
   isDeletable: boolean,
   isAbleToDeleteCompletely: boolean,
   isAbleToDeleteCompletely: boolean,
+  isRevertible: boolean,
 }
 }
 
 
 export type IPageInfoForEntity = IPageInfo & {
 export type IPageInfoForEntity = IPageInfo & {

+ 2 - 1
packages/app/src/server/routes/apiv3/page.js

@@ -10,7 +10,7 @@ const express = require('express');
 const { body, query } = require('express-validator');
 const { body, query } = require('express-validator');
 
 
 const router = express.Router();
 const router = express.Router();
-const { convertToNewAffiliationPath } = pagePathUtils;
+const { convertToNewAffiliationPath, isTrashPage } = pagePathUtils;
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
 
 
@@ -372,6 +372,7 @@ module.exports = (crowi) => {
           isMovable: false,
           isMovable: false,
           isDeletable: false,
           isDeletable: false,
           isAbleToDeleteCompletely: false,
           isAbleToDeleteCompletely: false,
+          isRevertible: false,
         };
         };
       }
       }
 
 

+ 3 - 0
packages/app/src/server/service/page.ts

@@ -1690,6 +1690,7 @@ class PageService {
 
 
   constructBasicPageInfo(page: IPage, isGuestUser?: boolean): IPageInfo | IPageInfoForEntity {
   constructBasicPageInfo(page: IPage, isGuestUser?: boolean): IPageInfo | IPageInfoForEntity {
     const isMovable = isGuestUser ? false : !isTopPage(page.path) && !isUserPage(page.path) && !isUserNamePage(page.path);
     const isMovable = isGuestUser ? false : !isTopPage(page.path) && !isUserPage(page.path) && !isUserNamePage(page.path);
+    const isRevertible = isTrashPage(page.path);
 
 
     if (page.isEmpty) {
     if (page.isEmpty) {
       return {
       return {
@@ -1697,6 +1698,7 @@ class PageService {
         isMovable,
         isMovable,
         isDeletable: false,
         isDeletable: false,
         isAbleToDeleteCompletely: false,
         isAbleToDeleteCompletely: false,
+        isRevertible: false,
       };
       };
     }
     }
 
 
@@ -1713,6 +1715,7 @@ class PageService {
       isMovable,
       isMovable,
       isDeletable: Page.isDeletableName(page.path),
       isDeletable: Page.isDeletableName(page.path),
       isAbleToDeleteCompletely: false,
       isAbleToDeleteCompletely: false,
+      isRevertible,
     };
     };
 
 
   }
   }

+ 1 - 0
packages/app/src/server/views/layout/layout.html

@@ -110,6 +110,7 @@
 <div id="page-presentation-modal"></div>
 <div id="page-presentation-modal"></div>
 <div id="page-accessories-modal"></div>
 <div id="page-accessories-modal"></div>
 <div id="descendants-page-list-modal"></div>
 <div id="descendants-page-list-modal"></div>
+<div id="page-put-back-modal"></div>
 
 
 {% include '../modal/shortcuts.html' %}
 {% include '../modal/shortcuts.html' %}
 
 

+ 25 - 0
packages/app/src/stores/modal.tsx

@@ -150,6 +150,31 @@ export const usePageRenameModal = (status?: RenameModalStatus): SWRResponse<Rena
   };
   };
 };
 };
 
 
+type PutBackPageModalStatus = {
+  isOpened: boolean,
+  pageId?: string,
+  path?: string,
+}
+
+type PutBackPageModalUtils = {
+  open(pageId: string, path: string): Promise<PutBackPageModalStatus | undefined>
+  close():Promise<PutBackPageModalStatus | undefined>
+}
+
+export const usePutBackPageMOdal = (status?: PutBackPageModalStatus): SWRResponse<PutBackPageModalStatus, Error> & PutBackPageModalUtils => {
+  const initialData = { isOpened: false, pageId: '', path: '' };
+  const swrResponse = useStaticSWR<PutBackPageModalStatus, Error>('putBackPageModalStatus', status, { fallbackData: initialData });
+
+  return {
+    ...swrResponse,
+    open: (pageId: string, path: string) => swrResponse.mutate({
+      isOpened: true, pageId, path,
+    }),
+    close: () => swrResponse.mutate({ isOpened: false }),
+  };
+};
+
+
 /*
 /*
 * PagePresentationModal
 * PagePresentationModal
 */
 */