Переглянути джерело

Merge pull request #4735 from weseek/feat/77525-80678-delete-selected-pages

Feat/77525 80678 delete selected pages
Yuki Takei 4 роки тому
батько
коміт
b0d2c09475

+ 2 - 1
packages/app/resource/locales/en_US/translation.json

@@ -576,7 +576,8 @@
     "deletion_modal_header": "Delete page",
     "deletion_modal_header": "Delete page",
     "delete_completely": "Delete completely",
     "delete_completely": "Delete completely",
     "include_certain_path" : "Include {{pathToInclude}} path ",
     "include_certain_path" : "Include {{pathToInclude}} path ",
-    "delete_all_selected_page" : "Delete All"
+    "delete_all_selected_page" : "Delete All",
+    "currently_not_implemented":"This is not currently implemented"
   },
   },
   "security_setting": {
   "security_setting": {
     "Guest Users Access": "Guest users access",
     "Guest Users Access": "Guest users access",

+ 2 - 1
packages/app/resource/locales/ja_JP/translation.json

@@ -576,7 +576,8 @@
     "deletion_modal_header": "以下のページを削除",
     "deletion_modal_header": "以下のページを削除",
     "delete_completely": "完全に削除する",
     "delete_completely": "完全に削除する",
     "include_certain_path": "{{pathToInclude}}下を含む ",
     "include_certain_path": "{{pathToInclude}}下を含む ",
-    "delete_all_selected_page" : "一括削除"
+    "delete_all_selected_page" : "一括削除",
+    "currently_not_implemented":"現在未実装の機能です"
   },
   },
   "security_setting": {
   "security_setting": {
     "Guest Users Access": "ゲストユーザーのアクセス",
     "Guest Users Access": "ゲストユーザーのアクセス",

+ 2 - 1
packages/app/resource/locales/zh_CN/translation.json

@@ -848,7 +848,8 @@
 		"deletion_modal_header": "删除页",
 		"deletion_modal_header": "删除页",
 		"delete_completely": "完全删除",
 		"delete_completely": "完全删除",
     "include_certain_path": "包含 {{pathToInclude}} 路径 ",
     "include_certain_path": "包含 {{pathToInclude}} 路径 ",
-    "delete_all_selected_page": "删除所有"
+    "delete_all_selected_page": "删除所有",
+    "currently_not_implemented": "这是当前未实现的功能"
 	},
 	},
 	"to_cloud_settings": "進入 GROWI.cloud 的管理界面",
 	"to_cloud_settings": "進入 GROWI.cloud 的管理界面",
 	"login": {
 	"login": {

+ 75 - 0
packages/app/src/components/SearchPage.jsx

@@ -3,6 +3,7 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
 
 
 import { withUnstatedContainers } from './UnstatedUtils';
 import { withUnstatedContainers } from './UnstatedUtils';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
@@ -12,6 +13,7 @@ import SearchPageLayout from './SearchPage/SearchPageLayout';
 import SearchResultContent from './SearchPage/SearchResultContent';
 import SearchResultContent from './SearchPage/SearchResultContent';
 import SearchResultList from './SearchPage/SearchResultList';
 import SearchResultList from './SearchPage/SearchResultList';
 import SearchControl from './SearchPage/SearchControl';
 import SearchControl from './SearchPage/SearchControl';
+import DeletePageListModal from './SearchPage/DeletePageListModal';
 
 
 import { CheckboxType } from '../interfaces/search';
 import { CheckboxType } from '../interfaces/search';
 
 
@@ -20,6 +22,15 @@ export const specificPathNames = {
   trash: '/trash',
   trash: '/trash',
 };
 };
 
 
+const toastrOption = {
+  closeButton: true,
+  progressBar: true,
+  newestOnTop: false,
+  showDuration: '100',
+  hideDuration: '100',
+  timeOut: '3000',
+};
+
 class SearchPage extends React.Component {
 class SearchPage extends React.Component {
 
 
   constructor(props) {
   constructor(props) {
@@ -50,6 +61,10 @@ class SearchPage extends React.Component {
     this.onExcludeUsersHome = this.onExcludeUsersHome.bind(this);
     this.onExcludeUsersHome = this.onExcludeUsersHome.bind(this);
     this.onExcludeTrash = this.onExcludeTrash.bind(this);
     this.onExcludeTrash = this.onExcludeTrash.bind(this);
     this.onPagingNumberChanged = this.onPagingNumberChanged.bind(this);
     this.onPagingNumberChanged = this.onPagingNumberChanged.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);
   }
   }
 
 
   componentDidMount() {
   componentDidMount() {
@@ -220,6 +235,56 @@ class SearchPage extends React.Component {
     });
     });
   };
   };
 
 
