Просмотр исходного кода

Improved success message & deleteModalStatus

Taichi Masuyama 4 лет назад
Родитель
Сommit
95287fe5e6

+ 47 - 18
packages/app/src/components/PageDeleteModal.tsx

@@ -5,9 +5,10 @@ import {
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiPost } from '~/client/util/apiv1-client';
-import { usePageDeleteModal, usePageDeleteModalOpened } from '~/stores/ui';
+import { apiv3Post } from '~/client/util/apiv3-client';
+import { usePageDeleteModalStatus, usePageDeleteModalOpened } from '~/stores/ui';
 
 
-import { IPageApiv1Result } from '~/interfaces/page';
+import { IPageApiv1Result, IDeleteManyPageApiv3Result } from '~/interfaces/page';
 
 
 import ApiErrorMessageList from './PageManagement/ApiErrorMessageList';
 import ApiErrorMessageList from './PageManagement/ApiErrorMessageList';
 
 
@@ -37,7 +38,7 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
     isDeleteCompletelyModal, isAbleToDeleteCompletely,
     isDeleteCompletelyModal, isAbleToDeleteCompletely,
   } = props;
   } = props;
 
 
-  const { data: pagesDataToDelete, close: closeDeleteModal } = usePageDeleteModal();
+  const { data: deleteModalStatus, close: closeDeleteModal } = usePageDeleteModalStatus();
   const { data: pageDeleteModalOpened } = usePageDeleteModalOpened();
   const { data: pageDeleteModalOpened } = usePageDeleteModalOpened();
 
 
   const isOpened = pageDeleteModalOpened?.isOpend != null ? pageDeleteModalOpened.isOpend : false;
   const isOpened = pageDeleteModalOpened?.isOpend != null ? pageDeleteModalOpened.isOpend : false;
@@ -47,7 +48,7 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
   const deleteMode = isDeleteCompletely ? 'completely' : 'temporary';
   const deleteMode = isDeleteCompletely ? 'completely' : 'temporary';
 
 
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  const [errs, setErrs] = useState(null);
+  const [errs, setErrs] = useState<Error[] | null>(null);
 
 
   function changeIsDeleteRecursivelyHandler() {
   function changeIsDeleteRecursivelyHandler() {
     setIsDeleteRecursively(!isDeleteRecursively);
     setIsDeleteRecursively(!isDeleteRecursively);
@@ -61,30 +62,58 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
   }
   }
 
 
   async function deletePage() {
   async function deletePage() {
-    // toastr.warning(t('search_result.currently_not_implemented'));
-    // Todo implement page delete function at https://redmine.weseek.co.jp/issues/82222
-    // setErrs(null);
+    if (deleteModalStatus == null || deleteModalStatus.pages == null) {
+      return;
+    }
 
 
-    if (pagesDataToDelete?.pages != null && (pagesDataToDelete.pages.length > 0)) {
+    /*
+     * When multiple pages
+     */
+    if (deleteModalStatus.pages.length > 1) {
+      try {
+        const isRecursively = isDeleteRecursively === true ? true : undefined;
+        const isCompletely = isDeleteCompletely === true ? true : undefined;
+
+        const pageIdToRevisionIdMap = {};
+        deleteModalStatus.pages.forEach((p) => { pageIdToRevisionIdMap[p.pageId] = p.revisionId });
+
+        const { data } = await apiv3Post<IDeleteManyPageApiv3Result>('/pages/delete', {
+          pageIdToRevisionIdMap,
+          isRecursively,
+          isCompletely,
+        });
+
+        if (pageDeleteModalOpened != null && pageDeleteModalOpened.onDeleted != null) {
+          pageDeleteModalOpened.onDeleted(data.paths, data.isRecursively);
+        }
+      }
+      catch (err) {
+        setErrs([err]);
+      }
+    }
+    /*
+     * When single page
+     */
+    else {
       try {
       try {
         const recursively = isDeleteRecursively === true ? true : undefined;
         const recursively = isDeleteRecursively === true ? true : undefined;
         const completely = isDeleteCompletely === true ? true : undefined;
         const completely = isDeleteCompletely === true ? true : undefined;
 
 
-        // TODO: Create an endpoint (pages.removeMany)
-        const result = await apiPost('/pages.removeMany', {
-          pages: pagesDataToDelete.pages,
+        const page = deleteModalStatus.pages[0];
+
+        const { path, isRecursively } = await apiPost('/pages.remove', {
+          page_id: page.pageId,
+          revision_id: page.revisionId,
           recursively,
           recursively,
           completely,
           completely,
         }) as IPageApiv1Result;
         }) as IPageApiv1Result;
 
 
-        const redirectPagePath = result.page.path;
-
-        if (pageDeleteModalOpened?.onDeleted) {
-          pageDeleteModalOpened.onDeleted(redirectPagePath);
+        if (pageDeleteModalOpened != null && pageDeleteModalOpened.onDeleted != null) {
+          pageDeleteModalOpened.onDeleted(path, isRecursively);
         }
         }
       }
       }
       catch (err) {
       catch (err) {
-        setErrs(err);
+        setErrs([err]);
       }
       }
     }
     }
   }
   }
