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

Revert "Merge branch 'master' into feat/87970-list-child-groups"

This reverts commit 474ff8637aa7e37216800feac6486ca734f355b0, reversing
changes made to 3101d28e7e326d491f21b72d6b592b22995e5f32.
Shun Miyazawa 4 лет назад
Родитель
Сommit
bd1af0e88d
38 измененных файлов с 867 добавлено и 1118 удалено
  1. 1 1
      .github/workflows/reusable-app-prod.yml
  2. 2 2
      packages/app/src/client/app.jsx
  3. 0 13
      packages/app/src/client/interfaces/selectable-all.ts
  4. 1 0
      packages/app/src/components/IdenticalPathPage.tsx
  5. 1 1
      packages/app/src/components/Page/RevisionLoader.jsx
  6. 5 10
      packages/app/src/components/Page/RevisionRenderer.jsx
  7. 16 5
      packages/app/src/components/PageDeleteModal.tsx
  8. 14 40
      packages/app/src/components/PageList/PageListItemL.tsx
  9. 1 1
      packages/app/src/components/PaginationWrapper.tsx
  10. 396 0
      packages/app/src/components/SearchPage.jsx
  11. 0 260
      packages/app/src/components/SearchPage.tsx
  12. 64 0
      packages/app/src/components/SearchPage/DeleteSelectedPageGroup.tsx
  13. 42 0
      packages/app/src/components/SearchPage/IncludeSpecificPathButton.jsx
  14. 0 74
      packages/app/src/components/SearchPage/OperateAllControl.tsx
  15. 91 72
      packages/app/src/components/SearchPage/SearchControl.tsx
  16. 23 22
      packages/app/src/components/SearchPage/SearchOptionModal.tsx
  17. 79 0
      packages/app/src/components/SearchPage/SearchPageLayout.tsx
  18. 10 8
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  19. 39 53
      packages/app/src/components/SearchPage/SearchResultList.tsx
  20. 7 9
      packages/app/src/components/SearchPage/SortControl.tsx
  21. 0 166
      packages/app/src/components/SearchPage2/SearchPageBase.tsx
  22. 5 10
      packages/app/src/components/Sidebar/PageTree/Item.tsx
  23. 3 3
      packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx
  24. 13 9
      packages/app/src/interfaces/search.ts
  25. 12 2
      packages/app/src/server/interfaces/search.ts
  26. 6 8
      packages/app/src/server/service/search-delegator/elasticsearch.ts
  27. 2 4
      packages/app/src/server/service/search-delegator/private-legacy-pages.ts
  28. 6 4
      packages/app/src/server/service/search.ts
  29. 3 25
      packages/app/src/stores/modal.tsx
  30. 0 92
      packages/app/src/stores/search.tsx
  31. 24 10
      packages/app/src/styles/_search.scss
  32. 0 4
      packages/app/src/styles/atoms/_custom_control.scss
  33. 0 0
      packages/app/test/cypress/integration/2-advanced-examples/misc.spec.ts
  34. 0 0
      packages/app/test/cypress/integration/2-advanced-examples/viewport.spec.ts
  35. 0 103
      packages/app/test/cypress/integration/2-basic-features/access-to-admin-page.spec.ts
  36. 0 28
      packages/app/test/cypress/integration/2-basic-features/access-to-me-page.spec.ts
  37. 1 31
      packages/app/test/cypress/integration/2-basic-features/access-to-page.spec.ts
  38. 0 48
      packages/app/test/cypress/integration/3-search/access-to-result-page-directly.spec.ts

+ 1 - 1
.github/workflows/reusable-app-prod.yml

@@ -187,7 +187,7 @@ jobs:
       fail-fast: false
       matrix:
         # List string expressions that is comma separated ids of tests in "test/cypress/integration"
-        spec-group: ['1', '2', '3']
+        spec-group: ['1', '2']
 
     services:
       mongodb:

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

@@ -13,7 +13,7 @@ import { swrGlobalConfiguration } from '~/utils/swr-utils';
 import InAppNotificationPage from '../components/InAppNotification/InAppNotificationPage';
 import ErrorBoundary from '../components/ErrorBoudary';
 import Sidebar from '../components/Sidebar';
-import { SearchPage } from '../components/SearchPage';
+import SearchPage from '../components/SearchPage';
 import TagsList from '../components/TagsList';
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
 import { defaultEditorOptions, defaultPreviewOptions } from '../components/PageEditor/OptionsSelector';