+  getSelectedPages() {
+    return this.state.searchedPages.filter((page) => {
+      return Array.from(this.state.selectedPagesIdList).find(id => id === page.id);
+    });
+  }
+
+  onClickDeleteAllButton() {
+    if (this.state.selectedPagesIdList.size === 0) { return }
+    this.setState({ isDeleteConfirmModalShown: true });
+  }
+
+  onCloseDeleteConfirmModal() {
+    this.setState({ isDeleteConfirmModalShown: false });
+  }
+
+  onChangeDeleteCompletely() {
+    this.setState({ isDeleteCompletely: !this.state.isDeleteCompletely });
+  }
+
+  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 });
+    }
+  }
+
   renderSearchResultContent = () => {
   renderSearchResultContent = () => {
     return (
     return (
       <SearchResultContent
       <SearchResultContent
@@ -258,6 +323,7 @@ class SearchPage extends React.Component {
         onExcludeTrash={this.onExcludeTrash}
         onExcludeTrash={this.onExcludeTrash}
         onClickSelectAllCheckbox={this.toggleAllCheckBox}
         onClickSelectAllCheckbox={this.toggleAllCheckBox}
         selectAllCheckboxType={this.state.selectAllCheckboxType}
         selectAllCheckboxType={this.state.selectAllCheckboxType}
+        onClickDeleteAllButton={this.onClickDeleteAllButton}
       >
       >
       </SearchControl>
       </SearchControl>
     );
     );
@@ -274,6 +340,15 @@ class SearchPage extends React.Component {
           searchingKeyword={this.state.searchedKeyword}
           searchingKeyword={this.state.searchedKeyword}
         >
         >
         </SearchPageLayout>
         </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}
+        />
       </div>
       </div>
     );
     );
   }
   }

+ 2 - 5
packages/app/src/components/SearchPage/DeletePageListModal.jsx

