瀏覽代碼

Merge branch 'imprv/88785-refactoring-page-rename-modal' into imprv/88786-refactoring-page-duplicate-modal

kaori 4 年之前
父節點
當前提交
f423dd94b4

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

@@ -22,7 +22,6 @@ import PageComments from '../components/PageComments';
 import PageContentFooter from '../components/PageContentFooter';
 import PageContentFooter from '../components/PageContentFooter';
 import PageTimeline from '../components/PageTimeline';
 import PageTimeline from '../components/PageTimeline';
 import CommentEditorLazyRenderer from '../components/PageComment/CommentEditorLazyRenderer';
 import CommentEditorLazyRenderer from '../components/PageComment/CommentEditorLazyRenderer';
-import PageManagement from '../components/Page/PageManagement';
 import ShareLinkAlert from '../components/Page/ShareLinkAlert';
 import ShareLinkAlert from '../components/Page/ShareLinkAlert';
 import DuplicatedAlert from '../components/Page/DuplicatedAlert';
 import DuplicatedAlert from '../components/Page/DuplicatedAlert';
 import RedirectedAlert from '../components/Page/RedirectedAlert';
 import RedirectedAlert from '../components/Page/RedirectedAlert';
@@ -127,7 +126,6 @@ if (pageContainer.state.pageId != null) {
   Object.assign(componentMappings, {
   Object.assign(componentMappings, {
     'page-comments-list': <PageComments />,
     'page-comments-list': <PageComments />,
     'page-comment-write': <CommentEditorLazyRenderer />,
     'page-comment-write': <CommentEditorLazyRenderer />,
-    'page-management': <PageManagement />,
     'page-content-footer': <PageContentFooter />,
     'page-content-footer': <PageContentFooter />,
 
 
     'recent-created-icon': <RecentlyCreatedIcon />,
     'recent-created-icon': <RecentlyCreatedIcon />,

+ 0 - 264
packages/app/src/components/Page/PageManagement.jsx

@@ -1,264 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { UncontrolledTooltip } from 'reactstrap';
-import { withTranslation } from 'react-i18next';
-import urljoin from 'url-join';
-
-import { pagePathUtils } from '@growi/core';
-import { usePageDeleteModal } from '~/stores/modal';
-
-import { withUnstatedContainers } from '../UnstatedUtils';
-import AppContainer from '~/client/services/AppContainer';
-import PageRenameModal from '../PageRenameModal';
-import PageDuplicateModal from '../PageDuplicateModal';
-import CreateTemplateModal from '../CreateTemplateModal';
-import PagePresentationModal from '../PagePresentationModal';
-import PresentationIcon from '../Icons/PresentationIcon';
-
-const { isTopPage } = pagePathUtils;
-
-
-const LegacyPageManagemenet = (props) => {
-  const {
-    t, appContainer, isCompactMode, pageId, revisionId, path, isDeletable, isAbleToDeleteCompletely,
-  } = props;
-
-  const { open: openDeleteModal } = usePageDeleteModal();
-
-  const { currentUser } = appContainer;
-  const isTopPagePath = isTopPage(path);
-  const [isPageRenameModalShown, setIsPageRenameModalShown] = useState(false);
-  const [isPageDuplicateModalShown, setIsPageDuplicateModalShown] = useState(false);
-  const [isPageTemplateModalShown, setIsPageTempleteModalShown] = useState(false);
-  const [isPagePresentationModalShown, setIsPagePresentationModalShown] = useState(false);
-  const presentationHref = urljoin(window.location.origin, path, '?presentation=1');
-
-  function openPageRenameModalHandler() {
-    setIsPageRenameModalShown(true);
-  }
-
-  function closePageRenameModalHandler() {
-    setIsPageRenameModalShown(false);
-  }
-
-  function openPageDuplicateModalHandler() {
-    setIsPageDuplicateModalShown(true);
-  }
-  function closePageDuplicateModalHandler() {
-    setIsPageDuplicateModalShown(false);
-  }
-
-  function openPageTemplateModalHandler() {
-    setIsPageTempleteModalShown(true);
-  }
-
-  function closePageTemplateModalHandler() {
-    setIsPageTempleteModalShown(false);
-  }
-
-  function openPagePresentationModalHandler() {
-    setIsPagePresentationModalShown(true);
-  }
-
-  function closePagePresentationModalHandler() {
-    setIsPagePresentationModalShown(false);
-  }
-
-
-  // TODO GW-2746 bulk export pages
-  // async function getArchivePageData() {
-  //   try {
-  //     const res = await appContainer.apiv3Get('page/count-children-pages', { pageId });
-  //     setTotalPages(res.data.dummy);
-  //   }
-  //   catch (err) {
-  //     setErrorMessage(t('export_bulk.failed_to_count_pages'));
-  //   }
-  // }
-
-  async function exportPageHandler(format) {
-    const url = new URL(urljoin(window.location.origin, '_api/v3/page/export', pageId));
-    url.searchParams.append('format', format);
-    url.searchParams.append('revisionId', revisionId);
-    window.location.href = url.href;
-  }
-
-  // TODO GW-2746 create api to bulk export pages
-  // function openArchiveModalHandler() {
-  //   setIsArchiveCreateModalShown(true);
-  //   getArchivePageData();
-  // }
-
-  // TODO GW-2746 create api to bulk export pages
-  // function closeArchiveCreateModalHandler() {
-  //   setIsArchiveCreateModalShown(false);
-  // }
-
-  function renderDropdownItemForTopPage() {
-    return (
-      <>
-        <button className="dropdown-item" type="button" onClick={openPageDuplicateModalHandler}>
-          <i className="icon-fw icon-docs"></i> { t('Duplicate') }
-        </button>
-        {/* TODO Presentation Mode is not function. So if it is really necessary, survey this cause and implement Presentation Mode in top page */}
-        {/* <button className="dropdown-item" type="button" onClick={openPagePresentationModalHandler}>
-          <i className="icon-fw"><PresentationIcon /></i><span className="d-none d-sm-inline"> { t('Presentation Mode') }</span>
-        </button> */}
-        <button type="button" className="dropdown-item" onClick={() => { exportPageHandler('md') }}>
-          <i className="icon-fw icon-cloud-download"></i>{t('export_bulk.export_page_markdown')}
-        </button>
-        <div className="dropdown-divider"></div>
-      </>
-    );
-  }
-
-  function renderDropdownItemForNotTopPage() {
-    return (
-      <>
-        <button className="dropdown-item" type="button" onClick={openPageRenameModalHandler}>
-          <i className="icon-fw icon-action-redo"></i> { t('Move/Rename') }
-        </button>
-        <button className="dropdown-item" type="button" onClick={openPageDuplicateModalHandler}>
-          <i className="icon-fw icon-docs"></i> { t('Duplicate') }
-        </button>
-        <button className="dropdown-item" type="button" onClick={openPagePresentationModalHandler}>
-          <i className="icon-fw"><PresentationIcon /></i> { t('Presentation Mode') }
-        </button>
-        <button className="dropdown-item" type="button" onClick={() => { exportPageHandler('md') }}>
-          <i className="icon-fw icon-cloud-download"></i>{t('export_bulk.export_page_markdown')}
-        </button>
-        {/* TODO GW-2746 create api to bulk export pages */}
-        {/* <button className="dropdown-item" type="button" onClick={openArchiveModalHandler}>
-          <i className="icon-fw"></i>{t('Create Archive Page')}
-        </button> */}
-        <div className="dropdown-divider"></div>
-      </>
-    );
-  }
-
-  function generatePageObjectToDelete() {
-    return { pageId, revisionId, path };
-  }
-  const pageToDelete = generatePageObjectToDelete();
-
-  function renderDropdownItemForDeletablePage() {
-    return (
-      <>
-        <div className="dropdown-divider"></div>
-        <button className="dropdown-item text-danger" type="button" onClick={() => openDeleteModal([pageToDelete])}>
-          <i className="icon-fw icon-fire"></i> { t('Delete') }
-        </button>
-      </>
-    );
-  }
-
-
-  function renderModals() {
-    if (currentUser == null) {
-      return null;
-    }
-
-    return (
-      <>
-        <PageRenameModal
-          isOpen={isPageRenameModalShown}
-          onClose={closePageRenameModalHandler}
-          pageId={pageId}
-          revisionId={revisionId}
-          path={path}
-        />
-        <PageDuplicateModal
-          isOpen={isPageDuplicateModalShown}
-          onClose={closePageDuplicateModalHandler}
-          pageId={pageId}
-          path={path}
-        />
-        <CreateTemplateModal
-          path={path}
-          isOpen={isPageTemplateModalShown}
-          onClose={closePageTemplateModalHandler}
-        />
-        <PagePresentationModal
-          isOpen={isPagePresentationModalShown}
-          onClose={closePagePresentationModalHandler}
-          href={presentationHref}
-        />
-      </>
-    );
-  }
-
-  function renderDotsIconForCurrentUser() {
-    return (
-      <>
-        <button
-          type="button"
-          className="btn-link nav-link dropdown-toggle dropdown-toggle-no-caret border-0 rounded btn-page-item-control"
-          data-toggle="dropdown"
-        >
-          <i className="text-muted icon-options"></i>
-        </button>
-      </>
-    );
-  }
-
-  function renderDotsIconForGuestUser() {
-    return (
-      <>
-        <button
-          type="button"
-          className="btn nav-link bg-transparent dropdown-toggle dropdown-toggle-no-caret disabled"
-          id="icon-options-guest-tltips"
-        >
-          <i className="text-muted icon-options"></i>
-        </button>
-        <UncontrolledTooltip placement="top" target="icon-options-guest-tltips" fade={false}>
-          {t('Not available for guest')}
-        </UncontrolledTooltip>
-      </>
-    );
-  }
-
-
-  return (
-    <>
-      {currentUser == null ? renderDotsIconForGuestUser() : renderDotsIconForCurrentUser()}
-      <div className="dropdown-menu dropdown-menu-right">
-        {isTopPagePath ? renderDropdownItemForTopPage() : renderDropdownItemForNotTopPage()}
-        <button className="dropdown-item" type="button" onClick={openPageTemplateModalHandler}>
-          <i className="icon-fw icon-magic-wand"></i> { t('template.option_label.create/edit') }
-        </button>
-        {(!isTopPagePath && isDeletable) && renderDropdownItemForDeletablePage()}
-      </div>
-      {renderModals()}
-    </>
-  );
-};
-
-/**
- * Wrapper component for using unstated
- */
-const LegacyPageManagemenetWrapper = withUnstatedContainers(LegacyPageManagemenet, [AppContainer]);
-
-
-LegacyPageManagemenet.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-
-  pageId: PropTypes.string.isRequired,
-  revisionId: PropTypes.string.isRequired,
-  path: PropTypes.string.isRequired,
-  isDeletable: PropTypes.bool.isRequired,
-  isAbleToDeleteCompletely: PropTypes.bool,
-
-  isCompactMode: PropTypes.bool,
-};
-
-LegacyPageManagemenet.defaultProps = {
-  isCompactMode: false,
-};
-
-const PageManagement = (props) => {
-  return <LegacyPageManagemenetWrapper {...props}></LegacyPageManagemenetWrapper>;
-};
-export default withTranslation()(PageManagement);

+ 10 - 5
packages/app/src/components/PrivateLegacyPages.tsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useCallback, useEffect, useMemo, useRef, useState,
+  useCallback, useMemo, useRef, useState,
 } from 'react';
 } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
@@ -14,12 +14,14 @@ import { toastSuccess } from '~/client/util/apiNotification';
 import {
 import {
   ISearchConfigurations, useSWRxNamedQuerySearch,
   ISearchConfigurations, useSWRxNamedQuerySearch,
 } from '~/stores/search';
 } from '~/stores/search';
-import { ILegacyPrivatePage, useLegacyPrivatePagesMigrationModal } from '~/stores/modal';
+import {
+  ILegacyPrivatePage, useLegacyPrivatePagesMigrationModal,
+} from '~/stores/modal';
 
 
 import PaginationWrapper from './PaginationWrapper';
 import PaginationWrapper from './PaginationWrapper';
 import { OperateAllControl } from './SearchPage/OperateAllControl';
 import { OperateAllControl } from './SearchPage/OperateAllControl';
 
 
-import { IReturnSelectedPageIds, SearchPageBase } from './SearchPage2/SearchPageBase';
+import { IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage2/SearchPageBase';
 import { MenuItemType } from './Common/Dropdown/PageItemControl';
 import { MenuItemType } from './Common/Dropdown/PageItemControl';
 import { LegacyPrivatePagesMigrationModal } from './LegacyPrivatePagesMigrationModal';
 import { LegacyPrivatePagesMigrationModal } from './LegacyPrivatePagesMigrationModal';
 
 
@@ -176,6 +178,9 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
     }
     }
   }, []);
   }, []);
 
 
