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

Merge pull request #4772 from weseek/feat/77525-82546-extend-delete-modal

82546 modify PageDeleteModal
Yohei Shiina 4 лет назад
Родитель
Сommit
00dc0f0160

+ 6 - 3
packages/app/src/components/Page/PageManagement.jsx

@@ -157,10 +157,15 @@ const PageManagement = (props) => {
     );
   }
 
+  function generatePageObjectToDelete() {
+    return { pageId, revisionId, path };
+  }
+
   function renderModals() {
     if (currentUser == null) {
       return null;
     }
+    const pageToDelete = generatePageObjectToDelete();
 
     return (
       <>
@@ -184,9 +189,7 @@ const PageManagement = (props) => {
         <PageDeleteModal
           isOpen={isPageDeleteModalShown}
           onClose={closePageDeleteModalHandler}
-          pageId={pageId}
-          revisionId={revisionId}
-          path={path}
+          pages={[pageToDelete]}
           isAbleToDeleteCompletely={isAbleToDeleteCompletely}
         />
         <PagePresentationModal

+ 58 - 52
packages/app/src/components/PageDeleteModal.jsx → packages/app/src/components/PageDeleteModal.tsx

@@ -1,16 +1,20 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-
+import React, { useState, FC } from 'react';
+import toastr from 'toastr';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
+import { useTranslation } from 'react-i18next';
 
-import { withTranslation } from 'react-i18next';
-
-import { apiPost } from '~/client/util/apiv1-client';
+// import { apiPost } from '~/client/util/apiv1-client';
 
 import ApiErrorMessageList from './PageManagement/ApiErrorMessageList';
 
+export type IPageForPageDeleteModal = {
+  pageId: string,
+  revisionId: string,
+  path: string
+}
+
 const deleteIconAndKey = {
   completely: {
     color: 'danger',
@@ -24,9 +28,18 @@ const deleteIconAndKey = {
   },
 };
 
-const PageDeleteModal = (props) => {
+type Props = {
+  isOpen: boolean,
+  pages: IPageForPageDeleteModal[],
+  isDeleteCompletelyModal: boolean,
+  isAbleToDeleteCompletely: boolean,
+  onClose?: () => void,
+}
+
+const PageDeleteModal: FC<Props> = (props: Props) => {
+  const { t } = useTranslation('');
   const {
-    t, isOpen, onClose, isDeleteCompletelyModal, pageId, revisionId, path, isAbleToDeleteCompletely,
+    isOpen, onClose, isDeleteCompletelyModal, pages, isAbleToDeleteCompletely,
   } = props;
   const [isDeleteRecursively, setIsDeleteRecursively] = useState(true);
   const [isDeleteCompletely, setIsDeleteCompletely] = useState(isDeleteCompletelyModal && isAbleToDeleteCompletely);
@@ -46,27 +59,29 @@ const PageDeleteModal = (props) => {
   }
 
   async function deletePage() {
-    setErrs(null);
-
-    try {
-      // control flag
-      // If is it not true, Request value must be `null`.
-      const recursively = isDeleteRecursively ? true : null;
-      const completely = isDeleteCompletely ? true : null;
-
-      const response = await apiPost('/pages.remove', {
-        page_id: pageId,
-        revision_id: revisionId,
-        recursively,
-        completely,
-      });
-
-      const trashPagePath = response.page.path;
-      window.location.href = encodeURI(trashPagePath);
-    }
-    catch (err) {
-      setErrs(err);
-    }
+    toastr.warning(t('search_result.currently_not_implemented'));
+    // Todo implement page delete function at https://redmine.weseek.co.jp/issues/82222
+    // setErrs(null);
+
+    // try {
+    //   // control flag
+    //   // If is it not true, Request value must be `null`.
+    //   const recursively = isDeleteRecursively ? true : null;
+    //   const completely = isDeleteCompletely ? true : null;
+
+    //   const response = await apiPost('/pages.remove', {
+    //     page_id: pageId,
+    //     revision_id: revisionId,
+    //     recursively,
+    //     completely,
+    //   });
+
+    //   const trashPagePath = response.page.path;
+    //   window.location.href = encodeURI(trashPagePath);
+    // }
+    // catch (err) {
+    //   setErrs(err);
+    // }
   }
 
   async function deleteButtonHandler() {
@@ -80,12 +95,13 @@ const PageDeleteModal = (props) => {
           className="custom-control-input"
           id="deleteRecursively"
           type="checkbox"
-          checked={isDeleteRecursively}
+          // checked={isDeleteRecursively}
+          checked={false}
           onChange={changeIsDeleteRecursivelyHandler}
+          disabled // Todo: enable this at https://redmine.weseek.co.jp/issues/82222
         />
         <label className="custom-control-label" htmlFor="deleteRecursively">
           { t('modal_delete.delete_recursively') }
-          <p className="form-text text-muted mt-0"><code>{path}</code> { t('modal_delete.recursively') }</p>
         </label>
       </div>
     );
@@ -99,11 +115,14 @@ const PageDeleteModal = (props) => {
           name="completely"
           id="deleteCompletely"
           type="checkbox"
-          disabled={!isAbleToDeleteCompletely}
+          // disabled={!isAbleToDeleteCompletely}
+          disabled // Todo: will be implemented at https://redmine.weseek.co.jp/issues/82222
           checked={isDeleteCompletely}
           onChange={changeIsDeleteCompletelyHandler}
         />
-        <label className="custom-control-label text-danger" htmlFor="deleteCompletely">
+        {/* ↓↓ undo this comment out at https://redmine.weseek.co.jp/issues/82222 ↓↓ */}
+        {/* <label className="custom-control-label text-danger" htmlFor="deleteCompletely"> */}
+        <label className="custom-control-label" htmlFor="deleteCompletely">
           { t('modal_delete.delete_completely') }
           <p className="form-text text-muted mt-0"> { t('modal_delete.completely') }</p>
         </label>
@@ -126,7 +145,11 @@ const PageDeleteModal = (props) => {
       <ModalBody>
         <div className="form-group">
           <label>{ t('modal_delete.deleting_page') }:</label><br />
-          <code>{ path }</code>
+          {/* Todo: change the way to show path on modal when too many pages are selected */}
+          {/* https://redmine.weseek.co.jp/issues/82787 */}
+          {pages.map((page) => {
+            return <div><code>{ page.path }</code></div>;
+          })}
         </div>
         {renderDeleteRecursivelyForm()}
         {!isDeleteCompletelyModal && renderDeleteCompletelyForm()}
@@ -143,21 +166,4 @@ const PageDeleteModal = (props) => {
   );
 };
 
-PageDeleteModal.propTypes = {
-  t: PropTypes.func.isRequired, //  i18next
-
-  isOpen: PropTypes.bool.isRequired,
-  onClose: PropTypes.func.isRequired,
-
-  pageId: PropTypes.string.isRequired,
-  revisionId: PropTypes.string.isRequired,
-  path: PropTypes.string.isRequired,
-  isDeleteCompletelyModal: PropTypes.bool,
-  isAbleToDeleteCompletely: PropTypes.bool,
-};
-
-PageDeleteModal.defaultProps = {
-  isDeleteCompletelyModal: false,
-};
-
-export default withTranslation()(PageDeleteModal);
+export default PageDeleteModal;

+ 28 - 65
packages/app/src/components/SearchPage.jsx

@@ -3,7 +3,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
 
 import { withUnstatedContainers } from './UnstatedUtils';
 import AppContainer from '~/client/services/AppContainer';
@@ -12,7 +11,7 @@ import SearchPageLayout from './SearchPage/SearchPageLayout';
 import SearchResultContent from './SearchPage/SearchResultContent';
 import SearchResultList from './SearchPage/SearchResultList';
 import SearchControl from './SearchPage/SearchControl';
-import DeletePageListModal from './SearchPage/DeletePageListModal';
+import PageDeleteModal from './PageDeleteModal';
 
 import { CheckboxType } from '../interfaces/search';
 
@@ -20,16 +19,6 @@ export const specificPathNames = {
   user: '/user',
   trash: '/trash',
 };
-
-const toastrOption = {
-  closeButton: true,
-  progressBar: true,
-  newestOnTop: false,
-  showDuration: '100',
-  hideDuration: '100',
-  timeOut: '3000',
-};
-
 class SearchPage extends React.Component {
 
   constructor(props) {
@@ -50,6 +39,8 @@ class SearchPage extends React.Component {
       excludeUsersHome: true,
       excludeTrash: true,
       selectAllCheckboxType: CheckboxType.NONE_CHECKED,
+      isDeleteConfirmModalShown: false,
+      deleteTargetPageIds: new Set(),
     };
 
     this.changeURL = this.changeURL.bind(this);
@@ -61,10 +52,9 @@ class SearchPage extends React.Component {
     this.onExcludeTrash = this.onExcludeTrash.bind(this);
     this.onPagingNumberChanged = this.onPagingNumberChanged.bind(this);
     this.onPagingLimitChanged = this.onPagingLimitChanged.bind(this);
-    this.deleteSelectedPages = this.deleteSelectedPages.bind(this);
-    this.onClickDeleteAllButton = this.onClickDeleteAllButton.bind(this);
-    this.onCloseDeleteConfirmModal = this.onCloseDeleteConfirmModal.bind(this);
-    this.onChangeDeleteCompletely = this.onChangeDeleteCompletely.bind(this);
+    this.deleteSinglePageButtonHandler = this.deleteSinglePageButtonHandler.bind(this);
+    this.deleteAllPagesButtonHandler = this.deleteAllPagesButtonHandler.bind(this);
+    this.closeDeleteConfirmModalHandler = this.closeDeleteConfirmModalHandler.bind(this);
   }
 
   componentDidMount() {
@@ -238,54 +228,30 @@ class SearchPage extends React.Component {
     });
   };
 
-  getSelectedPages() {
-    return this.state.searchResults.filter((page) => {
-      return Array.from(this.state.selectedPagesIdList).find(id => id === page.pageData._id);
+  getSelectedPagesToDelete() {
+    const filteredPages = this.state.searchResults.filter((page) => {
+      return Array.from(this.state.deleteTargetPageIds).find(id => id === page.pageData._id);
     });
+    return filteredPages.map(page => ({
+      pageId: page.pageData._id,
+      revisionId: page.pageData.revision,
+      path: page.pageData.path,
+    }));
   }
 
-  onClickDeleteAllButton() {
-    if (this.state.selectedPagesIdList.size === 0) { return }
+  deleteSinglePageButtonHandler(pageId) {
+    this.setState({ deleteTargetPageIds: new Set([pageId]) });
     this.setState({ isDeleteConfirmModalShown: true });
   }
 
-  onCloseDeleteConfirmModal() {
-    this.setState({ isDeleteConfirmModalShown: false });
-  }
-
-  onChangeDeleteCompletely() {
-    this.setState({ isDeleteCompletely: !this.state.isDeleteCompletely });
+  deleteAllPagesButtonHandler() {
+    if (this.state.selectedPagesIdList.size === 0) { return }
+    this.setState({ deleteTargetPageIds: this.state.selectedPagesIdList });
+    this.setState({ isDeleteConfirmModalShown: true });
   }
 
-  async deleteSelectedPages() {
-    const { t } = this.props;
-    toastr.warning(t('search_result.currently_not_implemented'));
-    // const deleteCompletely = this.state.isDeleteCompletely || null;
-    try {
-
-      // const selectedPages = this.getSelectedPages();
-
-      // ************** replace these code that remove pages with code that does bulk remove **************
-      // Todo: https://redmine.weseek.co.jp/issues/82220
-      // await Promise.all(selectedPages.map(async(page) => {
-      //   const removePageParams = { page_id: page._id, revision_id: page.revision, completely: deleteCompletely };
-      //   try {
-      //     const res = await this.props.appContainer.apiPost('/pages.remove', removePageParams);
-      //     if (res.ok) { this.state.selectedPagesIdList.delete(page) }
-      //   }
-      //   catch (err) {
-      //     this.setState({ errorMessageForDeleting: err.message });
-      //     throw new Error(err.message);
-      //   }
-      // }));
-      // **************************************************************************************************
-
-      this.search({ keyword: this.state.searchedKeyword });
-      this.onCloseDeleteConfirmModal();
-    }
-    catch (err) {
-      toastr.error(err, 'Error occured', { toastrOption });
-    }
+  closeDeleteConfirmModalHandler() {
+    this.setState({ isDeleteConfirmModalShown: false });
   }
 
   renderSearchResultContent = () => {
@@ -311,6 +277,7 @@ class SearchPage extends React.Component {
         onClickSearchResultItem={this.selectPage}
         onClickCheckbox={this.toggleCheckBox}
         onPagingNumberChanged={this.onPagingNumberChanged}
+        onClickDeleteButton={this.deleteSinglePageButtonHandler}
       />
     );
   }
@@ -326,7 +293,7 @@ class SearchPage extends React.Component {
         onExcludeTrash={this.onExcludeTrash}
         onClickSelectAllCheckbox={this.toggleAllCheckBox}
         selectAllCheckboxType={this.state.selectAllCheckboxType}
-        onClickDeleteAllButton={this.onClickDeleteAllButton}
+        onClickDeleteAllButton={this.deleteAllPagesButtonHandler}
       >
       </SearchControl>
     );
@@ -345,14 +312,10 @@ class SearchPage extends React.Component {
           initialPagingLimit={this.props.appContainer.config.pageLimitationL || 50}
         >
         </SearchPageLayout>
-        <DeletePageListModal
-          isShown={this.state.isDeleteConfirmModalShown}
-          pages={this.getSelectedPages()}
-          errorMessage={this.state.errorMessageForDeleting}
-          cancel={this.onCloseDeleteConfirmModal}
-          confirmedToDelete={this.deleteSelectedPages}
-          isDeleteCompletely={this.state.isDeleteCompletely}
-          onChangeDeleteCompletely={this.onChangeDeleteCompletely}
+        <PageDeleteModal
+          isOpen={this.state.isDeleteConfirmModalShown}
+          onClose={this.closeDeleteConfirmModalHandler}
+          pages={this.getSelectedPagesToDelete()}
         />
       </div>
     );

+ 0 - 89
packages/app/src/components/SearchPage/DeletePageListModal.jsx

@@ -1,89 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { withTranslation } from 'react-i18next';
-
-import {
-  Button,
-  Modal, ModalHeader, ModalBody, ModalFooter,
-} from 'reactstrap';
-
-class DeletePageListModal extends React.Component {
-
-  /*
-   * the threshold for omitting body
-   */
-  static get OMIT_BODY_THRES() { return 400 }
-
-  render() {
-    const { t } = this.props;
-    if (this.props.pages == null || this.props.pages.length === 0) {
-      return <div></div>;
-    }
-
-    const listView = this.props.pages.map((page) => {
-      return (
-        <li key={page.pageData._id}>{page.pageData.path}</li>
-      );
-    });
-
-    return (
-      <Modal isOpen={this.props.isShown} toggle={this.props.cancel} className="page-list-delete-modal">
-        <ModalHeader tag="h4" toggle={this.props.cancel} className="bg-danger text-light">
-          {t('search_result.deletion_modal_header')}
-        </ModalHeader>
-        <ModalBody>
-          <ul>
-            {listView}
-          </ul>
-        </ModalBody>
-        <ModalFooter>
-          <div className="d-flex justify-content-between">
-            <span className="text-danger">{this.props.errorMessage}</span>
-            <span className="d-flex align-items-center">
-              <div className="custom-control custom-checkbox custom-checkbox-danger mr-2">
-                <input
-                  type="checkbox"
-                  className="custom-control-input"
-                  id="customCheck-delete-completely"
-                  checked={this.props.isDeleteCompletely}
-                  onChange={this.props.onChangeDeleteCompletely}
-                  disabled
-                />
-                <label
-                  className="custom-control-label text-danger"
-                  htmlFor="customCheck-delete-completely"
-                >
-                  {t('search_result.delete_completely')}
-                </label>
-              </div>
-              <Button color={this.props.isDeleteCompletely ? 'danger' : 'light'} onClick={this.props.confirmedToDelete}>
-                <i className="icon-trash"></i>
-                {t('search_result.delete')}
-              </Button>
-            </span>
-          </div>
-        </ModalFooter>
-      </Modal>
-    );
-  }
-
-}
-
-DeletePageListModal.defaultProps = {
-  isDeleteCompletely: false, // for when undefined is passed
-};
-
-DeletePageListModal.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-
-  isShown: PropTypes.bool.isRequired,
-  pages: PropTypes.array,
-  errorMessage: PropTypes.string,
-  cancel: PropTypes.func.isRequired, //                 for cancel evnet handling
-  isDeleteCompletely: PropTypes.bool,
-  confirmedToDelete: PropTypes.func.isRequired, //      for confirmed event handling
-  onChangeDeleteCompletely: PropTypes.func.isRequired, // for delete completely check event handling
-};
-
-export default withTranslation()(DeletePageListModal);

+ 2 - 0
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -15,6 +15,7 @@ type Props = {
   onClickSearchResultItem?: (pageId: string) => void,
   onClickCheckbox?: (pageId: string) => void,
   onClickInvoked?: (pageId: string) => void,
+  onClickDeleteButton?: (pageId: string) => void,
 }
 
 const SearchResultList: FC<Props> = (props:Props) => {
@@ -34,6 +35,7 @@ const SearchResultList: FC<Props> = (props:Props) => {
             onClickCheckbox={props.onClickCheckbox}
             isChecked={isChecked}
             isSelected={page.pageData._id === focusedPageId || false}
+            onClickDeleteButton={props.onClickDeleteButton}
           />
         );
       })}

+ 11 - 4
packages/app/src/components/SearchPage/SearchResultListItem.tsx

@@ -12,13 +12,19 @@ import { IPageHasId } from '~/interfaces/page';
 
 type PageItemControlProps = {
   page: IPageHasId,
+  onClickDeleteButton?: (pageId: string)=>void,
 }
 
-const PageItemControl: FC<PageItemControlProps> = (props: {page: IPageHasId}) => {
+const PageItemControl: FC<PageItemControlProps> = (props: PageItemControlProps) => {
 
-  const { page } = props;
+  const { page, onClickDeleteButton } = props;
   const { t } = useTranslation('');
 
+  const deleteButtonHandler = () => {
+    if (onClickDeleteButton != null) {
+      onClickDeleteButton(page._id);
+    }
+  };
   return (
     <>
       <button
@@ -47,7 +53,7 @@ const PageItemControl: FC<PageItemControlProps> = (props: {page: IPageHasId}) =>
           TODO: add function to the following buttons like using modal or others
           ref: https://estoc.weseek.co.jp/redmine/issues/79026
         */}
-        <button className="dropdown-item text-danger" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
+        <button className="dropdown-item text-danger" type="button" onClick={deleteButtonHandler}>
           <i className="icon-fw icon-fire"></i>{t('Delete')}
         </button>
         <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
@@ -71,6 +77,7 @@ type Props = {
   isChecked: boolean,
   onClickCheckbox?: (pageId: string) => void,
   onClickSearchResultItem?: (pageId: string) => void,
+  onClickDeleteButton?: (pageId: string) => void,
 }
 
 const SearchResultListItem: FC<Props> = (props:Props) => {
@@ -126,7 +133,7 @@ const SearchResultListItem: FC<Props> = (props:Props) => {
               </div>
               {/* doropdown icon includes page control buttons */}
               <div className="ml-auto">
-                <PageItemControl page={pageData} />
+                <PageItemControl page={pageData} onClickDeleteButton={props.onClickDeleteButton} />
               </div>
             </div>
             <div className="my-2">