@@ -85,7 +85,7 @@ logger.info('unstated containers have been initialized');
 Object.assign(componentMappings, {
   'grw-sidebar-wrapper': <Sidebar />,
 
-  'search-page': <SearchPage appContainer={appContainer} />,
+  'search-page': <SearchPage crowi={appContainer} />,
   'all-in-app-notifications': <InAppNotificationPage />,
   'identical-path-page': <IdenticalPathPage />,
 

+ 0 - 13
packages/app/src/client/interfaces/selectable-all.ts

@@ -1,13 +0,0 @@
-export interface ISelectable {
-  select: () => void,
-  deselect: () => void,
-}
-
-export interface ISelectableAndIndeterminatable extends ISelectable {
-  setIndeterminate: () => void,
-}
-
-export interface ISelectableAll {
-  selectAll: () => void,
-  deselectAll: () => void,
-}

+ 1 - 0
packages/app/src/components/IdenticalPathPage.tsx

@@ -109,6 +109,7 @@ const IdenticalPathPage:FC<IdenticalPathPageProps> = (props: IdenticalPathPagePr
                   key={pageId}
                   page={pageWithMeta}
                   isSelected={false}
+                  isChecked={false}
                   isEnableActions
                   showPageUpdatedTime
                 // Todo: add onClickDeleteButton when delete feature implemented

+ 1 - 1
packages/app/src/components/Page/RevisionLoader.jsx

@@ -126,7 +126,7 @@ LegacyRevisionLoader.propTypes = {
   revisionId: PropTypes.string.isRequired,
   lazy: PropTypes.bool,
   onRevisionLoaded: PropTypes.func,
-  highlightKeywords: PropTypes.arrayOf(PropTypes.string),
+  highlightKeywords: PropTypes.string,
 };
 
 const RevisionLoader = (props) => {

+ 5 - 10
packages/app/src/components/Page/RevisionRenderer.jsx

@@ -62,18 +62,13 @@ class LegacyRevisionRenderer extends React.PureComponent {
    * @param {string} body html strings
    * @param {string} keywords
    */
-  getHighlightedBody(body, _keywords) {
-    const keywords = Array.isArray(_keywords)
-      ? _keywords
-      : [_keywords];
-
+  getHighlightedBody(body, keywords) {
     const normalizedKeywordsArray = [];
     // !!TODO!!: add test code refs: https://redmine.weseek.co.jp/issues/86841
     // Separate keywords
     // - Surrounded by double quotation
     // - Split by both full-width and half-width spaces
-    // [...keywords.match(/"[^"]+"|[^\u{20}\u{3000}]+/ug)].forEach((keyword, i) => {
-    keywords.forEach((keyword, i) => {
+    [...keywords.match(/"[^"]+"|[^\u{20}\u{3000}]+/ug)].forEach((keyword, i) => {
       if (keyword === '') {
         return;
       }
@@ -143,7 +138,7 @@ class LegacyRevisionRenderer extends React.PureComponent {
     await interceptorManager.process('prePostProcess', context);
     context.parsedHTML = growiRenderer.postProcess(context.parsedHTML);
 
-    if (highlightKeywords != null && highlightKeywords.length > 0) {
+    if (highlightKeywords != null) {
       context.parsedHTML = this.getHighlightedBody(context.parsedHTML, highlightKeywords);
     }
     await interceptorManager.process('postPostProcess', context);
@@ -172,7 +167,7 @@ LegacyRevisionRenderer.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
   markdown: PropTypes.string.isRequired,
-  highlightKeywords: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
+  highlightKeywords: PropTypes.string,
   additionalClassName: PropTypes.string,
 };
 
@@ -190,7 +185,7 @@ const RevisionRenderer = (props) => {
 RevisionRenderer.propTypes = {
   growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
   markdown: PropTypes.string.isRequired,
-  highlightKeywords: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
+  highlightKeywords: PropTypes.string,
   additionalClassName: PropTypes.string,
 };
 

+ 16 - 5
packages/app/src/components/PageDeleteModal.tsx

@@ -26,14 +26,21 @@ const deleteIconAndKey = {
   },
 };
 
-const PageDeleteModal: FC = () => {
-  const { t } = useTranslation();
+type Props = {
+  isDeleteCompletelyModal: boolean,
+  isAbleToDeleteCompletely: boolean,
+  onClose?: () => void,
+}
+
+const PageDeleteModal: FC<Props> = (props: Props) => {
+  const { t } = useTranslation('');
+  const {
+    isDeleteCompletelyModal, isAbleToDeleteCompletely,
+  } = props;
 
   const { data: deleteModalData, close: closeDeleteModal } = usePageDeleteModal();
 
   const isOpened = deleteModalData?.isOpened ?? false;
-  const isAbleToDeleteCompletely = deleteModalData?.isAbleToDeleteCompletely ?? false;
-  const isDeleteCompletelyModal = deleteModalData?.isDeleteCompletelyModal ?? false;
 
   const [isDeleteRecursively, setIsDeleteRecursively] = useState(true);
   const [isDeleteCompletely, setIsDeleteCompletely] = useState(isDeleteCompletelyModal && isAbleToDeleteCompletely);
@@ -136,6 +143,7 @@ const PageDeleteModal: FC = () => {
   // DeleteCompletely is currently disabled
   // TODO1 : Retrive isAbleToDeleteCompleltly state everywhere in the system via swr.
   // Story: https://redmine.weseek.co.jp/issues/82222
+
   // TODO2 : use toaster
   // TASK : https://redmine.weseek.co.jp/issues/82299
   function renderDeleteCompletelyForm() {
@@ -146,10 +154,13 @@ const PageDeleteModal: FC = () => {
           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}
         />
+        {/* ↓↓ 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>

+ 14 - 40
packages/app/src/components/PageList/PageListItemL.tsx

@@ -1,9 +1,4 @@
-import React, {
-  forwardRef,
-  ForwardRefRenderFunction, memo, useCallback, useImperativeHandle, useRef,
-} from 'react';
-
-import { CustomInput } from 'reactstrap';
+import React, { memo, useCallback } from 'react';
 
 import Clamp from 'react-multiline-clamp';
 import { format } from 'date-fns';
@@ -21,44 +16,25 @@ import { IPageSearchMeta, isIPageSearchMeta } from '~/interfaces/search';
 import { PageItemControl } from '../Common/Dropdown/PageItemControl';
 import LinkedPagePath from '~/models/linked-page-path';
 import PagePathHierarchicalLink from '../PagePathHierarchicalLink';
-import { ISelectable } from '~/client/interfaces/selectable-all';
 
 type Props = {
   page: IPageWithMeta | IPageWithMeta<IPageInfoAll & IPageSearchMeta>,
   isSelected?: boolean, // is item selected(focused)
+  isChecked?: boolean, // is checkbox of item checked
   isEnableActions?: boolean,
   showPageUpdatedTime?: boolean, // whether to show page's updated time at the top-right corner of item
-  onCheckboxChanged?: (isChecked: boolean, pageId: string) => void,
+  onClickCheckbox?: (pageId: string) => void,
   onClickItem?: (pageId: string) => void,
   onClickDeleteButton?: (pageId: string) => void,
 }
 
-const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (props: Props, ref): JSX.Element => {
+export const PageListItemL = memo((props: Props): JSX.Element => {
   const {
     // todo: refactoring variable name to clear what changed
-    page: { pageData, pageMeta }, isSelected, isEnableActions,
+    page: { pageData, pageMeta }, isSelected, onClickItem, onClickCheckbox, isChecked, isEnableActions,
     showPageUpdatedTime,
-    onClickItem, onCheckboxChanged,
   } = props;
 
-  const inputRef = useRef<HTMLInputElement>(null);
-
-  // publish ISelectable methods
-  useImperativeHandle(ref, () => ({
-    select: () => {
-      const input = inputRef.current;
-      if (input != null) {
-        input.checked = true;
-      }
-    },
-    deselect: () => {
-      const input = inputRef.current;
-      if (input != null) {
-        input.checked = false;
-      }
-    },
-  }));
-
   const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
@@ -94,7 +70,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
     openRenameModal(pageId, revisionId as string, path);
   }, [openRenameModal, pageData]);
 
-  const styleListGroupItem = (!isDeviceSmallerThanLg && onClickItem != null) ? 'list-group-item-action' : '';
+  const styleListGroupItem = (!isDeviceSmallerThanLg && onClickCheckbox != null) ? 'list-group-item-action' : '';
   // background color of list item changes when class "active" exists under 'list-group-item'
   const styleActive = !isDeviceSmallerThanLg && isSelected ? 'active' : '';
 
@@ -109,14 +85,14 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
       >
         <div className="d-flex">
           {/* checkbox */}
-          {onCheckboxChanged != null && (
-            <div className="d-flex align-items-center justify-content-center pl-md-2 pl-3">
-              <CustomInput
+          {onClickCheckbox != null && (
+            <div className="form-check d-flex align-items-center justify-content-center px-md-2 pl-3 pr-2 search-item-checkbox">
+              <input
+                className="form-check-input position-relative m-0"
                 type="checkbox"
-                id={`cbSelect-${pageData._id}`}
-                data-testid="cb-select"
-                innerRef={inputRef}
-                onChange={(e) => { onCheckboxChanged(e.target.checked, pageData._id) }}
+                id="flexCheckDefault"
+                onChange={() => { onClickCheckbox(pageData._id) }}
+                checked={isChecked}
               />
             </div>
           )}
@@ -180,6 +156,4 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
       </div>
     </li>
   );
-};
-
-export const PageListItemL = memo(forwardRef(PageListItemLSubstance));
+});

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

@@ -7,7 +7,7 @@ import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
 
 type Props = {
   activePage: number,
-  changePage?: (activePage: number) => void,
+  changePage?: (number) => void,
   totalItemsCount: number,
   pagingLimit?: number,
   align?: string,

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

@@ -0,0 +1,396 @@
+// This is the root component for #search-page
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import {
+  DetachCodeBlockInterceptor,
+  RestoreCodeBlockInterceptor,
+} from '../client/util/interceptor/detach-code-blocks';
+
+import { withUnstatedContainers } from './UnstatedUtils';
+import AppContainer from '~/client/services/AppContainer';
+import { toastError } from '~/client/util/apiNotification';
+import SearchPageLayout from './SearchPage/SearchPageLayout';
+import SearchResultContent from './SearchPage/SearchResultContent';
+import SearchResultList from './SearchPage/SearchResultList';
+import SearchControl from './SearchPage/SearchControl';
+import { CheckboxType, SORT_AXIS, SORT_ORDER } from '~/interfaces/search';
+import PageDeleteModal from './PageDeleteModal';
+import { useIsGuestUser } from '~/stores/context';
+
+export const specificPathNames = {
+  user: '/user',
+  trash: '/trash',
+};
+class SearchPage extends React.Component {
+
+  constructor(props) {
+    super(props);
+    // NOTE : selectedPages is deletion related state, will be used later in story 77535, 77565.
+    // deletionModal, deletion related functions are all removed, add them back when necessary.
+    // i.e ) in story 77525 or any tasks implementing deletion functionalities
+    this.state = {
+      searchingKeyword: decodeURI(this.props.query.q) || '',
+      searchedKeyword: '',
+      searchResults: [],
+      searchResultMeta: {},
+      focusedSearchResultData: null,
+      selectedPagesIdList: new Set(),
+      searchResultCount: 0,
+      activePage: 1,
+      pagingLimit: this.props.appContainer.config.pageLimitationL || 50,
+      excludeUserPages: true,
+      excludeTrashPages: true,
+      sort: SORT_AXIS.RELATION_SCORE,
+      order: SORT_ORDER.DESC,
+      selectAllCheckboxType: CheckboxType.NONE_CHECKED,
+      isDeleteConfirmModalShown: false,
+      deleteTargetPageIds: new Set(),
+    };
+
+    // TODO: Move this code to the right place after completing the "omit unstated" initiative.
+    const { interceptorManager } = props.appContainer;
+    interceptorManager.addInterceptor(new DetachCodeBlockInterceptor(props.appContainer), 10); // process as soon as possible
+    interceptorManager.addInterceptor(new RestoreCodeBlockInterceptor(props.appContainer), 900); // process as late as possible
+
+    this.changeURL = this.changeURL.bind(this);
+    this.search = this.search.bind(this);
+    this.onSearchInvoked = this.onSearchInvoked.bind(this);
+    this.selectPage = this.selectPage.bind(this);
+    this.toggleCheckBox = this.toggleCheckBox.bind(this);
+    this.switchExcludeUserPagesHandler = this.switchExcludeUserPagesHandler.bind(this);
+    this.switchExcludeTrashPagesHandler = this.switchExcludeTrashPagesHandler.bind(this);
+    this.onChangeSortInvoked = this.onChangeSortInvoked.bind(this);
+    this.onPagingNumberChanged = this.onPagingNumberChanged.bind(this);
+    this.onPagingLimitChanged = this.onPagingLimitChanged.bind(this);
+    this.deleteSinglePageButtonHandler = this.deleteSinglePageButtonHandler.bind(this);
+    this.deleteAllPagesButtonHandler = this.deleteAllPagesButtonHandler.bind(this);
+    this.closeDeleteConfirmModalHandler = this.closeDeleteConfirmModalHandler.bind(this);
+  }
+
+  componentDidMount() {
+    const keyword = this.state.searchingKeyword;
+    if (keyword !== '') {
+      this.search({ keyword });
+    }
+  }
+
+  static getQueryByLocation(location) {
+    const search = location.search || '';
+    const query = {};
+
+    search.replace(/^\?/, '').split('&').forEach((element) => {
+      const queryParts = element.split('=');
+      query[queryParts[0]] = decodeURIComponent(queryParts[1]).replace(/\+/g, ' ');
+    });
+
+    return query;
+  }
+
+  switchExcludeUserPagesHandler() {
+    this.setState({ excludeUserPages: !this.state.excludeUserPages });
+  }
+
+  switchExcludeTrashPagesHandler() {
+    this.setState({ excludeTrashPages: !this.state.excludeTrashPages });
+  }
+
+  onChangeSortInvoked(nextSort, nextOrder) {
+    this.setState({
+      sort: nextSort,
+      order: nextOrder,
+    });
+  }
+
+  changeURL(keyword, refreshHash) {
+    let hash = window.location.hash || '';
+    // TODO 整理する
+    if (refreshHash || this.state.searchedKeyword !== '') {
+      hash = '';
+    }
+    if (window.history && window.history.pushState) {
+      window.history.pushState('', `Search - ${keyword}`, `/_search?q=${keyword}${hash}`);
+    }
+  }
+
+  createSearchQuery(keyword) {
+    let query = keyword;
+
+    // pages included in specific path are not retrived when prefix is added
+    if (this.state.excludeTrashPages) {
+      query = `${query} -prefix:${specificPathNames.trash}`;
+    }
+    if (this.state.excludeUserPages) {
+      query = `${query} -prefix:${specificPathNames.user}`;
+    }
+
+    return query;
+  }
+
+  /**
+   * this method is called when user changes paging number
+   */
+  async onPagingNumberChanged(activePage) {
+    this.setState({ activePage }, () => this.search({ keyword: this.state.searchedKeyword }));
+  }
+
+  /**
+   * this method is called when user searches by pressing Enter or using searchbox
+   */
+  async onSearchInvoked(data) {
+    this.setState({ activePage: 1 }, () => this.search(data));
+  }
+
+  /**
+   * change number of pages to display per page and execute search method after.
+   */
+  async onPagingLimitChanged(limit) {
+    this.setState({ pagingLimit: limit }, () => this.search({ keyword: this.state.searchedKeyword }));
+  }
+
+  // todo: refactoring
+  // refs: https://redmine.weseek.co.jp/issues/82139
+  async search(data) {
+    // reset following states when search runs
+    this.setState({
+      selectedPagesIdList: new Set(),
+      selectAllCheckboxType: CheckboxType.NONE_CHECKED,
+    });
+
+    const keyword = data.keyword;
+    if (keyword === '') {
+      this.setState({
+        searchingKeyword: '',
+        searchedKeyword: '',
+        searchResults: [],
+        searchResultMeta: {},
+        searchResultCount: 0,
+        activePage: 1,
+      });
+
+      return true;
+    }
+
+    this.setState({
+      searchingKeyword: keyword,
+    });
+    const pagingLimit = this.state.pagingLimit;
+    const offset = (this.state.activePage * pagingLimit) - pagingLimit;
+    const { sort, order } = this.state;
+    try {
+      const res = await this.props.appContainer.apiGet('/search', {
+        q: this.createSearchQuery(keyword),
+        limit: pagingLimit,
+        offset,
+        sort,
+        order,
+      });
+
+      this.changeURL(keyword);
+      if (res.data.length > 0) {
+        this.setState({
+          searchedKeyword: keyword,
+          searchResults: res.data,
+          searchResultMeta: res.meta,
+          searchResultCount: res.meta.total,
+          focusedSearchResultData: res.data[0],
+          // reset active page if keyword changes, otherwise set the current state
+          activePage: this.state.searchedKeyword === keyword ? this.state.activePage : 1,
+        });
+      }
+      else {
+        this.setState({
+          searchedKeyword: keyword,
+          searchResults: [],
+          searchResultMeta: {},
+          searchResultCount: 0,
+          focusedSearchResultData: {},
+          activePage: 1,
+        });
+      }
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
+  selectPage= (pageId) => {
+    const index = this.state.searchResults.findIndex(({ pageData }) => {
+      return pageData._id === pageId;
+    });
+    this.setState({
+      focusedSearchResultData: this.state.searchResults[index],
+    });
+  }
+
+  toggleCheckBox = (pageId) => {
+    const { selectedPagesIdList } = this.state;
+
+    if (selectedPagesIdList.has(pageId)) {
+      selectedPagesIdList.delete(pageId);
+    }
+    else {
+      selectedPagesIdList.add(pageId);
+    }
+    switch (selectedPagesIdList.size) {
+      case 0:
+        return this.setState({ selectAllCheckboxType: CheckboxType.NONE_CHECKED });
+      case this.state.searchResults.length:
+        return this.setState({ selectAllCheckboxType: CheckboxType.ALL_CHECKED });
+      default:
+        return this.setState({ selectAllCheckboxType: CheckboxType.INDETERMINATE });
+    }
+  }
+
+  toggleAllCheckBox = (nextSelectAllCheckboxType) => {
+    const { selectedPagesIdList, searchResults } = this.state;
+    if (nextSelectAllCheckboxType === CheckboxType.NONE_CHECKED) {
+      selectedPagesIdList.clear();
+    }
+    else {
+      searchResults.forEach((page) => {
+        selectedPagesIdList.add(page.pageData._id);
+      });
+    }
+    this.setState({
+      selectedPagesIdList,
+      selectAllCheckboxType: nextSelectAllCheckboxType,
+    });
+  };
+
+  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,
+    }));
+  }
+
+  deleteSinglePageButtonHandler(pageId) {
+    this.setState({ deleteTargetPageIds: new Set([pageId]) });
+    this.setState({ isDeleteConfirmModalShown: true });
+  }
+
+  deleteAllPagesButtonHandler() {
+    if (this.state.selectedPagesIdList.size === 0) { return }
+    this.setState({ deleteTargetPageIds: this.state.selectedPagesIdList });
+    this.setState({ isDeleteConfirmModalShown: true });
+  }
+
+  closeDeleteConfirmModalHandler() {
+    this.setState({ isDeleteConfirmModalShown: false });
+  }
+
+  renderSearchResultContent = () => {
+    return (
+      <SearchResultContent
+        appContainer={this.props.appContainer}
+        searchingKeyword={this.state.searchingKeyword}
+        focusedSearchResultData={this.state.focusedSearchResultData}
+        showPageControlDropdown={!this.props.isGuestUser}
+      >
+      </SearchResultContent>
+    );
+  }
+
+  renderSearchResultList = () => {
+    return (
+      <SearchResultList
+        pages={this.state.searchResults || []}
+        isEnableActions={!this.props.isGuestUser}
+        focusedSearchResultData={this.state.focusedSearchResultData}
+        selectedPagesIdList={this.state.selectedPagesIdList || []}
+        searchResultCount={this.state.searchResultCount}
+        activePage={this.state.activePage}
+        pagingLimit={this.state.pagingLimit}
+        onClickItem={this.selectPage}
+        onClickCheckbox={this.toggleCheckBox}
+        onPagingNumberChanged={this.onPagingNumberChanged}
+        onClickDeleteButton={this.deleteSinglePageButtonHandler}
+      />
+    );
+  }
+
+  renderSearchControl = () => {
+    return (
+      <SearchControl
+        searchingKeyword={this.state.searchingKeyword}
+        sort={this.state.sort}
+        order={this.state.order}
+        searchResultCount={this.state.searchResultCount || 0}
+        appContainer={this.props.appContainer}
+        onSearchInvoked={this.onSearchInvoked}
+        onClickSelectAllCheckbox={this.toggleAllCheckBox}
+        selectAllCheckboxType={this.state.selectAllCheckboxType}
+        onClickDeleteAllButton={this.deleteAllPagesButtonHandler}
+        onExcludeUserPagesSwitched={this.switchExcludeUserPagesHandler}
+        onExcludeTrashPagesSwitched={this.switchExcludeTrashPagesHandler}
+        excludeUserPages={this.state.excludeUserPages}
+        excludeTrashPages={this.state.excludeTrashPages}
+        onChangeSortInvoked={this.onChangeSortInvoked}
+      >
+      </SearchControl>
+    );
+  }
+
+  render() {
+    return (
+      <div>
+        <SearchPageLayout
+          SearchControl={this.renderSearchControl}
+          SearchResultList={this.renderSearchResultList}
+          SearchResultContent={this.renderSearchResultContent}
+          searchResultMeta={this.state.searchResultMeta}
+          searchingKeyword={this.state.searchedKeyword}
+          onPagingLimitChanged={this.onPagingLimitChanged}
+          pagingLimit={this.state.pagingLimit}
+          activePage={this.state.activePage}
+        >
+        </SearchPageLayout>
+        {/* TODO: show PageDeleteModal with usePageDeleteModal by 87569  */}
+        <PageDeleteModal
+          isOpen={this.state.isDeleteConfirmModalShown}
+          onClose={this.closeDeleteConfirmModalHandler}
+          pages={this.getSelectedPagesToDelete()}
+        />
+      </div>
+    );
+  }
+
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const SearchPageHOCWrapper = withTranslation()(withUnstatedContainers(SearchPage, [AppContainer]));
+
+SearchPage.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  query: PropTypes.object,
+  isGuestUser: PropTypes.bool.isRequired,
+};
+SearchPage.defaultProps = {
+  // pollInterval: 1000,
+  query: SearchPage.getQueryByLocation(window.location || {}),
+};
+
+const SearchPageFCWrapper = (props) => {
+  const { data: isGuestUser } = useIsGuestUser();
+
+  /*
+   * dependencies
+   */
+  if (isGuestUser == null) {
+    return null;
+  }
+
+  return <SearchPageHOCWrapper {...props} isGuestUser={isGuestUser} />;
+};
+
+export default SearchPageFCWrapper;

+ 0 - 260
packages/app/src/components/SearchPage.tsx

@@ -1,260 +0,0 @@
-import React, {
-  useCallback, useEffect, useMemo, useRef, useState,
-} from 'react';
-import { useTranslation } from 'react-i18next';
-
-import { parse as parseQuerystring } from 'querystring';
-
-import AppContainer from '~/client/services/AppContainer';
-import { IFormattedSearchResult } from '~/interfaces/search';
-import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
-import { ISearchConditions, ISearchConfigurations, useSWRxFullTextSearch } from '~/stores/search';
-
-import PaginationWrapper from './PaginationWrapper';
-import { OperateAllControl } from './SearchPage/OperateAllControl';
-import SearchControl from './SearchPage/SearchControl';
-
-import { SearchPageBase } from './SearchPage2/SearchPageBase';
-
-
-// TODO: replace with "customize:showPageLimitationS"
-const INITIAL_PAGIONG_SIZE = 20;
-
-
-/**
- * SearchResultListHead
- */
-
-type SearchResultListHeadProps = {
-  searchResult: IFormattedSearchResult,
-  searchingKeyword: string,
-  offset: number,
-  pagingSize: number,
-  onPagingSizeChanged: (size: number) => void,
-}
-
-const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.Element => {
-  const { t } = useTranslation();
-
-  const {
-    searchResult, searchingKeyword, offset, pagingSize,
-    onPagingSizeChanged,
-  } = props;
-
-  const { took, total, hitsCount } = searchResult.meta;
-  const leftNum = offset + 1;
-  const rightNum = offset + hitsCount;
-
-  return (
-    <div className="d-flex align-items-center justify-content-between">
-      <div className="text-nowrap">
-        {t('search_result.result_meta')}
-        <span className="search-result-keyword">{`"${searchingKeyword}"`}</span>
-        <span className="ml-3">{`${leftNum}-${rightNum}`} / {total}</span>
-        { took != null && (
-          <span className="ml-3 text-muted">({took}ms)</span>
-        ) }
-      </div>
-      <div className="input-group search-result-select-group ml-4 d-lg-flex d-none">
-        <div className="input-group-prepend">
-          <label className="input-group-text text-muted" htmlFor="inputGroupSelect01">{t('search_result.number_of_list_to_display')}</label>
-        </div>
-        <select
-          defaultValue={pagingSize}
-          className="custom-select"
-          id="inputGroupSelect01"
-          onChange={e => onPagingSizeChanged(Number(e.target.value))}
-        >
-          {[20, 50, 100, 200].map((limit) => {
-            return <option key={limit} value={limit}>{limit} {t('search_result.page_number_unit')}</option>;
-          })}
-        </select>
-      </div>
-    </div>
-  );
-});
-
-
-/**
- * SearchPage
- */
-
-const getParsedUrlQuery = () => {
-  const search = window.location.search || '?';
-  return parseQuerystring(search.slice(1)); // remove heading '?' and parse
-};
-
-type Props = {
-  appContainer: AppContainer,
-}
-
-export const SearchPage = (props: Props): JSX.Element => {
-  const { t } = useTranslation();
-
-  const {
-    appContainer,
-  } = props;
-
-  // parse URL Query
-  const parsedQueries = getParsedUrlQuery().q;
-  const initQ = (Array.isArray(parsedQueries) ? parsedQueries.join(' ') : parsedQueries) ?? '';
-
-  const [keyword, setKeyword] = useState<string>(initQ);
-  const [configurationsByControl, setConfigurationsByControl] = useState<Partial<ISearchConfigurations>>({
-  });
-  const [configurationsByPagination, setConfigurationsByPagination] = useState<Partial<ISearchConfigurations>>({
-    limit: INITIAL_PAGIONG_SIZE,
-  });
-
-  const selectAllControlRef = useRef<ISelectableAndIndeterminatable|null>(null);
-  const searchPageBaseRef = useRef<ISelectableAll|null>(null);
-
-  const { data, conditions } = useSWRxFullTextSearch(keyword, {
-    limit: INITIAL_PAGIONG_SIZE,
-    ...configurationsByControl,
-    ...configurationsByPagination,
-  });
-
-  const searchInvokedHandler = useCallback((_keyword: string, newConfigurations: Partial<ISearchConfigurations>) => {
-    setKeyword(_keyword);
-    setConfigurationsByControl(newConfigurations);
-  }, []);
-
-  const selectAllCheckboxChangedHandler = useCallback((isChecked: boolean) => {
-    const instance = searchPageBaseRef.current;
-
-    if (instance == null) {
-      return;
-    }
-
-    if (isChecked) {
-      instance.selectAll();
-    }
-    else {
-      instance.deselectAll();
-    }
-  }, []);
-
-  const selectedPagesByCheckboxesChangedHandler = useCallback((selectedCount: number, totalCount: number) => {
-    const instance = selectAllControlRef.current;
-
-    if (instance == null) {
-      return;
-    }
-
-    if (selectedCount === 0) {
-      instance.deselect();
-    }
-    else if (selectedCount === totalCount) {
-      instance.select();
-    }
-    else {
-      instance.setIndeterminate();
-    }
-  }, []);
-
-  const pagingNumberChangedHandler = useCallback((activePage: number) => {
-    const currentLimit = configurationsByPagination.limit ?? INITIAL_PAGIONG_SIZE;
-    setConfigurationsByPagination({
-      ...configurationsByPagination,
-      offset: (activePage - 1) * currentLimit,
-    });
-  }, [configurationsByPagination]);
-
-  const initialSearchConditions: Partial<ISearchConditions> = useMemo(() => {
-    return {
-      keyword: initQ,
-      limit: INITIAL_PAGIONG_SIZE,
-    };
-  }, [initQ]);
-
-  // push state
-  useEffect(() => {
-    const newUrl = new URL('/_search', 'http://example.com');
-    newUrl.searchParams.append('q', keyword);
-    window.history.pushState('', `Search - ${keyword}`, `${newUrl.pathname}${newUrl.search}`);
-  }, [keyword]);
-  const hitsCount = data?.meta.hitsCount;
-
-  const { offset, limit } = conditions;
-
-  const deleteAllControl = useMemo(() => {
-    const isDisabled = hitsCount === 0;
-
-    return (
-      <OperateAllControl
-        ref={selectAllControlRef}
-        isCheckboxDisabled={isDisabled}
-        onCheckboxChanged={selectAllCheckboxChangedHandler}
-      >
-        <button
-          type="button"
-          className="btn btn-outline-danger border-0 px-2"
-          disabled={isDisabled}
-          onClick={() => null /* TODO implement */}
-        >
-          <i className="icon-fw icon-trash"></i>
-          {t('search_result.delete_all_selected_page')}
-        </button>
-      </OperateAllControl>
-    );
-  }, [hitsCount, selectAllCheckboxChangedHandler, t]);
-
-  const searchControl = useMemo(() => {
-    return (
-      <SearchControl
-        initialSearchConditions={initialSearchConditions}
-        onSearchInvoked={searchInvokedHandler}
-        deleteAllControl={deleteAllControl}
-      >
-      </SearchControl>
-    );
-  }, [deleteAllControl, initialSearchConditions, searchInvokedHandler]);
-
-  const searchResultListHead = useMemo(() => {
-    if (data == null) {
-      return <></>;
-    }
-    return (
-      <SearchResultListHead
-        searchResult={data}
-        searchingKeyword={keyword}
-        offset={offset}
-        pagingSize={limit}
-        onPagingSizeChanged={() => {}}
-      />
-    );
-  }, [data, keyword, limit, offset]);
-
-  const searchPager = useMemo(() => {
-    // when pager is not needed
-    if (data == null || data.meta.hitsCount === data.meta.total) {
-      return <></>;
-    }
-
-    const { total } = data.meta;
-    const { offset, limit } = conditions;
-
-    return (
-      <PaginationWrapper
-        activePage={Math.floor(offset / limit) + 1}
-        totalItemsCount={total}
-        pagingLimit={configurationsByPagination?.limit}
-        changePage={pagingNumberChangedHandler}
-      />
-    );
-  }, [conditions, configurationsByPagination?.limit, data, pagingNumberChangedHandler]);
-
-  return (
-    <SearchPageBase
-      ref={searchPageBaseRef}
-      appContainer={appContainer}
-      pages={data?.data}
-      onSelectedPagesByCheckboxesChanged={selectedPagesByCheckboxesChangedHandler}
-      // Components
-      searchControl={searchControl}
-      searchResultListHead={searchResultListHead}
-      searchPager={searchPager}
-    />
-  );
-};

+ 64 - 0
packages/app/src/components/SearchPage/DeleteSelectedPageGroup.tsx

@@ -0,0 +1,64 @@
+import React, { FC, useEffect, useRef } from 'react';
+import { useTranslation } from 'react-i18next';
+import { IndeterminateInputElement } from '~/interfaces/indeterminate-input-elm';
+import { CheckboxType } from '../../interfaces/search';
+
+type Props = {
+  isSelectAllCheckboxDisabled: boolean,
+  selectAllCheckboxType: CheckboxType,
+  onClickDeleteAllButton?: () => void,
+  onClickSelectAllCheckbox?: (nextSelectAllCheckboxType: CheckboxType) => void,
+}
+
+const DeleteSelectedPageGroup:FC<Props> = (props:Props) => {
+  const { t } = useTranslation();
+  const {
+    onClickDeleteAllButton, onClickSelectAllCheckbox, selectAllCheckboxType,
+  } = props;
+
+  const onClickCheckbox = () => {
+    if (onClickSelectAllCheckbox != null) {
+      const next = selectAllCheckboxType === CheckboxType.ALL_CHECKED ? CheckboxType.NONE_CHECKED : CheckboxType.ALL_CHECKED;
+      onClickSelectAllCheckbox(next);
+    }
+  };
+
+  const onClickDeleteButton = () => {
+    if (onClickDeleteAllButton != null) { onClickDeleteAllButton() }
+  };
+
+  const selectAllCheckboxElm = useRef<IndeterminateInputElement>(null);
+  useEffect(() => {
+    if (selectAllCheckboxElm.current != null) {
+      selectAllCheckboxElm.current.indeterminate = selectAllCheckboxType === CheckboxType.INDETERMINATE;
+    }
+  }, [selectAllCheckboxType]);
+
+  return (
+
+    <div className="d-flex align-items-center">
+      <input
+        id="check-all-pages"
+        type="checkbox"
+        name="check-all-pages"
+        className="grw-indeterminate-checkbox"
+        ref={selectAllCheckboxElm}
+        disabled={props.isSelectAllCheckboxDisabled}
+        onClick={onClickCheckbox}
+        checked={selectAllCheckboxType === CheckboxType.ALL_CHECKED}
+      />
+      <button
+        type="button"
+        className="btn text-danger font-weight-light p-0 ml-2"
+        disabled={selectAllCheckboxType === CheckboxType.NONE_CHECKED}
+        onClick={onClickDeleteButton}
+      >
+        <i className="icon-trash"></i>
+        {t('search_result.delete_all_selected_page')}
+      </button>
+    </div>
+  );
+
+};
+
+export default DeleteSelectedPageGroup;

+ 42 - 0
packages/app/src/components/SearchPage/IncludeSpecificPathButton.jsx

@@ -0,0 +1,42 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
+
+const IncludeSpecificPathButton = (props) => {
+  const { pathToInclude, checked } = props;
+  const { t } = useTranslation();
+
+  // TODO : implement this function
+  // 77526 story https://estoc.weseek.co.jp/redmine/issues/77526
+  // 77535 stroy https://estoc.weseek.co.jp/redmine/issues/77535
+  function includeSpecificPathInSearchResult(pathToInclude) {
+    console.log(`now including ${pathToInclude} in search result`);
+  }
+  return (
+    <div className="border px-2 btn btn-outline-secondary">
+      <label className="mb-0">
+        <span className="font-weight-light">
+          {pathToInclude === '/user'
+            ? t('search_result.include_certain_path', { pathToInclude: '/user' }) : t('search_result.include_certain_path', { pathToInclude: '/trash' })}
+        </span>
+        <input
+          type="checkbox"
+          name="check-include-specific-path"
+          onChange={() => {
+            if (checked) {
+              includeSpecificPathInSearchResult(pathToInclude);
+            }
+          }}
+        />
+      </label>
+    </div>
+  );
+
+};
+
+IncludeSpecificPathButton.propTypes = {
+  pathToInclude: PropTypes.string.isRequired,
+  checked: PropTypes.bool.isRequired,
+};
+
+export default IncludeSpecificPathButton;

+ 0 - 74
packages/app/src/components/SearchPage/OperateAllControl.tsx

@@ -1,74 +0,0 @@
-import React, {
-  ChangeEvent, forwardRef, ForwardRefRenderFunction, useImperativeHandle, useRef,
-} from 'react';
-import { CustomInput } from 'reactstrap';
-import { ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
-import { IndeterminateInputElement } from '~/interfaces/indeterminate-input-elm';
-
-type Props = {
-  isCheckboxDisabled?: boolean,
-  onCheckboxChanged?: (isChecked: boolean) => void,
-
-  children?: React.ReactNode,
-}
-
-const OperateAllControlSubstance: ForwardRefRenderFunction<ISelectableAndIndeterminatable, Props> = (props: Props, ref): JSX.Element => {
-  const {
-    isCheckboxDisabled,
-    onCheckboxChanged,
-
-    children,
-  } = props;
-
-  const selectAllCheckboxElm = useRef<IndeterminateInputElement>(null);
-
-  // publish ISelectable methods
-  useImperativeHandle(ref, () => ({
-    select: () => {
-      const input = selectAllCheckboxElm.current;
-      if (input != null) {
-        input.checked = true;
-        input.indeterminate = false;
-      }
-    },
-    deselect: () => {
-      const input = selectAllCheckboxElm.current;
-      if (input != null) {
-        input.checked = false;
-        input.indeterminate = false;
-      }
-    },
-    setIndeterminate: () => {
-      const input = selectAllCheckboxElm.current;
-      if (input != null) {
-        input.indeterminate = true;
-      }
-    },
-  }));
-
-  const checkboxChangedHandler = (e: ChangeEvent<HTMLInputElement>) => {
-    if (onCheckboxChanged != null) {
-      onCheckboxChanged(e.target.checked);
-    }
-  };
-
-  return (
-
-    <div className="d-flex align-items-center">
-      <CustomInput
-        type="checkbox"
-        id="cb-check-all"
-        data-testid="cb-select-all"
-        innerRef={selectAllCheckboxElm}
-        disabled={isCheckboxDisabled}
-        onChange={checkboxChangedHandler}
-      />
-      <span className="ml-2">
-        {children}
-      </span>
-    </div>
-  );
-
-};
-
-export const OperateAllControl = React.memo(forwardRef(OperateAllControlSubstance));

+ 91 - 72
packages/app/src/components/SearchPage/SearchControl.tsx

@@ -1,101 +1,131 @@
-import React, {
-  FC, useCallback, useEffect, useState,
-} from 'react';
+import React, { FC, useState } from 'react';
 import { useTranslation } from 'react-i18next';
-
-import { SORT_AXIS, SORT_ORDER } from '~/interfaces/search';
-import { ISearchConditions, ISearchConfigurations } from '~/stores/search';
-
 import SearchPageForm from './SearchPageForm';
+import AppContainer from '../../client/services/AppContainer';
+import DeleteSelectedPageGroup from './DeleteSelectedPageGroup';
 import SearchOptionModal from './SearchOptionModal';
 import SortControl from './SortControl';
+import { CheckboxType, SORT_AXIS, SORT_ORDER } from '../../interfaces/search';
 
 type Props = {
-  initialSearchConditions: Partial<ISearchConditions>,
-
-  onSearchInvoked: (keyword: string, configurations: Partial<ISearchConfigurations>) => void,
-
-  deleteAllControl: React.ReactNode,
+  searchingKeyword: string,
+  sort: SORT_AXIS,
+  order: SORT_ORDER,
+  appContainer: AppContainer,
+  searchResultCount: number,
+  selectAllCheckboxType: CheckboxType,
+  onClickDeleteAllButton?: () => void
+  onClickSelectAllCheckbox?: (nextSelectAllCheckboxType: CheckboxType) => void,
+  excludeUserPages: boolean,
+  excludeTrashPages: boolean,
+  onSearchInvoked: (data: {keyword: string}) => boolean,
+  onExcludeUserPagesSwitched?: () => void,
+  onExcludeTrashPagesSwitched?: () => void,
+  onChangeSortInvoked?: (nextSort: SORT_AXIS, nextOrder: SORT_ORDER) => void,
 }
 
-const SearchControl: FC <Props> = React.memo((props: Props) => {
-
-  const {
-    initialSearchConditions,
-    onSearchInvoked,
-    deleteAllControl,
-  } = props;
+const SearchControl: FC <Props> = (props: Props) => {
 
-  const [keyword, setKeyword] = useState(initialSearchConditions.keyword ?? '');
-  const [sort, setSort] = useState<SORT_AXIS>(initialSearchConditions.sort ?? SORT_AXIS.RELATION_SCORE);
-  const [order, setOrder] = useState<SORT_ORDER>(initialSearchConditions.order ?? SORT_ORDER.DESC);
-  const [includeUserPages, setIncludeUserPages] = useState(initialSearchConditions.includeUserPages ?? false);
-  const [includeTrashPages, setIncludeTrashPages] = useState(initialSearchConditions.includeTrashPages ?? false);
   const [isFileterOptionModalShown, setIsFileterOptionModalShown] = useState(false);
-
+  // Temporaly workaround for lint error
+  // later needs to be fixed: SearchControl to typescript componet
+  const SearchPageFormTypeAny : any = SearchPageForm;
   const { t } = useTranslation('');
+  const { searchResultCount } = props;
 
-  const invokeSearch = useCallback(() => {
-    if (onSearchInvoked == null) {
-      return;
+  const switchExcludeUserPagesHandler = () => {
+    if (props.onExcludeUserPagesSwitched != null) {
+      props.onExcludeUserPagesSwitched();
     }
+  };
 
-    onSearchInvoked(keyword, {
-      sort, order, includeUserPages, includeTrashPages,
-    });
-  }, [keyword, sort, order, includeTrashPages, includeUserPages, onSearchInvoked]);
+  const switchExcludeTrashPagesHandler = () => {
+    if (props.onExcludeTrashPagesSwitched != null) {
+      props.onExcludeTrashPagesSwitched();
+    }
+  };
+
+  const onChangeSortInvoked = (nextSort: SORT_AXIS, nextOrder:SORT_ORDER) => {
+    if (props.onChangeSortInvoked != null) {
+      props.onChangeSortInvoked(nextSort, nextOrder);
+    }
+  };
 
-  const searchFormChangedHandler = useCallback(({ keyword }) => {
-    setKeyword(keyword as string);
-  }, []);
+  const openSearchOptionModalHandler = () => {
+    setIsFileterOptionModalShown(true);
+  };
 
-  const changeSortHandler = useCallback((nextSort: SORT_AXIS, nextOrder: SORT_ORDER) => {
-    setSort(nextSort);
-    setOrder(nextOrder);
-  }, []);
+  const closeSearchOptionModalHandler = () => {
+    setIsFileterOptionModalShown(false);
+  };
 
-  useEffect(() => {
-    invokeSearch();
-  }, [invokeSearch]);
+  const onRetrySearchInvoked = () => {
+    if (props.onSearchInvoked != null) {
+      props.onSearchInvoked({ keyword: props.searchingKeyword });
+    }
+  };
+
+  const rednerSearchOptionModal = () => {
+    return (
+      <SearchOptionModal
+        isOpen={isFileterOptionModalShown || false}
+        onClickFilteringSearchResult={onRetrySearchInvoked}
+        onClose={closeSearchOptionModalHandler}
+        onExcludeUserPagesSwitched={switchExcludeUserPagesHandler}
+        onExcludeTrashPagesSwitched={switchExcludeTrashPagesHandler}
+        excludeUserPages={props.excludeUserPages}
+        excludeTrashPages={props.excludeTrashPages}
+      />
+    );
+  };
+
+  const renderSortControl = () => {
+    return (
+      <SortControl
+        sort={props.sort}
+        order={props.order}
+        onChangeSortInvoked={onChangeSortInvoked}
+      />
+    );
+  };
 
   return (
     <div className="position-sticky fixed-top shadow-sm">
       <div className="grw-search-page-nav d-flex py-3 align-items-center">
         <div className="flex-grow-1 mx-4">
-          <SearchPageForm
-            keyword={keyword}
-            onSearchFormChanged={searchFormChangedHandler}
+          <SearchPageFormTypeAny
+            keyword={props.searchingKeyword}
+            appContainer={props.appContainer}
+            onSearchFormChanged={props.onSearchInvoked}
           />
         </div>
 
         {/* sort option: show when screen is larger than lg */}
         <div className="mr-4 d-lg-flex d-none">
-          <SortControl
-            sort={sort}
-            order={order}
-            onChange={changeSortHandler}
-          />
+          {renderSortControl()}
         </div>
       </div>
       {/* TODO: replace the following elements deleteAll button , relevance button and include specificPath button component */}
       <div className="search-control d-flex align-items-center py-md-2 py-3 px-md-4 px-3 border-bottom border-gray">
         <div className="d-flex pl-md-2">
-          {deleteAllControl}
+          {/* Todo: design will be fixed in #80324. Function will be implemented in #77525 */}
+          <DeleteSelectedPageGroup
+            isSelectAllCheckboxDisabled={searchResultCount === 0}
+            selectAllCheckboxType={props.selectAllCheckboxType}
+            onClickDeleteAllButton={props.onClickDeleteAllButton}
+            onClickSelectAllCheckbox={props.onClickSelectAllCheckbox}
+          />
         </div>
         {/* sort option: show when screen is smaller than lg */}
         <div className="mr-md-4 mr-2 d-flex d-lg-none ml-auto">
-          <SortControl
-            sort={sort}
-            order={order}
-            onChange={changeSortHandler}
-          />
+          {renderSortControl()}
         </div>
         {/* filter option */}
         <div className="d-lg-none">
           <button
             type="button"
             className="btn"
-            onClick={() => setIsFileterOptionModalShown(true)}
+            onClick={openSearchOptionModalHandler}
           >
             <i className="icon-equalizer"></i>
           </button>
@@ -108,8 +138,7 @@ const SearchControl: FC <Props> = React.memo((props: Props) => {
                   className="mr-2"
                   type="checkbox"
                   id="flexCheckDefault"
-                  defaultChecked={includeUserPages}
-                  onChange={e => setIncludeUserPages(e.target.checked)}
+                  onClick={switchExcludeUserPagesHandler}
                 />
                 {t('Include Subordinated Target Page', { target: '/user' })}
               </label>
@@ -122,8 +151,7 @@ const SearchControl: FC <Props> = React.memo((props: Props) => {
                   className="mr-2"
                   type="checkbox"
                   id="flexCheckChecked"
-                  defaultChecked={includeTrashPages}
-                  onChange={e => setIncludeTrashPages(e.target.checked)}
+                  onClick={switchExcludeTrashPagesHandler}
                 />
                 {t('Include Subordinated Target Page', { target: '/trash' })}
               </label>
@@ -131,19 +159,10 @@ const SearchControl: FC <Props> = React.memo((props: Props) => {
           </div>
         </div>
       </div>
-
-      <SearchOptionModal
-        isOpen={isFileterOptionModalShown || false}
-        onClose={() => setIsFileterOptionModalShown(false)}
-        includeUserPages={includeUserPages}
-        includeTrashPages={includeTrashPages}
-        onIncludeUserPagesSwitched={setIncludeUserPages}
-        onIncludeTrashPagesSwitched={setIncludeTrashPages}
-      />
-
+      {rednerSearchOptionModal()}
     </div>
   );
-});
+};
 
 
 export default SearchControl;

+ 23 - 22
packages/app/src/components/SearchPage/SearchOptionModal.tsx

@@ -2,17 +2,18 @@ import React, { FC } from 'react';
 import { useTranslation } from 'react-i18next';
 
 import {
-  Modal, ModalHeader, ModalBody,
+  Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
 
 type Props = {
   isOpen: boolean,
-  includeUserPages: boolean,
-  includeTrashPages: boolean,
+  excludeUserPages: boolean,
+  excludeTrashPages: boolean,
   onClose?: () => void,
-  onIncludeUserPagesSwitched?: (isChecked: boolean) => void,
-  onIncludeTrashPagesSwitched?: (isChecked: boolean) => void,
+  onExcludeUserPagesSwitched?: () => void,
+  onExcludeTrashPagesSwitched?: () => void,
+  onClickFilteringSearchResult?: () => void,
 }
 
 const SearchOptionModal: FC<Props> = (props: Props) => {
@@ -20,10 +21,7 @@ const SearchOptionModal: FC<Props> = (props: Props) => {
   const { t } = useTranslation('');
 
   const {
-    isOpen, includeUserPages, includeTrashPages,
-    onClose,
-    onIncludeUserPagesSwitched,
-    onIncludeTrashPagesSwitched,
+    isOpen, onClose, excludeUserPages, excludeTrashPages,
   } = props;
 
   const onCloseModal = () => {
@@ -32,15 +30,10 @@ const SearchOptionModal: FC<Props> = (props: Props) => {
     }
   };
 
-  const includeUserPagesChangeHandler = (isChecked: boolean) => {
-    if (onIncludeUserPagesSwitched != null) {
-      onIncludeUserPagesSwitched(isChecked);
-    }
-  };
-
-  const includeTrashPagesChangeHandler = (isChecked: boolean) => {
-    if (onIncludeTrashPagesSwitched != null) {
-      onIncludeTrashPagesSwitched(isChecked);
+  const onClickFilteringSearchResult = () => {
+    if (props.onClickFilteringSearchResult != null) {
+      props.onClickFilteringSearchResult();
+      onCloseModal();
     }
   };
 
@@ -56,8 +49,8 @@ const SearchOptionModal: FC<Props> = (props: Props) => {
               <input
                 className="mr-2"
                 type="checkbox"
-                onChange={e => includeUserPagesChangeHandler(e.target.checked)}
-                checked={includeUserPages}
+                onChange={props.onExcludeUserPagesSwitched}
+                checked={!excludeUserPages}
               />
               {t('Include Subordinated Target Page', { target: '/user' })}
             </label>
@@ -67,14 +60,22 @@ const SearchOptionModal: FC<Props> = (props: Props) => {
               <input
                 className="mr-2"
                 type="checkbox"
-                onChange={e => includeTrashPagesChangeHandler(e.target.checked)}
-                checked={includeTrashPages}
+                onChange={props.onExcludeTrashPagesSwitched}
+                checked={!excludeTrashPages}
               />
               {t('Include Subordinated Target Page', { target: '/trash' })}
             </label>
           </div>
         </div>
       </ModalBody>
+      <ModalFooter>
+        <button
+          type="button"
+          className="btn btn-secondary"
+          onClick={onClickFilteringSearchResult}
+        >{t('search_result.search_again')}
+        </button>
+      </ModalFooter>
     </Modal>
   );
 };

+ 79 - 0
packages/app/src/components/SearchPage/SearchPageLayout.tsx

@@ -0,0 +1,79 @@
+import React, { FC } from 'react';
+import { useTranslation } from 'react-i18next';
+
+type SearchResultMeta = {
+  took?: number,
+  total?: number,
+  results?: number
+}
+
+type Props = {
+  SearchControl: React.FunctionComponent,
+  SearchResultList: React.FunctionComponent,
+  SearchResultContent: React.FunctionComponent,
+  searchResultMeta: SearchResultMeta,
+  searchingKeyword: string,
+  pagingLimit: number,
+  activePage: number,
+  onPagingLimitChanged: (limit: number) => void
+}
+
+const SearchPageLayout: FC<Props> = (props: Props) => {
+  const { t } = useTranslation('');
+  const {
+    SearchResultList, SearchControl, SearchResultContent, searchResultMeta, searchingKeyword, pagingLimit, activePage,
+  } = props;
+
+  const renderShowingPageCountInfo = () => {
+    if (searchResultMeta.total == null || searchResultMeta.total === 0) return;
+    const leftNum = pagingLimit * (activePage - 1) + 1;
+    const rightNum = (leftNum - 1) + (searchResultMeta.results || 0);
+    return <span className="ml-3">{`${leftNum}-${rightNum}`} / {searchResultMeta.total || 0}</span>;
+  };
+
+  return (
+    <div className="content-main">
+      <div className="search-result d-flex" id="search-result">
+        <div className="mw-0 flex-grow-1 flex-basis-0 border boder-gray search-result-list" id="search-result-list">
+
+          <SearchControl></SearchControl>
+          <div className="search-result-list-scroll">
+            <div className="d-flex align-items-center justify-content-between my-3 ml-4">
+              <div className="search-result-meta text-nowrap">
+                <span className="font-weight-light">{t('search_result.result_meta')} </span>
+                <span className="h5">{`"${searchingKeyword}"`}</span>
+                {/* Todo: replace "1-10" to the appropriate value */}
+                {renderShowingPageCountInfo()}
+              </div>
+              <div className="input-group search-result-select-group ml-4 d-lg-flex d-none">
+                <div className="input-group-prepend">
+                  <label className="input-group-text text-muted" htmlFor="inputGroupSelect01">{t('search_result.number_of_list_to_display')}</label>
+                </div>
+                <select
+                  defaultValue={props.pagingLimit}
+                  className="custom-select"
+                  id="inputGroupSelect01"
+                  onChange={(e) => { props.onPagingLimitChanged(Number(e.target.value)) }}
+                >
+                  {[20, 50, 100, 200].map((limit) => {
+                    return <option key={limit} value={limit}>{limit}{t('search_result.page_number_unit')}</option>;
+                  })}
+                </select>
+              </div>
+            </div>
+
+            <div className="page-list px-md-4">
+              <SearchResultList></SearchResultList>
+            </div>
+          </div>
+        </div>
+        <div className="mw-0 flex-grow-1 flex-basis-0 d-none d-lg-block search-result-content">
+          <SearchResultContent></SearchResultContent>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+
+export default SearchPageLayout;

+ 10 - 8
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -48,8 +48,8 @@ const MUTATION_OBSERVER_CONFIG = { childList: true, subtree: true };
 
 type Props ={
   appContainer: AppContainer,
-  pageWithMeta : IPageWithMeta<IPageSearchMeta>,
-  highlightKeywords?: string[],
+  searchingKeyword:string,
+  focusedSearchResultData : IPageWithMeta<IPageSearchMeta>,
   showPageControlDropdown?: boolean,
 }
 
@@ -72,7 +72,7 @@ const generateObserverCallback = (doScroll: ()=>void) => {
   };
 };
 
-export const SearchResultContent: FC<Props> = (props: Props) => {
+const SearchResultContent: FC<Props> = (props: Props) => {
   const scrollElementRef = useRef(null);
 
   // ***************************  Auto Scroll  ***************************
@@ -94,8 +94,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
 
   const {
     appContainer,
-    pageWithMeta,
-    highlightKeywords,
+    focusedSearchResultData,
     showPageControlDropdown,
   } = props;
 
@@ -103,7 +102,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
 
-  const page = pageWithMeta?.pageData;
+  const page = focusedSearchResultData?.pageData;
 
   const growiRenderer = appContainer.getRenderer('searchresult');
 
@@ -147,7 +146,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
         </div>
       </>
     );
-  }, [page, showPageControlDropdown, duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler]);
+  }, [page, showPageControlDropdown, renameItemClickedHandler, deleteItemClickedHandler]);
 
   // return if page is null
   if (page == null) return <></>;
@@ -164,9 +163,12 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
           pageId={page._id}
           pagePath={page.path}
           revisionId={page.revision}
-          highlightKeywords={highlightKeywords}
+          highlightKeywords={props.searchingKeyword}
         />
       </div>
     </div>
   );
 };
+
+
+export default SearchResultContent;

+ 39 - 53
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -1,65 +1,38 @@
-import React, {
-  forwardRef,
-  ForwardRefRenderFunction, useCallback, useImperativeHandle, useRef,
-} from 'react';
-import { ISelectable, ISelectableAll } from '~/client/interfaces/selectable-all';
-import { IPageWithMeta, isIPageInfoForListing } from '~/interfaces/page';
+import React, { FC } from 'react';
+import { IPageInfoForEntity, IPageWithMeta, isIPageInfoForListing } from '~/interfaces/page';
 import { IPageSearchMeta } from '~/interfaces/search';
-import { useIsGuestUser } from '~/stores/context';
 import { useSWRxPageInfoForList } from '~/stores/page';
 
 import { PageListItemL } from '../PageList/PageListItemL';
+import PaginationWrapper from '../PaginationWrapper';
 
 
 type Props = {
-  pages: IPageWithMeta<IPageSearchMeta>[],
-  selectedPageId?: string,
-  onPageSelected?: (page?: IPageWithMeta<IPageSearchMeta>) => void,
-  onCheckboxChanged?: (isChecked: boolean, pageId: string) => void,
+  pages: IPageWithMeta<IPageInfoForEntity & IPageSearchMeta>[],
+  selectedPagesIdList: Set<string>
+  isEnableActions: boolean,
+  searchResultCount?: number,
+  activePage?: number,
+  pagingLimit?: number,
+  focusedSearchResultData?: IPageWithMeta<IPageSearchMeta>,
+  onPagingNumberChanged?: (activePage: number) => void,
+  onClickItem?: (pageId: string) => void,
+  onClickCheckbox?: (pageId: string) => void,
+  onClickInvoked?: (pageId: string) => void,
+  onClickDeleteButton?: (pageId: string) => void,
 }
 
-const SearchResultListSubstance: ForwardRefRenderFunction<ISelectableAll, Props> = (props:Props, ref) => {
+const SearchResultList: FC<Props> = (props:Props) => {
   const {
-    pages, selectedPageId,
-    onPageSelected,
+    pages, focusedSearchResultData, selectedPagesIdList, isEnableActions,
   } = props;
 
   const pageIdsWithNoSnippet = pages
     .filter(page => (page.pageMeta?.elasticSearchResult?.snippet.length ?? 0) === 0)
     .map(page => page.pageData._id);
 
-  const { data: isGuestUser } = useIsGuestUser();
   const { data: idToPageInfo } = useSWRxPageInfoForList(pageIdsWithNoSnippet);
 
-  const itemsRef = useRef<(ISelectable|null)[]>([]);
-
-  // publish selectAll()
-  useImperativeHandle(ref, () => ({
-    selectAll: () => {
-      const items = itemsRef.current;
-      if (items != null) {
-        items.forEach(item => item != null && item.select());
-      }
-    },
-    deselectAll: () => {
-      const items = itemsRef.current;
-      if (items != null) {
-        items.forEach(item => item != null && item.deselect());
-      }
-    },
-  }));
-
-  const clickItemHandler = useCallback((pageId: string) => {
-    if (onPageSelected != null) {
-      const selectedPage = pages.find(page => page.pageData._id === pageId);
-      onPageSelected(selectedPage);
-    }
-  }, [onPageSelected, pages]);
-
-  const clickDeleteButtonHandler = useCallback((pageId: string) => {
-    // TODO implement
-  }, []);
-
   let injectedPage;
   // inject data to list
   if (idToPageInfo != null) {
@@ -81,26 +54,39 @@ const SearchResultListSubstance: ForwardRefRenderFunction<ISelectableAll, Props>
     });
   }
 
+  const focusedPageId = (focusedSearchResultData != null && focusedSearchResultData.pageData != null) ? focusedSearchResultData.pageData._id : '';
   return (
     <ul className="page-list-ul list-group list-group-flush">
-      { (injectedPage ?? pages).map((page, i) => {
+      { (injectedPage ?? pages).map((page) => {
+        const isChecked = selectedPagesIdList.has(page.pageData._id);
+
         return (
           <PageListItemL
             key={page.pageData._id}
-            // eslint-disable-next-line no-return-assign
-            ref={c => itemsRef.current[i] = c}
             page={page}
-            isEnableActions={isGuestUser}
-            isSelected={page.pageData._id === selectedPageId}
-            onClickItem={clickItemHandler}
-            onCheckboxChanged={props.onCheckboxChanged}
-            onClickDeleteButton={clickDeleteButtonHandler}
+            isEnableActions={isEnableActions}
+            onClickItem={props.onClickItem}
+            onClickCheckbox={props.onClickCheckbox}
+            isChecked={isChecked}
+            isSelected={page.pageData._id === focusedPageId || false}
+            onClickDeleteButton={props.onClickDeleteButton}
           />
         );
       })}
+      {props.searchResultCount != null && props.searchResultCount > 0 && (
+        <div className="my-4 mx-auto">
+          <PaginationWrapper
+            activePage={props.activePage || 1}
+            changePage={props.onPagingNumberChanged}
+            totalItemsCount={props.searchResultCount || 0}
+            pagingLimit={props.pagingLimit}
+          />
+        </div>
+      )}
+
     </ul>
   );
 
 };
 
-export const SearchResultList = forwardRef(SearchResultListSubstance);
+export default SearchResultList;

+ 7 - 9
packages/app/src/components/SearchPage/SortControl.tsx

@@ -7,22 +7,20 @@ const { DESC, ASC } = SORT_ORDER;
 type Props = {
   sort: SORT_AXIS,
   order: SORT_ORDER,
-  onChange?: (nextSort: SORT_AXIS, nextOrder: SORT_ORDER) => void,
+  onChangeSortInvoked?: (nextSort: SORT_AXIS, nextOrder: SORT_ORDER) => void,
 }
 
 const SortControl: FC <Props> = (props: Props) => {
 
   const { t } = useTranslation('');
 
-  const { sort, order, onChange } = props;
-
   const onClickChangeSort = (nextSortAxis: SORT_AXIS, nextSortOrder: SORT_ORDER) => {
-    if (onChange != null) {
-      onChange(nextSortAxis, nextSortOrder);
+    if (props.onChangeSortInvoked != null) {
+      props.onChangeSortInvoked(nextSortAxis, nextSortOrder);
     }
   };
 
-  const renderOrderIcon = () => {
+  const renderOrderIcon = (order: SORT_ORDER) => {
     const iconClassName = ASC === order ? 'fa fa-sort-amount-asc' : 'fa fa-sort-amount-desc';
     return <i className={iconClassName} aria-hidden="true" />;
   };
@@ -32,7 +30,7 @@ const SortControl: FC <Props> = (props: Props) => {
       <div className="input-group">
         <div className="input-group-prepend">
           <div className="input-group-text border text-muted" id="btnGroupAddon">
-            {renderOrderIcon()}
+            {renderOrderIcon(props.order)}
           </div>
         </div>
         <div className="border rounded-right">
@@ -41,11 +39,11 @@ const SortControl: FC <Props> = (props: Props) => {
             className="btn dropdown-toggle search-sort-option-btn"
             data-toggle="dropdown"
           >
-            <span className="mr-4 text-secondary">{t(`search_result.sort_axis.${sort}`)}</span>
+            <span className="mr-4 text-secondary">{t(`search_result.sort_axis.${props.sort}`)}</span>
           </button>
           <div className="dropdown-menu dropdown-menu-right">
             {Object.values(SORT_AXIS).map((sortAxis) => {
-              const nextOrder = (sort !== sortAxis || order === ASC) ? DESC : ASC;
+              const nextOrder = (props.sort !== sortAxis || props.order === ASC) ? DESC : ASC;
               return (
                 <button
                   key={sortAxis}

+ 0 - 166
packages/app/src/components/SearchPage2/SearchPageBase.tsx

@@ -1,166 +0,0 @@
-import React, {
-  forwardRef, ForwardRefRenderFunction, useEffect, useImperativeHandle, useRef, useState,
-} from 'react';
-import { ISelectableAll } from '~/client/interfaces/selectable-all';
-import AppContainer from '~/client/services/AppContainer';
-import { IPageWithMeta } from '~/interfaces/page';
-import { IPageSearchMeta } from '~/interfaces/search';
-import { useIsGuestUser } from '~/stores/context';
-
-import { SearchResultContent } from '../SearchPage/SearchResultContent';
-import { SearchResultList } from '../SearchPage/SearchResultList';
-
-type Props = {
-  appContainer: AppContainer,
-
-  pages?: IPageWithMeta<IPageSearchMeta>[],
-
-  onSelectedPagesByCheckboxesChanged?: (selectedCount: number, totalCount: number) => void,
-
-  searchControl: React.ReactNode,
-  searchResultListHead: React.ReactNode,
-  searchPager: React.ReactNode,
-}
-
-const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll, Props> = (props:Props, ref) => {
-  const {
-    appContainer,
-    pages,
-    onSelectedPagesByCheckboxesChanged,
-    searchControl, searchResultListHead, searchPager,
-  } = props;
-
-  const searchResultListRef = useRef<ISelectableAll|null>(null);
-
-  const { data: isGuestUser } = useIsGuestUser();
-
-  // TODO get search keywords and split
-  // ref: RevisionRenderer
-  //   [...keywords.match(/"[^"]+"|[^\u{20}\u{3000}]+/ug)].forEach((keyword, i) => {
-  const [highlightKeywords, setHightlightKeywords] = useState<string[]>([]);
-  const [selectedPageIdsByCheckboxes] = useState<Set<string>>(new Set());
-  // const [allPageIds] = useState<Set<string>>(new Set());
-  const [selectedPageWithMeta, setSelectedPageWithMeta] = useState<IPageWithMeta<IPageSearchMeta> | undefined>();
-
-  // publish selectAll()
-  useImperativeHandle(ref, () => ({
-    selectAll: () => {
-      const instance = searchResultListRef.current;
-      if (instance != null) {
-        instance.selectAll();
-      }
-
-      if (pages != null) {
-        pages.forEach(page => selectedPageIdsByCheckboxes.add(page.pageData._id));
-      }
-    },
-    deselectAll: () => {
-      const instance = searchResultListRef.current;
-      if (instance != null) {
-        instance.deselectAll();
-      }
-
-      selectedPageIdsByCheckboxes.clear();
-    },
-  }));
-
-  const checkboxChangedHandler = (isChecked: boolean, pageId: string) => {
-    if (pages == null || pages.length === 0) {
-      return;
-    }
-
-    if (isChecked) {
-      selectedPageIdsByCheckboxes.add(pageId);
-    }
-    else {
-      selectedPageIdsByCheckboxes.delete(pageId);
-    }
-
-    if (onSelectedPagesByCheckboxesChanged != null) {
-      onSelectedPagesByCheckboxesChanged(selectedPageIdsByCheckboxes.size, pages.length);
-    }
-  };
-
-  // select first item on load
-  useEffect(() => {
-    if (selectedPageWithMeta == null && pages != null && pages.length > 0) {
-      setSelectedPageWithMeta(pages[0]);
-    }
-  }, [pages, selectedPageWithMeta]);
-
-  // reset selectedPageIdsByCheckboxes
-  useEffect(() => {
-    if (pages == null) {
-      return;
-    }
-
-    if (pages.length > 0) {
-      selectedPageIdsByCheckboxes.clear();
-    }
-
-    if (onSelectedPagesByCheckboxesChanged != null) {
-      onSelectedPagesByCheckboxesChanged(selectedPageIdsByCheckboxes.size, pages.length);
-    }
-  }, [onSelectedPagesByCheckboxesChanged, pages, selectedPageIdsByCheckboxes]);
-
-  const isLoading = pages == null;
-
-  return (
-    <div className="content-main">
-      <div className="search-result d-flex" id="search-result">
-
-        <div className="mw-0 flex-grow-1 flex-basis-0 border boder-gray search-result-list" id="search-result-list">
-
-          {searchControl}
-
-          <div className="search-result-list-scroll">
-
-            { isLoading && (
-              <div className="mw-0 flex-grow-1 flex-basis-0 m-5 text-muted text-center">
-                <i className="fa fa-2x fa-spinner fa-pulse mr-1"></i>
-              </div>
-            ) }
-
-            { !isLoading && (
-              <>
-                <div className="my-3 px-md-4">
-                  {searchResultListHead}
-                </div>
-                <div className="page-list px-md-4">
-                  <SearchResultList
-                    ref={searchResultListRef}
-                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                    pages={pages!}
-                    selectedPageId={selectedPageWithMeta?.pageData._id}
-                    onPageSelected={page => setSelectedPageWithMeta(page)}
-                    onCheckboxChanged={checkboxChangedHandler}
-                  />
-                </div>
-                <div className="my-4 d-flex justify-content-center">
-                  {searchPager}
-                </div>
-              </>
-            ) }
-
-          </div>
-
-        </div>
-
-        <div className="mw-0 flex-grow-1 flex-basis-0 d-none d-lg-block search-result-content">
-          { selectedPageWithMeta != null && (
-            <SearchResultContent
-              appContainer={appContainer}
-              pageWithMeta={selectedPageWithMeta}
-              highlightKeywords={highlightKeywords}
-              showPageControlDropdown={isGuestUser}
-            />
-          )}
-        </div>
-
-      </div>
-    </div>
-  );
-};
-
-
-export const SearchPageBase = forwardRef(SearchPageBaseSubstance);

+ 5 - 10
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -13,9 +13,7 @@ import { pathUtils, pagePathUtils } from '@growi/core';
 import { toastWarning, toastError, toastSuccess } from '~/client/util/apiNotification';
 
 import { useSWRxPageChildren } from '~/stores/page-listing';
-import { useSWRxPageInfo } from '~/stores/page';
 import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
-import { useShareLinkId } from '~/stores/context';
 import { IPageForPageDeleteModal } from '~/stores/modal';
 
 import TriangleIcon from '~/components/Icons/TriangleIcon';
@@ -31,7 +29,7 @@ interface ItemProps {
   isOpen?: boolean
   onClickDuplicateMenuItem?(pageId: string, path: string): void
   onClickRenameMenuItem?(pageId: string, revisionId: string, path: string): void
-  onClickDeleteMenuItem?(pageToDelete: IPageForPageDeleteModal | null, isAbleToDeleteCompletely: boolean): void
+  onClickDeleteMenuItem?(pageToDelete: IPageForPageDeleteModal | null): void
 }
 
 // Utility to mark target
@@ -78,8 +76,6 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
   const { page, children } = itemNode;
 
   const [pageTitle, setPageTitle] = useState(page.path);
-  const { data: shareLinkId } = useShareLinkId();
-  const { data: pageInfo } = useSWRxPageInfo(page._id ?? null, shareLinkId);
   const [currentChildren, setCurrentChildren] = useState(children);
   const [isOpen, setIsOpen] = useState(_isOpen);
   const [isNewPageInputShown, setNewPageInputShown] = useState(false);
@@ -241,7 +237,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
     onClickRenameMenuItem(pageId, revisionId as string, path);
   }, [onClickRenameMenuItem, page]);
 
-  const deleteMenuItemClickHandler = useCallback(async(_pageId: string): Promise<void> => {
+  const onClickDeleteButton = useCallback(async(_pageId: string): Promise<void> => {
     if (onClickDeleteMenuItem == null) {
       return;
     }
@@ -257,10 +253,9 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
       revisionId: revisionId as string,
       path,
     };
-    const isAbleToDeleteCompletely = pageInfo?.isAbleToDeleteCompletely ?? false;
 
-    onClickDeleteMenuItem(pageToDelete, isAbleToDeleteCompletely);
-  }, [onClickDeleteMenuItem, page, pageInfo?.isAbleToDeleteCompletely]);
+    onClickDeleteMenuItem(pageToDelete);
+  }, [page, onClickDeleteMenuItem]);
 
   const onPressEnterForCreateHandler = async(inputText: string) => {
     setNewPageInputShown(false);
@@ -387,7 +382,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
             showBookmarkMenuItem
             onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
             onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
-            onClickDeleteMenuItem={deleteMenuItemClickHandler}
+            onClickDeleteMenuItem={onClickDeleteButton}
             onClickRenameMenuItem={renameMenuItemClickHandler}
           >
             <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0">

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

@@ -64,7 +64,7 @@ const renderByInitialNode = (
     targetPathOrId?: string,
     onClickDuplicateMenuItem?: (pageId: string, path: string) => void,
     onClickRenameMenuItem?: (pageId: string, revisionId: string, path: string) => void,
-    onClickDeleteMenuItem?: (pageToDelete: IPageForPageDeleteModal | null, isAbleToDeleteCompletely: boolean) => void,
+    onClickDeleteMenuItem?: (pageToDelete: IPageForPageDeleteModal | null) => void,
 ): JSX.Element => {
 
   return (
@@ -146,8 +146,8 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
     }
   };
 
-  const onClickDeleteMenuItem = (pageToDelete: IPageForPageDeleteModal, isAbleToDeleteCompletely) => {
-    openDeleteModal([pageToDelete], onDeletedHandler, isAbleToDeleteCompletely);
+  const onClickDeleteMenuItem = (pageToDelete: IPageForPageDeleteModal) => {
+    openDeleteModal([pageToDelete], onDeletedHandler);
   };
 
   if (error1 != null || error2 != null) {

+ 13 - 9
packages/app/src/interfaces/search.ts

@@ -1,5 +1,11 @@
 import { IPageInfoAll, IPageWithMeta } from './page';
 
+export enum CheckboxType {
+  NONE_CHECKED = 'noneChecked',
+  INDETERMINATE = 'indeterminate',
+  ALL_CHECKED = 'allChecked',
+}
+
 export type IPageSearchMeta = {
   bookmarkCount?: number,
   elasticSearchResult?: {
@@ -13,20 +19,18 @@ export const isIPageSearchMeta = (meta: IPageInfoAll | (IPageInfoAll & IPageSear
   return meta != null && 'elasticSearchResult' in meta;
 };
 
-export type ISearchResult<T > = ISearchResultMeta & {
-  data: T[],
-}
+export type IFormattedSearchResult = {
+  data: IPageWithMeta<IPageSearchMeta>[]
+
+  totalCount: number
 
-export type ISearchResultMeta = {
   meta: {
-    took?: number
     total: number
-    hitsCount: number
-  },
+    took?: number
+    count?: number
+  }
 }
 
-export type IFormattedSearchResult = ISearchResult<IPageWithMeta<IPageSearchMeta>>;
-
 export const SORT_AXIS = {
   RELATION_SCORE: 'relationScore',
   CREATED_AT: 'createdAt',

+ 12 - 2
packages/app/src/server/interfaces/search.ts

@@ -1,6 +1,5 @@
 /* eslint-disable camelcase */
 import { SearchDelegatorName } from '~/interfaces/named-query';
-import { ISearchResult } from '~/interfaces/search';
 
 
 export type QueryTerms = {
@@ -26,7 +25,18 @@ export interface SearchResolver{
 
 export interface SearchDelegator<T = unknown> {
   name?: SearchDelegatorName
-  search(data: SearchableData | null, user, userGroups, option): Promise<ISearchResult<T>>
+  search(data: SearchableData | null, user, userGroups, option): Promise<Result<T> & MetaData>
+}
+
+export type Result<T> = {
+  data: T[]
+}
+
+export type MetaData = {
+  meta: {
+    [key:string]: any,
+    total: number,
+  }
 }
 
 export type SearchableData = {

+ 6 - 8
packages/app/src/server/service/search-delegator/elasticsearch.ts

@@ -9,14 +9,12 @@ import streamToPromise from 'stream-to-promise';
 
 import { createBatchStream } from '../../util/batch-stream';
 import loggerFactory from '~/utils/logger';
-import { PageModel } from '../../models/page';
+import { PageDocument, PageModel } from '../../models/page';
 import {
-  SearchDelegator, SearchableData, QueryTerms,
+  MetaData, SearchDelegator, Result, SearchableData, QueryTerms,
 } from '../../interfaces/search';
 import { SearchDelegatorName } from '~/interfaces/named-query';
-import {
-  IFormattedSearchResult, ISearchResult, SORT_AXIS, SORT_ORDER,
-} from '~/interfaces/search';
+import { SORT_AXIS, SORT_ORDER } from '~/interfaces/search';
 import ElasticsearchClient from './elasticsearch-client';
 
 const logger = loggerFactory('growi:service:search-delegator:elasticsearch');
@@ -613,7 +611,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data> {
    *   data: [ pages ...],
    * }
    */
-  async searchKeyword(query): Promise<IFormattedSearchResult> {
+  async searchKeyword(query) {
     // for debug
     if (process.env.NODE_ENV === 'development') {
       const { body: result } = await this.client.indices.validateQuery({
@@ -636,7 +634,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data> {
       meta: {
         took: result.took,
         total: totalValue,
-        hitsCount: result.hits.hits.length,
+        results: result.hits.hits.length,
       },
       data: result.hits.hits.map((elm) => {
         return {
@@ -942,7 +940,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data> {
     };
   }
 
-  async search(data: SearchableData, user, userGroups, option): Promise<ISearchResult<unknown>> {
+  async search(data: SearchableData, user, userGroups, option): Promise<Result<Data> & MetaData> {
     const { queryString, terms } = data;
 
     const from = option.offset || null;

+ 2 - 4
packages/app/src/server/service/search-delegator/private-legacy-pages.ts

@@ -4,10 +4,9 @@ import { PageModel, PageDocument } from '~/server/models/page';
 import { SearchDelegatorName } from '~/interfaces/named-query';
 import { IPage } from '~/interfaces/page';
 import {
-  SearchableData, SearchDelegator,
+  MetaData, Result, SearchableData, SearchDelegator,
 } from '../../interfaces/search';
 import { serializeUserSecurely } from '../../models/serializers/user-serializer';
-import { ISearchResult } from '~/interfaces/search';
 
 
 class PrivateLegacyPagesDelegator implements SearchDelegator<IPage> {
@@ -18,7 +17,7 @@ class PrivateLegacyPagesDelegator implements SearchDelegator<IPage> {
     this.name = SearchDelegatorName.PRIVATE_LEGACY_PAGES;
   }
 
-  async search(_data: SearchableData | null, user, userGroups, option): Promise<ISearchResult<IPage>> {
+  async search(_data: SearchableData | null, user, userGroups, option): Promise<Result<IPage> & MetaData> {
     const { offset, limit } = option;
 
     if (offset == null || limit == null) {
@@ -52,7 +51,6 @@ class PrivateLegacyPagesDelegator implements SearchDelegator<IPage> {
       data: pages,
       meta: {
         total: pages.length,
-        hitsCount: pages.length,
       },
     };
   }

+ 6 - 4
packages/app/src/server/service/search.ts

@@ -1,12 +1,12 @@
 import xss from 'xss';
 
 import { SearchDelegatorName } from '~/interfaces/named-query';
-import { IFormattedSearchResult, ISearchResult, ISearchResultMeta } from '~/interfaces/search';
+import { IFormattedSearchResult } from '~/interfaces/search';
 import loggerFactory from '~/utils/logger';
 
 import NamedQuery from '../models/named-query';
 import {
-  SearchDelegator, SearchQueryParser, SearchResolver, ParsedQuery, SearchableData, QueryTerms,
+  SearchDelegator, SearchQueryParser, SearchResolver, ParsedQuery, Result, MetaData, SearchableData, QueryTerms,
 } from '../interfaces/search';
 import ElasticsearchDelegator from './search-delegator/elasticsearch';
 import PrivateLegacyPagesDelegator from './search-delegator/private-legacy-pages';
@@ -236,7 +236,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
     return [this.nqDelegators[SearchDelegatorName.DEFAULT], data];
   }
 
-  async searchKeyword(keyword: string, user, userGroups, searchOpts): Promise<[ISearchResult<unknown>, string]> {
+  async searchKeyword(keyword: string, user, userGroups, searchOpts): Promise<[Result<any> & MetaData, string]> {
     let parsedQuery;
     // parse
     try {
@@ -348,7 +348,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
   /**
    * formatting result
    */
-  async formatSearchResult(searchResult: ISearchResult<any>, delegatorName): Promise<IFormattedSearchResult> {
+  async formatSearchResult(searchResult: Result<any> & MetaData, delegatorName): Promise<IFormattedSearchResult> {
     if (!this.checkIsFormattable(searchResult, delegatorName)) {
       const data = searchResult.data.map((page) => {
         return {
@@ -359,6 +359,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
 
       return {
         data,
+        totalCount: data.length,
         meta: searchResult.meta,
       };
     }
@@ -376,6 +377,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
 
     // set meta data
     result.meta = searchResult.meta;
+    result.totalCount = findPageResult.totalCount;
 
     // set search result page data
     result.data = searchResult.data.map((data) => {

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

@@ -34,8 +34,6 @@ export type IPageForPageDeleteModal = {
   pageId: string,
   revisionId?: string,
   path: string
-  isAbleToDeleteCompletely?: boolean,
-  isDeleteCompletelyModal?: boolean,
 }
 
 export type OnDeletedFunction = (pathOrPaths: string | string[], isRecursively: Nullable<true>, isCompletely: Nullable<true>) => void;
@@ -44,40 +42,20 @@ type DeleteModalStatus = {
   isOpened: boolean,
   pages?: IPageForPageDeleteModal[],
   onDeleted?: OnDeletedFunction,
-  isAbleToDeleteCompletely?: boolean,
-  isDeleteCompletelyModal?: boolean,
 }
 
 type DeleteModalStatusUtils = {
-  open(
-    pages?: IPageForPageDeleteModal[],
-    onDeleted?: OnDeletedFunction,
-    isAbleToDeleteCompletely?: boolean,
-    isDeleteCompletelyModal?: boolean,
-  ): Promise<DeleteModalStatus | undefined>,
+  open(pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction): Promise<DeleteModalStatus | undefined>,
   close(): Promise<DeleteModalStatus | undefined>,
 }
 
 export const usePageDeleteModal = (status?: DeleteModalStatus): SWRResponse<DeleteModalStatus, Error> & DeleteModalStatusUtils => {
-  const initialData: DeleteModalStatus = {
-    isOpened: false,
-    pages: [],
-    onDeleted: () => {},
-    isAbleToDeleteCompletely: false,
-    isDeleteCompletelyModal: false,
-  };
+  const initialData: DeleteModalStatus = { isOpened: false, pages: [], onDeleted: () => {} };
   const swrResponse = useStaticSWR<DeleteModalStatus, Error>('deleteModalStatus', status, { fallbackData: initialData });
 
   return {
     ...swrResponse,
-    open: (
-        pages?: IPageForPageDeleteModal[],
-        onDeleted?: OnDeletedFunction,
-        isAbleToDeleteCompletely?: boolean,
-        isDeleteCompletelyModal?: boolean,
-    ) => swrResponse.mutate({
-      isOpened: true, pages, onDeleted, isAbleToDeleteCompletely, isDeleteCompletelyModal,
-    }),
+    open: (pages?: IPageForPageDeleteModal[], onDeleted?: OnDeletedFunction) => swrResponse.mutate({ isOpened: true, pages, onDeleted }),
     close: () => swrResponse.mutate({ isOpened: false }),
   };
 };

+ 0 - 92
packages/app/src/stores/search.tsx

@@ -1,92 +0,0 @@
-import { SWRResponse } from 'swr';
-import useSWRImmutable from 'swr/immutable';
-
-import { apiGet } from '~/client/util/apiv1-client';
-
-import { IFormattedSearchResult, SORT_AXIS, SORT_ORDER } from '~/interfaces/search';
-
-
-export type ISearchConfigurations = {
-  limit: number,
-  offset?: number,
-  sort?: SORT_AXIS,
-  order?: SORT_ORDER,
-  includeTrashPages?: boolean,
-  includeUserPages?: boolean,
-}
-
-type ISearchConfigurationsFixed = {
-  limit: number,
-  offset: number,
-  sort: SORT_AXIS,
-  order: SORT_ORDER,
-  includeTrashPages: boolean,
-  includeUserPages: boolean,
-}
-
-export type ISearchConditions = ISearchConfigurationsFixed & {
-  keyword: string,
-  rawQuery: string,
-}
-
-const createSearchQuery = (keyword: string, includeTrashPages: boolean, includeUserPages: boolean): string => {
-  let query = keyword;
-
-  // pages included in specific path are not retrived when prefix is added
-  if (!includeTrashPages) {
-    query = `${query} -prefix:/trash`;
-  }
-  if (!includeUserPages) {
-    query = `${query} -prefix:/user`;
-  }
-
-  return query;
-};
-
-export const useSWRxFullTextSearch = (
-    keyword: string, configurations: ISearchConfigurations,
-): SWRResponse<IFormattedSearchResult, Error> & { conditions: ISearchConditions } => {
-
-  const {
-    limit, offset, sort, order, includeTrashPages, includeUserPages,
-  } = configurations;
-
-  const fixedConfigurations: ISearchConfigurationsFixed = {
-    limit,
-    offset: offset ?? 0,
-    sort: sort ?? SORT_AXIS.RELATION_SCORE,
-    order: order ?? SORT_ORDER.DESC,
-    includeTrashPages: includeTrashPages ?? false,
-    includeUserPages: includeUserPages ?? false,
-  };
-  const rawQuery = createSearchQuery(keyword, fixedConfigurations.includeTrashPages, fixedConfigurations.includeUserPages);
-
-  const swrResult = useSWRImmutable(
-    ['/search', keyword, fixedConfigurations],
-    (endpoint, keyword, fixedConfigurations) => {
-      const {
-        limit, offset, sort, order,
-      } = fixedConfigurations;
-
-      return apiGet(
-        endpoint, {
-          q: encodeURIComponent(rawQuery),
-          limit,
-          offset,
-          sort,
-          order,
-        },
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      ).then(result => result as IFormattedSearchResult);
-    },
-  );
-
-  return {
-    ...swrResult,
-    conditions: {
-      keyword,
-      rawQuery,
-      ...fixedConfigurations,
-    },
-  };
-};

+ 24 - 10
packages/app/src/styles/_search.scss

@@ -162,8 +162,7 @@
       }
     }
 
-    .search-result-keyword {
-      font-size: 17.5px;
+    .search-result-meta {
       font-weight: bold;
     }
     .search-result-select-group {
@@ -171,14 +170,14 @@
         max-width: 8rem;
       }
     }
-
-    // list group
-    .page-list {
-      // not show top label in search result list
-      .page-list-meta {
-        .top-label {
-          display: none;
-        }
+    .search-result-list-delete-checkbox {
+      margin: 0 10px 0 0;
+      vertical-align: middle;
+    }
+    // not show top label in search result list
+    .page-list-meta {
+      .top-label {
+        display: none;
       }
     }
   }
@@ -219,6 +218,21 @@
   }
 }
 
+// 2021/9/22 TODO: Remove after moving to SearchResult
+.search-page-input {
+  position: sticky;
+  top: 15px;
+  // placed at front-most
+  z-index: 15;
+
+  margin-bottom: 15px;
+
+  .input-group-btn .btn {
+    height: 34px;
+    padding: 0px 10px;
+  }
+}
+
 // support for your search
 .grw-search-table {
   caption {

+ 0 - 4
packages/app/src/styles/atoms/_custom_control.scss

@@ -1,7 +1,3 @@
-.custom-checkbox .custom-control-label::before {
-  border-radius: $border-radius !important;
-}
-
 label.custom-control-label {
   font-weight: normal;
 }

+ 0 - 0
packages/app/test/cypress/integration/0-advanced-examples/misc.spec.ts → packages/app/test/cypress/integration/2-advanced-examples/misc.spec.ts


+ 0 - 0
packages/app/test/cypress/integration/0-advanced-examples/viewport.spec.ts → packages/app/test/cypress/integration/2-advanced-examples/viewport.spec.ts


+ 0 - 103
packages/app/test/cypress/integration/2-basic-features/access-to-admin-page.spec.ts

@@ -1,103 +0,0 @@
-const ssPrefix = 'access-to-admin-page-';
-
-const adminMenues = [
-  'app', // App
-  'security', // Security
-  'security', // Security
-  'security', // Security
-  'security', // Security
-  'security', // Security
-  'security', // Security
-  'security', // Security
-  'security', // Security
-  'security', // Security
-  'security', // Security
-];
-
-context('Access to Admin page', () => {
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('/admin is successfully loaded', () => {
-    cy.visit('/admin');
-    cy.screenshot(`${ssPrefix}-admin`, { capture: 'viewport' });
-  });
-
-  it('/admin/app is successfully loaded', () => {
-    cy.visit('/admin/app');
-    cy.screenshot(`${ssPrefix}-admin-app`, { capture: 'viewport' });
-  });
-
-  it('/admin/security is successfully loaded', () => {
-    cy.visit('/admin/security');
-    cy.screenshot(`${ssPrefix}-admin-security`, { capture: 'viewport' });
-  });
-
-  it('/admin/markdown is successfully loaded', () => {
-    cy.visit('/admin/markdown');
-    cy.screenshot(`${ssPrefix}-admin-markdown`, { capture: 'viewport' });
-  });
-
-  it('/admin/customize is successfully loaded', () => {
-    cy.visit('/admin/customize');
-    cy.screenshot(`${ssPrefix}-admin-customize`, { capture: 'viewport' });
-  });
-
-  it('/admin/importer is successfully loaded', () => {
-    cy.visit('/admin/importer');
-    cy.screenshot(`${ssPrefix}-admin-importer`, { capture: 'viewport' });
-  });
-
-  it('/admin/export is successfully loaded', () => {
-    cy.visit('/admin/export');
-    cy.screenshot(`${ssPrefix}-admin-export`, { capture: 'viewport' });
-  });
-
-  it('/admin/notification is successfully loaded', () => {
-    cy.visit('/admin/notification');
-    cy.screenshot(`${ssPrefix}-admin-notification`, { capture: 'viewport' });
-  });
-
-  it('/admin/slack-integration is successfully loaded', () => {
-    cy.visit('/admin/slack-integration');
-    cy.screenshot(`${ssPrefix}-admin-slack-integration`, { capture: 'viewport' });
-  });
-
-  it('/admin/slack-integration-legacy is successfully loaded', () => {
-    cy.visit('/admin/slack-integration-legacy');
-    cy.screenshot(`${ssPrefix}-admin-slack-integration-legacy`, { capture: 'viewport' });
-  });
-
-  it('/admin/users is successfully loaded', () => {
-    cy.visit('/admin/users');
-    cy.screenshot(`${ssPrefix}-admin-users`, { capture: 'viewport' });
-  });
-
-  it('/admin/user-groups is successfully loaded', () => {
-    cy.visit('/admin/user-groups');
-    cy.screenshot(`${ssPrefix}-admin-user-groups`, { capture: 'viewport' });
-  });
-
-  it('/admin/search is successfully loaded', () => {
-    cy.visit('/admin/search');
-    cy.screenshot(`${ssPrefix}-admin-search`, { capture: 'viewport' });
-  });
-
-});

+ 0 - 28
packages/app/test/cypress/integration/2-basic-features/access-to-me-page.spec.ts

@@ -1,28 +0,0 @@
-const ssPrefix = 'access-to-page-';
-
-context('Access to page', () => {
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('/me is successfully loaded', () => {
-    cy.visit('/me', {  });
-    cy.screenshot(`${ssPrefix}-me`, { capture: 'viewport' });
-  });
-
-});

+ 1 - 31
packages/app/test/cypress/integration/2-basic-features/access-to-page.spec.ts

@@ -1,6 +1,6 @@
+const ssPrefix = 'access-to-page-';
 
 context('Access to page', () => {
-  const ssPrefix = 'access-to-page-';
 
   let connectSid: string | undefined;
 
@@ -30,34 +30,4 @@ context('Access to page', () => {
     cy.screenshot(`${ssPrefix}-sandbox-headers`, { capture: 'viewport' });
   });
 
-  it('/trash is successfully loaded', () => {
-    cy.visit('/trash', {  });
-    cy.screenshot(`${ssPrefix}-trash`, { capture: 'viewport' });
-  });
-
-  it('/Sandbox/Math is successfully loaded', () => {
-    cy.visit('/Sandbox/Math');
-    cy.screenshot(`${ssPrefix}-sandbox-math`, { capture: 'viewport' });
-  });
-
-  it('/Sandbox with edit is successfully loaded', () => {
-    cy.visit('/Sandbox#edit');
-    cy.screenshot(`${ssPrefix}-sandbox-edit-page`, { capture: 'viewport' });
-  })
-
-  it('/user/admin is successfully loaded', () => {
-    cy.visit('/user/admin', {  });
-    cy.screenshot(`${ssPrefix}-user-admin`, { capture: 'viewport' });
-  });
-
-  it('Draft page is successfully shown', () => {
-    cy.visit('/me/drafts');
-    cy.screenshot(`${ssPrefix}-draft-page`, { capture: 'viewport' });
-  });
-  
-  it('/tags is successfully loaded', () => {
-    cy.visit('/tags');
-    cy.screenshot(`${ssPrefix}-tags`, { capture: 'viewport' });
-  });
-  
 });

+ 0 - 48
packages/app/test/cypress/integration/3-search/access-to-result-page-directly.spec.ts

@@ -1,48 +0,0 @@
-context('Access to search result page directly', () => {
-  const ssPrefix = 'access-to-result-page-directly-';
-
-  let connectSid: string | undefined;
-
-  before(() => {
-    // login
-    cy.fixture("user-admin.json").then(user => {
-      cy.login(user.username, user.password);
-    });
-    cy.getCookie('connect.sid').then(cookie => {
-      connectSid = cookie?.value;
-    });
-  });
-
-  beforeEach(() => {
-    if (connectSid != null) {
-      cy.setCookie('connect.sid', connectSid);
-    }
-  });
-
-  it('/_search with "q" param is successfully loaded', () => {
-    cy.visit('/_search', { qs: { q: 'sandbox headers blockquotes' } });
-    // eslint-disable-next-line cypress/no-unnecessary-waiting
-    cy.wait(1000);
-    cy.screenshot(`${ssPrefix}-with-q`, { capture: 'viewport' });
-  });
-
-  it('checkboxes behaviors', () => {
-    cy.visit('/_search', { qs: { q: 'sandbox headers blockquotes' } });
-
-    cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-first-checkbox-on`, { capture: 'viewport' });
-    cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-first-checkbox-off`, { capture: 'viewport' });
-
-    // click select all checkbox
-    cy.getByTestid('cb-select-all').click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-1`, { capture: 'viewport' });
-    cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-2`, { capture: 'viewport' });
-    cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-3`, { capture: 'viewport' });
-    cy.getByTestid('cb-select-all').click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-4`, { capture: 'viewport' });
-  });
-
-});