@@ -147,8 +176,8 @@ const PageDeleteModal: FC<Props> = (props: Props) => {
   }
   }
 
 
   const renderPagePathsToDelete = () => {
   const renderPagePathsToDelete = () => {
-    if (pagesDataToDelete != null && pagesDataToDelete.pages != null) {
-      return pagesDataToDelete.pages.map(page => <div key={page.pageId}><code>{ page.path }</code></div>);
+    if (deleteModalStatus != null && deleteModalStatus.pages != null) {
+      return deleteModalStatus.pages.map(page => <div key={page.pageId}><code>{ page.path }</code></div>);
     }
     }
     return <></>;
     return <></>;
   };
   };

+ 16 - 5
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -1,13 +1,15 @@
 import React, { FC, useEffect } from 'react';
 import React, { FC, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
 
 
 import { IPageHasId } from '../../../interfaces/page';
 import { IPageHasId } from '../../../interfaces/page';
 import { ItemNode } from './ItemNode';
 import { ItemNode } from './ItemNode';
 import Item from './Item';
 import Item from './Item';
 import { useSWRxPageAncestorsChildren, useSWRxRootPage } from '../../../stores/page-listing';
 import { useSWRxPageAncestorsChildren, useSWRxRootPage } from '../../../stores/page-listing';
 import { TargetAndAncestors } from '~/interfaces/page-listing-results';
 import { TargetAndAncestors } from '~/interfaces/page-listing-results';
-import { toastError } from '~/client/util/apiNotification';
+import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import {
 import {
-  IPageForPageDeleteModal, usePageDuplicateModalStatus, usePageRenameModalStatus, usePageDeleteModal,
+  IPageForPageDeleteModal, usePageDuplicateModalStatus, usePageRenameModalStatus, usePageDeleteModalStatus,
+  OnDeletedFunction,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 
 
@@ -91,11 +93,13 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
     targetPath, targetPathOrId, targetAndAncestorsData, isEnableActions,
     targetPath, targetPathOrId, targetAndAncestorsData, isEnableActions,
   } = props;
   } = props;
 
 
+  const { t } = useTranslation();
+
   const { data: ancestorsChildrenData, error: error1 } = useSWRxPageAncestorsChildren(targetPath);
   const { data: ancestorsChildrenData, error: error1 } = useSWRxPageAncestorsChildren(targetPath);
   const { data: rootPageData, error: error2 } = useSWRxRootPage();
   const { data: rootPageData, error: error2 } = useSWRxRootPage();
   const { open: openDuplicateModal } = usePageDuplicateModalStatus();
   const { open: openDuplicateModal } = usePageDuplicateModalStatus();
   const { open: openRenameModal } = usePageRenameModalStatus();
   const { open: openRenameModal } = usePageRenameModalStatus();
-  const { open: openDeleteModal } = usePageDeleteModal();
+  const { open: openDeleteModal } = usePageDeleteModalStatus();
 
 
   useEffect(() => {
   useEffect(() => {
     const startFrom = document.getElementById('grw-sidebar-contents-scroll-target');
     const startFrom = document.getElementById('grw-sidebar-contents-scroll-target');
@@ -114,8 +118,15 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
     openRenameModal(pageId, revisionId, path);
     openRenameModal(pageId, revisionId, path);
   };
   };
 
 
-  const onDeletedHandler = (pagePath) => {
-    window.location.href = encodeURI(pagePath);
+  const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, isRecursively) => {
+    if (typeof pathOrPathsToDelete === 'string') {
+      if (isRecursively) {
+        toastSuccess(t('deleted_single_page_recursively', { path: pathOrPathsToDelete }));
+      }
+      else {
+        toastSuccess(t('deleted_single_page', { path: pathOrPathsToDelete }));
+      }
+    }
   };
   };
 
 
   const onClickDeleteMenuItem = (pageToDelete: IPageForPageDeleteModal) => {
   const onClickDeleteMenuItem = (pageToDelete: IPageForPageDeleteModal) => {

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

@@ -100,5 +100,11 @@ export type IPageWithMeta<M = IPageInfoAll> = {
 
 
 export type IPageApiv1Result = {
 export type IPageApiv1Result = {
   ok: boolean
   ok: boolean
-  page: IPageHasId,
+  path: string,
+  isRecursively: true | null | undefined,
+};
+
+export type IDeleteManyPageApiv3Result = {
+  paths: string[],
+  isRecursively: true | null | undefined,
 };
 };

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

@@ -761,7 +761,7 @@ module.exports = (crowi) => {
     // run delete
     // run delete
     crowi.pageService.deleteMultiplePages(pagesCanBeDeleted, req.user, isCompletely, isRecursively);
     crowi.pageService.deleteMultiplePages(pagesCanBeDeleted, req.user, isCompletely, isRecursively);
 
 
-    return res.apiv3({});
+    return res.apiv3({ paths: pagesCanBeDeleted.map(p => p.path), isRecursively });
   });
   });
 
 
   router.post('/v5-schema-migration', accessTokenParser, loginRequired, adminRequired, csrf, async(req, res) => {
   router.post('/v5-schema-migration', accessTokenParser, loginRequired, adminRequired, csrf, async(req, res) => {

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

@@ -1223,7 +1223,8 @@ module.exports = function(crowi, app) {
 
 
     debug('Page deleted', page.path);
     debug('Page deleted', page.path);
     const result = {};
     const result = {};
-    result.page = page; // TODO consider to use serializePageSecurely method -- 2018.08.06 Yuki Takei
+    result.path = page.path; // TODO consider to use serializePageSecurely method -- 2018.08.06 Yuki Takei
+    result.isRecursively = isRecursively;
 
 
     res.json(ApiResponse.success(result));
     res.json(ApiResponse.success(result));
 
 

+ 12 - 7
packages/app/src/stores/ui.tsx

@@ -303,35 +303,40 @@ export type IPageForPageDeleteModal = {
   path: string
   path: string
 }
 }
 
 
+export type OnDeletedFunction = (pathOrPaths: string | string[], isRecursively: true | null | undefined) => void;
+
 type DeleteModalStatus = {
 type DeleteModalStatus = {
   isOpened: boolean,
   isOpened: boolean,
   pages?: IPageForPageDeleteModal[],
   pages?: IPageForPageDeleteModal[],
-  onDeleted?: (pagePath: string) => void,
+  onDeleted?: OnDeletedFunction,
 }
 }
 
 
 type DeleteModalOpened = {
 type DeleteModalOpened = {
   isOpend: boolean,
   isOpend: boolean,
-  onDeleted?: (pagePath: string) => void,
+  onDeleted?: OnDeletedFunction,
 }
 }
 
 
 type DeleteModalStatusUtils = {
 type DeleteModalStatusUtils = {
-  open(pages?: IPageForPageDeleteModal[], onDeleted?: (pagePath: string) => void): Promise<DeleteModalStatus | undefined>
-  close(): Promise<DeleteModalStatus | undefined>
+  open(
+    pages?: IPageForPageDeleteModal[],
+    onDeleted?: OnDeletedFunction,
+  ): Promise<DeleteModalStatus | undefined>,
+  close(): Promise<DeleteModalStatus | undefined>,
 }
 }
 
 
-export const usePageDeleteModal = (status?: DeleteModalStatus): SWRResponse<DeleteModalStatus, Error> & DeleteModalStatusUtils => {
+export const usePageDeleteModalStatus = (status?: DeleteModalStatus): SWRResponse<DeleteModalStatus, Error> & DeleteModalStatusUtils => {
   const initialData: DeleteModalStatus = { isOpened: false };
   const initialData: DeleteModalStatus = { isOpened: false };
   const swrResponse = useStaticSWR<DeleteModalStatus, Error>('deleteModalStatus', status, { fallbackData: initialData });
   const swrResponse = useStaticSWR<DeleteModalStatus, Error>('deleteModalStatus', status, { fallbackData: initialData });
 
 
   return {
   return {
     ...swrResponse,
     ...swrResponse,
-    open: (pages?: IPageForPageDeleteModal[], onDeleted?:(pagePath: string) => void) => swrResponse.mutate({ isOpened: true, pages, onDeleted }),
+    open: (pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction) => swrResponse.mutate({ isOpened: true, pages, onDeleted }),
     close: () => swrResponse.mutate({ isOpened: false }),
     close: () => swrResponse.mutate({ isOpened: false }),
   };
   };
 };
 };
 
 
 export const usePageDeleteModalOpened = (): SWRResponse<(DeleteModalOpened | null), Error> => {
 export const usePageDeleteModalOpened = (): SWRResponse<(DeleteModalOpened | null), Error> => {
-  const { data } = usePageDeleteModal();
+  const { data } = usePageDeleteModalStatus();
   return useSWRImmutable(
   return useSWRImmutable(
     data != null ? ['isDeleteModalOpened', data] : null,
     data != null ? ['isDeleteModalOpened', data] : null,
     () => {
     () => {