@@ -15,9 +15,6 @@ class DeletePageListModal extends React.Component {
    */
    */
   static get OMIT_BODY_THRES() { return 400 }
   static get OMIT_BODY_THRES() { return 400 }
 
 
-  componentWillMount() {
-  }
-
   render() {
   render() {
     const { t } = this.props;
     const { t } = this.props;
     if (this.props.pages == null || this.props.pages.length === 0) {
     if (this.props.pages == null || this.props.pages.length === 0) {
@@ -50,7 +47,7 @@ class DeletePageListModal extends React.Component {
                   className="custom-control-input"
                   className="custom-control-input"
                   id="customCheck-delete-completely"
                   id="customCheck-delete-completely"
                   checked={this.props.isDeleteCompletely}
                   checked={this.props.isDeleteCompletely}
-                  onChange={this.props.toggleDeleteCompletely}
+                  onChange={this.props.onChangeDeleteCompletely}
                 />
                 />
                 <label
                 <label
                   className="custom-control-label text-danger"
                   className="custom-control-label text-danger"
@@ -85,7 +82,7 @@ DeletePageListModal.propTypes = {
   cancel: PropTypes.func.isRequired, //                 for cancel evnet handling
   cancel: PropTypes.func.isRequired, //                 for cancel evnet handling
   isDeleteCompletely: PropTypes.bool,
   isDeleteCompletely: PropTypes.bool,
   confirmedToDelete: PropTypes.func.isRequired, //      for confirmed event handling
   confirmedToDelete: PropTypes.func.isRequired, //      for confirmed event handling
-  toggleDeleteCompletely: PropTypes.func.isRequired, // for delete completely check event handling
+  onChangeDeleteCompletely: PropTypes.func.isRequired, // for delete completely check event handling
 };
 };
 
 
 export default withTranslation()(DeletePageListModal);
 export default withTranslation()(DeletePageListModal);

+ 6 - 7
packages/app/src/components/SearchPage/DeleteSelectedPageGroup.tsx

@@ -5,14 +5,14 @@ import { CheckboxType } from '../../interfaces/search';
 type Props = {
 type Props = {
   isSelectAllCheckboxDisabled: boolean,
   isSelectAllCheckboxDisabled: boolean,
   selectAllCheckboxType: CheckboxType,
   selectAllCheckboxType: CheckboxType,
-  onClickDeleteButton?: () => void,
+  onClickDeleteAllButton?: () => void,
   onClickSelectAllCheckbox?: (nextSelectAllCheckboxType: CheckboxType) => void,
   onClickSelectAllCheckbox?: (nextSelectAllCheckboxType: CheckboxType) => void,
 }
 }
 
 
 const DeleteSelectedPageGroup:FC<Props> = (props:Props) => {
 const DeleteSelectedPageGroup:FC<Props> = (props:Props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const {
   const {
-    onClickDeleteButton, onClickSelectAllCheckbox, selectAllCheckboxType,
+    onClickDeleteAllButton, onClickSelectAllCheckbox, selectAllCheckboxType,
   } = props;
   } = props;
 
 
   const onClickCheckbox = () => {
   const onClickCheckbox = () => {
@@ -20,7 +20,10 @@ const DeleteSelectedPageGroup:FC<Props> = (props:Props) => {
       const next = selectAllCheckboxType === CheckboxType.ALL_CHECKED ? CheckboxType.NONE_CHECKED : CheckboxType.ALL_CHECKED;
       const next = selectAllCheckboxType === CheckboxType.ALL_CHECKED ? CheckboxType.NONE_CHECKED : CheckboxType.ALL_CHECKED;
       onClickSelectAllCheckbox(next);
       onClickSelectAllCheckbox(next);
     }
     }
+  };
 
 
+  const onClickDeleteButton = () => {
+    if (onClickDeleteAllButton != null) { onClickDeleteAllButton() }
   };
   };
 
 
   return (
   return (
@@ -39,11 +42,7 @@ const DeleteSelectedPageGroup:FC<Props> = (props:Props) => {
         type="button"
         type="button"
         className="btn text-danger font-weight-light p-0 ml-3"
         className="btn text-danger font-weight-light p-0 ml-3"
         disabled={selectAllCheckboxType === CheckboxType.NONE_CHECKED}
         disabled={selectAllCheckboxType === CheckboxType.NONE_CHECKED}
-        onClick={() => {
-          if (onClickDeleteButton != null) {
-            onClickDeleteButton();
-          }
-        }}
+        onClick={onClickDeleteButton}
       >
       >
         <i className="icon-trash"></i>
         <i className="icon-trash"></i>
         {t('search_result.delete_all_selected_page')}
         {t('search_result.delete_all_selected_page')}

+ 2 - 7
packages/app/src/components/SearchPage/SearchControl.tsx

@@ -13,6 +13,7 @@ type Props = {
   onSearchInvoked: (data : any[]) => boolean,
   onSearchInvoked: (data : any[]) => boolean,
   onExcludeUsersHome?: () => void,
   onExcludeUsersHome?: () => void,
   onExcludeTrash?: () => void,
   onExcludeTrash?: () => void,
+  onClickDeleteAllButton?: () => void
   onClickSelectAllCheckbox?: (nextSelectAllCheckboxType: CheckboxType) => void,
   onClickSelectAllCheckbox?: (nextSelectAllCheckboxType: CheckboxType) => void,
 }
 }
 
 
@@ -35,12 +36,6 @@ const SearchControl: FC <Props> = (props: Props) => {
     }
     }
   };
   };
 
 
-  const onDeleteSelectedPageHandler = () => {
-    console.log('onDeleteSelectedPageHandler is called');
-    // TODO: implement this function to delete selected pages.
-    // https://estoc.weseek.co.jp/redmine/issues/77525
-  };
-
   return (
   return (
     <div className="">
     <div className="">
       <div className="search-page-input sps sps--abv">
       <div className="search-page-input sps sps--abv">
@@ -56,7 +51,7 @@ const SearchControl: FC <Props> = (props: Props) => {
         <DeleteSelectedPageGroup
         <DeleteSelectedPageGroup
           isSelectAllCheckboxDisabled={searchResultCount === 0}
           isSelectAllCheckboxDisabled={searchResultCount === 0}
           selectAllCheckboxType={props.selectAllCheckboxType}
           selectAllCheckboxType={props.selectAllCheckboxType}
-          onClickDeleteButton={onDeleteSelectedPageHandler}
+          onClickDeleteAllButton={props.onClickDeleteAllButton}
           onClickSelectAllCheckbox={props.onClickSelectAllCheckbox}
           onClickSelectAllCheckbox={props.onClickSelectAllCheckbox}
         />
         />
         <div className="d-flex align-items-center border rounded border-gray px-2 py-1 mr-2 ml-auto">
         <div className="d-flex align-items-center border rounded border-gray px-2 py-1 mr-2 ml-auto">

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

@@ -1,6 +1,7 @@
 import React, { FC } from 'react';
 import React, { FC } from 'react';
 
 
 import Clamp from 'react-multiline-clamp';
 import Clamp from 'react-multiline-clamp';
+import toastr from 'toastr';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
@@ -48,16 +49,16 @@ const PageItemControl: FC<PageItemControlProps> = (props: {page: ISearchedPage})
           TODO: add function to the following buttons like using modal or others
           TODO: add function to the following buttons like using modal or others
           ref: https://estoc.weseek.co.jp/redmine/issues/79026
           ref: https://estoc.weseek.co.jp/redmine/issues/79026
         */}
         */}
-        <button className="dropdown-item text-danger" type="button" onClick={() => console.log('delete modal show')}>
+        <button className="dropdown-item text-danger" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
           <i className="icon-fw icon-fire"></i>{t('Delete')}
           <i className="icon-fw icon-fire"></i>{t('Delete')}
         </button>
         </button>
-        <button className="dropdown-item" type="button" onClick={() => console.log('duplicate modal show')}>
+        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
           <i className="icon-fw icon-star"></i>{t('Add to bookmark')}
           <i className="icon-fw icon-star"></i>{t('Add to bookmark')}
         </button>
         </button>
-        <button className="dropdown-item" type="button" onClick={() => console.log('duplicate modal show')}>
+        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
           <i className="icon-fw icon-docs"></i>{t('Duplicate')}
           <i className="icon-fw icon-docs"></i>{t('Duplicate')}
         </button>
         </button>
-        <button className="dropdown-item" type="button" onClick={() => console.log('rename function will be added')}>
+        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
           <i className="icon-fw  icon-action-redo"></i>{t('Move/Rename')}
           <i className="icon-fw  icon-action-redo"></i>{t('Move/Rename')}
         </button>
         </button>
       </div>
       </div>