+  // for bulk deletion
+  const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate);
+
   const convertMenuItemClickedHandler = useCallback(() => {
   const convertMenuItemClickedHandler = useCallback(() => {
     if (data == null) {
     if (data == null) {
       return;
       return;
@@ -238,7 +243,7 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
                     <i className="icon-fw icon-refresh"></i>
                     <i className="icon-fw icon-refresh"></i>
                     {t('private_legacy_pages.convert_all_selected_pages')}
                     {t('private_legacy_pages.convert_all_selected_pages')}
                   </DropdownItem>
                   </DropdownItem>
-                  <DropdownItem onClick={() => { /* TODO: implement */ }}>
+                  <DropdownItem onClick={deleteAllButtonClickedHandler}>
                     <span className="text-danger">
                     <span className="text-danger">
                       <i className="icon-fw icon-trash"></i>
                       <i className="icon-fw icon-trash"></i>
                       {t('search_result.delete_all_selected_page')}
                       {t('search_result.delete_all_selected_page')}
@@ -251,7 +256,7 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
         </div>
         </div>
       </div>
       </div>
     );
     );
-  }, [convertMenuItemClickedHandler, hitsCount, isControlEnabled, selectAllCheckboxChangedHandler, t]);
+  }, [convertMenuItemClickedHandler, deleteAllButtonClickedHandler, hitsCount, isControlEnabled, selectAllCheckboxChangedHandler, t]);
 
 
   const searchResultListHead = useMemo(() => {
   const searchResultListHead = useMemo(() => {
     if (data == null) {
     if (data == null) {

+ 20 - 8
packages/app/src/components/SearchPage.tsx

@@ -15,7 +15,7 @@ import PaginationWrapper from './PaginationWrapper';
 import { OperateAllControl } from './SearchPage/OperateAllControl';
 import { OperateAllControl } from './SearchPage/OperateAllControl';
 import SearchControl from './SearchPage/SearchControl';
 import SearchControl from './SearchPage/SearchControl';
 
 
-import { SearchPageBase } from './SearchPage2/SearchPageBase';
+import { IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage2/SearchPageBase';
 
 
 
 
 // TODO: replace with "customize:showPageLimitationS"
 // TODO: replace with "customize:showPageLimitationS"
@@ -115,11 +115,11 @@ export const SearchPage = (props: Props): JSX.Element => {
   });
   });
 
 
   const selectAllControlRef = useRef<ISelectableAndIndeterminatable|null>(null);
   const selectAllControlRef = useRef<ISelectableAndIndeterminatable|null>(null);
-  const searchPageBaseRef = useRef<ISelectableAll|null>(null);
+  const searchPageBaseRef = useRef<ISelectableAll & IReturnSelectedPageIds|null>(null);
 
 
   const { data: isSearchServiceReachable } = useIsSearchServiceReachable();
   const { data: isSearchServiceReachable } = useIsSearchServiceReachable();
 
 
-  const { data, conditions } = useSWRxFullTextSearch(keyword, {
+  const { data, conditions, mutate } = useSWRxFullTextSearch(keyword, {
     limit: INITIAL_PAGIONG_SIZE,
     limit: INITIAL_PAGIONG_SIZE,
     ...configurationsByControl,
     ...configurationsByControl,
     ...configurationsByPagination,
     ...configurationsByPagination,
@@ -163,13 +163,22 @@ export const SearchPage = (props: Props): JSX.Element => {
     }
     }
   }, []);
   }, []);
 
 
+  const pagingSizeChangedHandler = useCallback((pagingSize: number) => {
+    setConfigurationsByPagination({
+      ...configurationsByPagination,
+      limit: pagingSize,
+    });
+    mutate();
+  }, [configurationsByPagination, mutate]);
+
   const pagingNumberChangedHandler = useCallback((activePage: number) => {
   const pagingNumberChangedHandler = useCallback((activePage: number) => {
     const currentLimit = configurationsByPagination.limit ?? INITIAL_PAGIONG_SIZE;
     const currentLimit = configurationsByPagination.limit ?? INITIAL_PAGIONG_SIZE;
     setConfigurationsByPagination({
     setConfigurationsByPagination({
       ...configurationsByPagination,
       ...configurationsByPagination,
       offset: (activePage - 1) * currentLimit,
       offset: (activePage - 1) * currentLimit,
     });
     });
-  }, [configurationsByPagination]);
+    mutate();
+  }, [configurationsByPagination, mutate]);
 
 
   const initialSearchConditions: Partial<ISearchConditions> = useMemo(() => {
   const initialSearchConditions: Partial<ISearchConditions> = useMemo(() => {
     return {
     return {
@@ -178,6 +187,9 @@ export const SearchPage = (props: Props): JSX.Element => {
     };
     };
   }, [initQ]);
   }, [initQ]);
 
 
+  // for bulk deletion
+  const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate);
+
   // push state
   // push state
   useEffect(() => {
   useEffect(() => {
     const newUrl = new URL('/_search', 'http://example.com');
     const newUrl = new URL('/_search', 'http://example.com');
@@ -201,14 +213,14 @@ export const SearchPage = (props: Props): JSX.Element => {
           type="button"
           type="button"
           className="btn btn-outline-danger border-0 px-2"
           className="btn btn-outline-danger border-0 px-2"
           disabled={isDisabled}
           disabled={isDisabled}
-          onClick={() => null /* TODO implement */}
+          onClick={deleteAllButtonClickedHandler}
         >
         >
           <i className="icon-fw icon-trash"></i>
           <i className="icon-fw icon-trash"></i>
           {t('search_result.delete_all_selected_page')}
           {t('search_result.delete_all_selected_page')}
         </button>
         </button>
       </OperateAllControl>
       </OperateAllControl>
     );
     );
-  }, [hitsCount, selectAllCheckboxChangedHandler, t]);
+  }, [deleteAllButtonClickedHandler, hitsCount, selectAllCheckboxChangedHandler, t]);
 
 
   const searchControl = useMemo(() => {
   const searchControl = useMemo(() => {
     if (!isSearchServiceReachable) {
     if (!isSearchServiceReachable) {
@@ -235,10 +247,10 @@ export const SearchPage = (props: Props): JSX.Element => {
         searchingKeyword={keyword}
         searchingKeyword={keyword}
         offset={offset}
         offset={offset}
         pagingSize={limit}
         pagingSize={limit}
-        onPagingSizeChanged={() => {}}
+        onPagingSizeChanged={pagingSizeChangedHandler}
       />
       />
     );
     );
-  }, [data, keyword, limit, offset]);
+  }, [data, keyword, limit, offset, pagingSizeChangedHandler]);
 
 
   const searchPager = useMemo(() => {
   const searchPager = useMemo(() => {
     // when pager is not needed
     // when pager is not needed

+ 62 - 2
packages/app/src/components/SearchPage2/SearchPageBase.tsx

@@ -1,16 +1,22 @@
 import React, {
 import React, {
-  forwardRef, ForwardRefRenderFunction, useEffect, useImperativeHandle, useRef, useState,
+  forwardRef, ForwardRefRenderFunction, useCallback, useEffect, useImperativeHandle, useRef, useState,
 } from 'react';
 } from 'react';
+import { useTranslation } from 'react-i18next';
 import { ISelectableAll } from '~/client/interfaces/selectable-all';
 import { ISelectableAll } from '~/client/interfaces/selectable-all';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
+import { toastSuccess } from '~/client/util/apiNotification';
 import { IPageWithMeta } from '~/interfaces/page';
 import { IPageWithMeta } from '~/interfaces/page';
-import { IPageSearchMeta } from '~/interfaces/search';
+import { IFormattedSearchResult, IPageSearchMeta } from '~/interfaces/search';
+import { OnDeletedFunction } from '~/interfaces/ui';
 import { useIsGuestUser, useIsSearchServiceConfigured, useIsSearchServiceReachable } from '~/stores/context';
 import { useIsGuestUser, useIsSearchServiceConfigured, useIsSearchServiceReachable } from '~/stores/context';
+import { IPageForPageDeleteModal, usePageDeleteModal } from '~/stores/modal';
+import { usePageTreeTermManager } from '~/stores/page-listing';
 import { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 import { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 
 
 import { SearchResultContent } from '../SearchPage/SearchResultContent';
 import { SearchResultContent } from '../SearchPage/SearchResultContent';
 import { SearchResultList } from '../SearchPage/SearchResultList';
 import { SearchResultList } from '../SearchPage/SearchResultList';
 
 
+
 export interface IReturnSelectedPageIds {
 export interface IReturnSelectedPageIds {
   getSelectedPageIds?: () => Set<string>,
   getSelectedPageIds?: () => Set<string>,
 }
 }
@@ -211,4 +217,58 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
 };
 };
 
 
 
 
+type VoidFunction = () => void;
+
+export const usePageDeleteModalForBulkDeletion = (
+    data: IFormattedSearchResult | undefined,
+    ref: React.MutableRefObject<(ISelectableAll & IReturnSelectedPageIds) | null>,
+    onDeleted?: OnDeletedFunction,
+): VoidFunction => {
+
+  const { t } = useTranslation();
+
+  const { open: openDeleteModal } = usePageDeleteModal();
+
+  // for PageTree mutation
+  const { advance: advancePt } = usePageTreeTermManager();
+
+  return () => {
+    if (data == null) {
+      return;
+    }
+
+    const instance = ref.current;
+    if (instance == null || instance.getSelectedPageIds == null) {
+      return;
+    }
+
+    const selectedPageIds = instance.getSelectedPageIds();
+
+    if (selectedPageIds.size === 0) {
+      return;
+    }
+
+    const selectedPages = data.data
+      .filter(pageWithMeta => selectedPageIds.has(pageWithMeta.pageData._id))
+      .map(pageWithMeta => ({
+        pageId: pageWithMeta.pageData._id,
+        path: pageWithMeta.pageData.path,
+        revisionId: pageWithMeta.pageData.revision as string,
+      } as IPageForPageDeleteModal));
+
+    openDeleteModal(selectedPages, {
+      onDeleted: (...args) => {
+        toastSuccess(args[2] ? t('deleted_pages_completely') : t('deleted_pages'));
+        advancePt();
+
+        if (onDeleted != null) {
+          onDeleted(...args);
+        }
+      },
+    });
+  };
+
+};
+
+
 export const SearchPageBase = forwardRef(SearchPageBaseSubstance);
 export const SearchPageBase = forwardRef(SearchPageBaseSubstance);

+ 1 - 1
packages/app/src/server/models/page.ts

@@ -578,7 +578,7 @@ schema.statics.findByPageIdsToEdit = async function(ids, user, shouldIncludeEmpt
 
 
   await this.addConditionToFilteringByViewerToEdit(builder, user);
   await this.addConditionToFilteringByViewerToEdit(builder, user);
 
 
-  const pages = await builder.query.lean().exec();
+  const pages = await builder.query.exec();
 
 
   return pages;
   return pages;
 };
 };