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

Merge pull request #2912 from weseek/feat/display-BookMarkList-including-pagination-as-component

Feat/display book mark list including pagination as component
Yuki Takei 5 лет назад
Родитель
Сommit
16f943286d

+ 15 - 2
resource/locales/en_US/admin/admin.json

@@ -107,8 +107,21 @@
       "tab_switch_desc2": "By invalidating, you can make page transition as the only object for forward/back command of the browser.",
       "tab_switch_desc2": "By invalidating, you can make page transition as the only object for forward/back command of the browser.",
       "attach_title_header": "Add h1 section when create new page automatically",
       "attach_title_header": "Add h1 section when create new page automatically",
       "attach_title_header_desc": "Add page path to the first line as h1 section when create new page",
       "attach_title_header_desc": "Add page path to the first line as h1 section when create new page",
-      "recent_created__n_draft_num_desc": "Number of recently created pages & drafts displayed",
-      "recently_created_n_draft_num_desc": "Number of recently created pages and drafts displayed on user page",
+
+      "list_num_s": "Number of list displayed on modals",
+      "list_num_desc_s": "Set number of list per page such as 'Pagelist', 'Timeline', 'Page History' and 'Share Link' pages",
+
+      "list_num_m": "Number of list displayed on article pages included other contents",
+      "list_num_desc_m": "Set number of list per page such as 'Bookmarks' and 'Recently created' pages",
+
+      "list_num_l": "Number of list displayed on 'Search' pages",
+      "list_num_desc_l": "Set number of list per page such as 'Search' pages",
+
+      "list_num_xl": "Number of list displayed on article pages",
+      "list_num_desc_xl": "Set number of list per page such as 'Not found' and 'Trash' pages",
+
+
+
       "stale_notification": "Display notification on stale pages",
       "stale_notification": "Display notification on stale pages",
       "stale_notification_desc": "Displays the notification to pages more than 1 year since the last update.",
       "stale_notification_desc": "Displays the notification to pages more than 1 year since the last update.",
       "show_all_reply_comments": "Show all reply comments",
       "show_all_reply_comments": "Show all reply comments",

+ 13 - 2
resource/locales/ja_JP/admin/admin.json

@@ -107,8 +107,19 @@
       "tab_switch_desc2": "無効化することで、ページ遷移のみを戻る/進む操作の対象にすることができます。",
       "tab_switch_desc2": "無効化することで、ページ遷移のみを戻る/進む操作の対象にすることができます。",
       "attach_title_header": "新規ページ作成時の h1 セクション自動挿入",
       "attach_title_header": "新規ページ作成時の h1 セクション自動挿入",
       "attach_title_header_desc": "新規作成したページの1行目に、ページのパスを h1 セクションとして挿入します。",
       "attach_title_header_desc": "新規作成したページの1行目に、ページのパスを h1 セクションとして挿入します。",
-      "recent_created__n_draft_num_desc": "最近作成したページと下書きの表示数",
-      "recently_created_n_draft_num_desc": "ホーム画面の Recently Created での、1ページの表示数を設定します。",
+
+      "list_num_s": "モーダルに表示されるリスト数",
+      "list_num_desc_s": "モーダルにおける <Pagelist> <Timeline> <Page History> <Share Link>での、1ページあたりの表示数を設定します。",
+
+      "list_num_m": "ユーザーページに表示されるリスト数",
+      "list_num_desc_m": "ユーザーページにおける <Bookmarks> <Recently Created>での、1ページあたりの表示数を設定します。",
+
+      "list_num_l": "検索ページに表示されるリスト数",
+      "list_num_desc_l": "<Search>での、1ページあたりの表示数を設定します。",
+
+      "list_num_xl": "Not FoundページやTrashページに表示されるリスト数",
+      "list_num_desc_xl": "記事エリアにおける<Not Found> <Trash>での、1ページあたりの表示数を設定します。",
+
       "stale_notification": "古いページに通知を表示する",
       "stale_notification": "古いページに通知を表示する",
       "stale_notification_desc": "最後の更新から1年を超えるページへの通知を表示します。",
       "stale_notification_desc": "最後の更新から1年を超えるページへの通知を表示します。",
       "show_all_reply_comments": "返信コメントを全て表示する",
       "show_all_reply_comments": "返信コメントを全て表示する",

+ 14 - 3
resource/locales/zh_CN/admin/admin.json

@@ -118,9 +118,20 @@
 			"tab_switch_desc1": "在浏览器中保存编辑选项卡和历史选项卡切换,并使其成为浏览器的前向/后向命令的对象。",
 			"tab_switch_desc1": "在浏览器中保存编辑选项卡和历史选项卡切换,并使其成为浏览器的前向/后向命令的对象。",
 			"tab_switch_desc2": "通过失效,您可以将页面转换作为浏览器的前向/后向命令的唯一对象。",
 			"tab_switch_desc2": "通过失效,您可以将页面转换作为浏览器的前向/后向命令的唯一对象。",
 			"attach_title_header": "自动创建新页面时添加h1节",
 			"attach_title_header": "自动创建新页面时添加h1节",
-			"attach_title_header_desc": "创建新页面时,将页面路径作为h1节添加到第一行",
-			"recent_created__n_draft_num_desc": "显示最近创建的页数和草稿数",
-			"recently_created_n_draft_num_desc": "用户页上显示的最近创建的页和草稿数",
+      "attach_title_header_desc": "创建新页面时,将页面路径作为h1节添加到第一行",
+
+      "list_num_s": "Number of list displayed on modals",
+      "list_num_desc_s": "Set number of list per page such as 'Pagelist', 'Timeline', 'Page History' and 'Share Link' pages",
+
+      "list_num_m": "Number of list displayed on article pages included other contents",
+      "list_num_desc_m": "Set number of list per page such as 'Bookmarks' and 'Recently created' pages",
+
+      "list_num_l": "Number of list displayed on 'Search' pages",
+      "list_num_desc_l": "Set number of list per page such as 'Search' pages",
+
+      "list_num_xl": "Number of list displayed on article pages",
+      "list_num_desc_xl": "Set number of list per page such as 'Not found' and 'Trash' pages",
+
 			"stale_notification": "在过期页上显示通知",
 			"stale_notification": "在过期页上显示通知",
 			"stale_notification_desc": "显示自上次更新以来超过1年的页面通知。",
 			"stale_notification_desc": "显示自上次更新以来超过1年的页面通知。",
 			"show_all_reply_comments": "显示所有回复评论",
 			"show_all_reply_comments": "显示所有回复评论",

+ 4 - 2
src/client/js/app.jsx

@@ -21,7 +21,7 @@ import PageManagement from './components/Page/PageManagement';
 import TrashPageAlert from './components/Page/TrashPageAlert';
 import TrashPageAlert from './components/Page/TrashPageAlert';
 import PageStatusAlert from './components/PageStatusAlert';
 import PageStatusAlert from './components/PageStatusAlert';
 import RecentCreated from './components/RecentCreated/RecentCreated';
 import RecentCreated from './components/RecentCreated/RecentCreated';
-import MyDraftList from './components/MyDraftList/MyDraftList';
+import MyBookmarkList from './components/MyBookmarkList/MyBookmarkList';
 import SeenUserList from './components/User/SeenUserList';
 import SeenUserList from './components/User/SeenUserList';
 import LikerList from './components/User/LikerList';
 import LikerList from './components/User/LikerList';
 import TableOfContents from './components/TableOfContents';
 import TableOfContents from './components/TableOfContents';
@@ -93,7 +93,9 @@ if (pageContainer.state.pageId != null) {
     'seen-user-list': <SeenUserList />,
     'seen-user-list': <SeenUserList />,
     'liker-list': <LikerList />,
     'liker-list': <LikerList />,
 
 
-    'user-draft-list': <MyDraftList />,
+    'user-bookmark-list': <MyBookmarkList />,
+    'user-created-list': <RecentCreated />,
+    // 'user-draft-list': <MyDraftList />,
   });
   });
 }
 }
 if (pageContainer.state.creator != null) {
 if (pageContainer.state.creator != null) {

+ 30 - 38
src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx

@@ -1,10 +1,7 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
-import {
-  Card, CardBody,
-  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
-} from 'reactstrap';
+import { Card, CardBody } from 'reactstrap';
 
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -14,6 +11,7 @@ import AppContainer from '../../../services/AppContainer';
 import AdminCustomizeContainer from '../../../services/AdminCustomizeContainer';
 import AdminCustomizeContainer from '../../../services/AdminCustomizeContainer';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import CustomizeFunctionOption from './CustomizeFunctionOption';
 import CustomizeFunctionOption from './CustomizeFunctionOption';
+import PagingSizeUncontrolledDropdown from './PagingSizeUncontrolledDropdown';
 
 
 class CustomizeFunctionSetting extends React.Component {
 class CustomizeFunctionSetting extends React.Component {
 
 
@@ -21,17 +19,10 @@ class CustomizeFunctionSetting extends React.Component {
     super(props);
     super(props);
 
 
     this.state = {
     this.state = {
-      isDropdownOpen: false,
     };
     };
-
-    this.onToggleDropdown = this.onToggleDropdown.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
   }
   }
 
 
-  onToggleDropdown() {
-    this.setState({ isDropdownOpen: !this.state.isDropdownOpen });
-  }
-
   async onClickSubmit() {
   async onClickSubmit() {
     const { t, adminCustomizeContainer } = this.props;
     const { t, adminCustomizeContainer } = this.props;
 
 
@@ -74,7 +65,6 @@ class CustomizeFunctionSetting extends React.Component {
                 </CustomizeFunctionOption>
                 </CustomizeFunctionOption>
               </div>
               </div>
             </div>
             </div>
-
             <div className="form-group row">
             <div className="form-group row">
               <div className="offset-md-3 col-md-6 text-left">
               <div className="offset-md-3 col-md-6 text-left">
                 <CustomizeFunctionOption
                 <CustomizeFunctionOption
@@ -90,32 +80,34 @@ class CustomizeFunctionSetting extends React.Component {
               </div>
               </div>
             </div>
             </div>
 
 
-            <div className="form-group row">
-              <div className="offset-md-3 col-md-6 text-left">
-                <div className="my-0 w-100">
-                  <label>{t('admin:customize_setting.function_options.recent_created__n_draft_num_desc')}</label>
-                </div>
-                <Dropdown isOpen={this.state.isDropdownOpen} toggle={this.onToggleDropdown}>
-                  <DropdownToggle className="text-right col-6" caret>
-                    <span className="float-left">{adminCustomizeContainer.state.currentRecentCreatedLimit}</span>
-                  </DropdownToggle>
-                  <DropdownMenu className="dropdown-menu" role="menu">
-                    <DropdownItem key={10} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(10) }}>
-                      <a role="menuitem">10</a>
-                    </DropdownItem>
-                    <DropdownItem key={30} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(30) }}>
-                      <a role="menuitem">30</a>
-                    </DropdownItem>
-                    <DropdownItem key={50} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(50) }}>
-                      <a role="menuitem">50</a>
-                    </DropdownItem>
-                  </DropdownMenu>
-                </Dropdown>
-                <p className="form-text text-muted">
-                  {t('admin:customize_setting.function_options.recently_created_n_draft_num_desc')}
-                </p>
-              </div>
-            </div>
+            <PagingSizeUncontrolledDropdown
+              label={t('admin:customize_setting.function_options.list_num_s')}
+              desc={t('admin:customize_setting.function_options.list_num_desc_s')}
+              toggleLabel={adminCustomizeContainer.state.pageLimitationS}
+              dropdownItemSize={[10, 20, 50, 100]}
+              onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationS}
+            />
+            <PagingSizeUncontrolledDropdown
+              label={t('admin:customize_setting.function_options.list_num_m')}
+              desc={t('admin:customize_setting.function_options.list_num_desc_m')}
+              toggleLabel={adminCustomizeContainer.state.pageLimitationM}
+              dropdownItemSize={[5, 10, 20, 50, 100]}
+              onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationM}
+            />
+            <PagingSizeUncontrolledDropdown
+              label={t('admin:customize_setting.function_options.list_num_l')}
+              desc={t('admin:customize_setting.function_options.list_num_desc_l')}
+              toggleLabel={adminCustomizeContainer.state.pageLimitationL}
+              dropdownItemSize={[20, 50, 100, 200]}
+              onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationL}
+            />
+            <PagingSizeUncontrolledDropdown
+              label={t('admin:customize_setting.function_options.list_num_xl')}
+              desc={t('admin:customize_setting.function_options.list_num_desc_xl')}
+              toggleLabel={adminCustomizeContainer.state.pageLimitationXL}
+              dropdownItemSize={[5, 10, 20, 50, 100]}
+              onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationXL}
+            />
 
 
             <div className="form-group row">
             <div className="form-group row">
               <div className="offset-md-3 col-md-6 text-left">
               <div className="offset-md-3 col-md-6 text-left">

+ 58 - 0
src/client/js/components/Admin/Customize/PagingSizeUncontrolledDropdown.jsx

@@ -0,0 +1,58 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+import {
+  UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem,
+} from 'reactstrap';
+
+
+const PagingSizeUncontrolledDropdown = (props) => {
+
+  function dropdownItemOnClickHandler(num) {
+    if (props.onChangeDropdownItem === null) {
+      return;
+    }
+    props.onChangeDropdownItem(num);
+  }
+
+  return (
+    <React.Fragment>
+      <div className="form-group row">
+        <div className="offset-md-3 col-md-6 text-left">
+          <div className="my-0 w-100">
+            <label>{props.label}</label>
+          </div>
+          <UncontrolledDropdown>
+            <DropdownToggle className="text-right col-6" caret>
+              <span className="float-left">{props.toggleLabel}</span>
+            </DropdownToggle>
+            <DropdownMenu className="dropdown-menu" role="menu">
+              {props.dropdownItemSize.map((num) => {
+                return (
+                  <DropdownItem key={num} role="presentation" onClick={() => dropdownItemOnClickHandler(num)}>
+                    <a role="menuitem">{num}</a>
+                  </DropdownItem>
+                );
+              })}
+            </DropdownMenu>
+          </UncontrolledDropdown>
+          <p className="form-text text-muted">
+            {props.desc}
+          </p>
+        </div>
+      </div>
+    </React.Fragment>
+  );
+};
+
+
+PagingSizeUncontrolledDropdown.propTypes = {
+  t: PropTypes.func.isRequired, //  i18next
+  label: PropTypes.string,
+  toggleLabel: PropTypes.number,
+  dropdownItemSize: PropTypes.array,
+  desc: PropTypes.string,
+  onChangeDropdownItem: PropTypes.func,
+};
+
+export default withTranslation()(PagingSizeUncontrolledDropdown);

+ 109 - 0
src/client/js/components/MyBookmarkList/MyBookmarkList.jsx

@@ -0,0 +1,109 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import loggerFactory from '@alias/logger';
+import { withUnstatedContainers } from '../UnstatedUtils';
+
+
+import AppContainer from '../../services/AppContainer';
+import PageContainer from '../../services/PageContainer';
+import { toastError } from '../../util/apiNotification';
+
+import PaginationWrapper from '../PaginationWrapper';
+
+import Page from '../PageList/Page';
+
+const logger = loggerFactory('growi:MyBookmarkList');
+class MyBookmarkList extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      pages: [],
+      activePage: 1,
+      totalPages: 0,
+      pagingLimit: null,
+    };
+
+    this.handlePage = this.handlePage.bind(this);
+  }
+
+  componentWillMount() {
+    this.getMyBookmarkList(1);
+  }
+
+  async handlePage(selectPageNumber) {
+    await this.getMyBookmarkList(selectPageNumber);
+  }
+
+  async getMyBookmarkList(selectPageNumber) {
+    const { appContainer } = this.props;
+    const userId = appContainer.currentUserId;
+    const page = selectPageNumber;
+
+    try {
+      const { data } = await this.props.appContainer.apiv3.get(`/bookmarks/${userId}`, { page });
+      if (data.paginationResult == null) {
+        throw new Error('data must conclude \'paginateResult\' property.');
+      }
+      const {
+        docs: pages, totalDocs: totalPages, limit: pagingLimit, page: activePage,
+      } = data.paginationResult;
+      this.setState({
+        pages,
+        totalPages,
+        pagingLimit,
+        activePage,
+      });
+    }
+    catch (error) {
+      logger.error('failed to fetch data', error);
+      toastError(error, 'Error occurred in bookmark page list');
+    }
+  }
+
+  /**
+   * generate Elements of Page
+   *
+   * @param {any} pages Array of pages Model Obj
+   *
+   */
+  generatePageList(pages) {
+    return pages.map(page => (
+      <li key={`my-bookmarks:${page._id}`}>
+        <Page page={page.page} />
+      </li>
+    ));
+  }
+
+
+  render() {
+    return (
+      <div className="page-list-container-create">
+        <ul className="page-list-ul page-list-ul-flat mb-3">
+          {this.generatePageList(this.state.pages)}
+        </ul>
+        <PaginationWrapper
+          activePage={this.state.activePage}
+          changePage={this.handlePage}
+          totalItemsCount={this.state.totalPages}
+          pagingLimit={this.state.pagingLimit}
+        />
+      </div>
+    );
+  }
+
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const MyBookmarkListWrapper = withUnstatedContainers(MyBookmarkList, [AppContainer, PageContainer]);
+
+MyBookmarkList.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+};
+
+export default MyBookmarkListWrapper;

+ 3 - 3
src/client/js/components/MyDraftList/MyDraftList.jsx

@@ -22,6 +22,7 @@ class MyDraftList extends React.Component {
       currentDrafts: [],
       currentDrafts: [],
       activePage: 1,
       activePage: 1,
       totalDrafts: 0,
       totalDrafts: 0,
+      // [TODO: rename pageLimitationM to pageLimitationL]
       pagingLimit: Infinity,
       pagingLimit: Infinity,
     };
     };
 
 
@@ -67,9 +68,8 @@ class MyDraftList extends React.Component {
   }
   }
 
 
   getCurrentDrafts(selectPageNumber) {
   getCurrentDrafts(selectPageNumber) {
-    const { appContainer } = this.props;
-
-    const limit = appContainer.getConfig().recentCreatedLimit;
+    // TODO implement temporarily paging number only this component (this paging size is pageLimitationL).
+    const limit = this.state.pagingLimit;
 
 
     const totalDrafts = this.state.drafts.length;
     const totalDrafts = this.state.drafts.length;
     const activePage = selectPageNumber;
     const activePage = selectPageNumber;

+ 6 - 11
src/client/js/components/PageAttachment.jsx

@@ -17,8 +17,8 @@ class PageAttachment extends React.Component {
 
 
     this.state = {
     this.state = {
       activePage: 1,
       activePage: 1,
-      limit: 10,
       totalAttachments: 0,
       totalAttachments: 0,
+      limit: null,
       attachments: [],
       attachments: [],
       inUse: {},
       inUse: {},
       attachmentToDelete: null,
       attachmentToDelete: null,
@@ -34,27 +34,24 @@ class PageAttachment extends React.Component {
 
 
   async handlePage(selectedPage) {
   async handlePage(selectedPage) {
     const { pageId } = this.props.pageContainer.state;
     const { pageId } = this.props.pageContainer.state;
-    const { limit } = this.state;
-    const offset = (selectedPage - 1) * limit;
-    const activePage = selectedPage;
+    const page = selectedPage;
 
 
     if (!pageId) { return }
     if (!pageId) { return }
 
 
-    const res = await this.props.appContainer.apiv3Get('/attachment/list', {
-      pageId, limit, offset,
-    });
+    const res = await this.props.appContainer.apiv3Get('/attachment/list', { pageId, page });
     const attachments = res.data.paginateResult.docs;
     const attachments = res.data.paginateResult.docs;
     const totalAttachments = res.data.paginateResult.totalDocs;
     const totalAttachments = res.data.paginateResult.totalDocs;
+    const pagingLimit = res.data.paginateResult.limit;
 
 
     const inUse = {};
     const inUse = {};
 
 
     for (const attachment of attachments) {
     for (const attachment of attachments) {
       inUse[attachment._id] = this.checkIfFileInUse(attachment);
       inUse[attachment._id] = this.checkIfFileInUse(attachment);
     }
     }
-
     this.setState({
     this.setState({
-      activePage,
+      activePage: selectedPage,
       totalAttachments,
       totalAttachments,
+      limit: pagingLimit,
       attachments,
       attachments,
       inUse,
       inUse,
     });
     });
@@ -114,11 +111,9 @@ class PageAttachment extends React.Component {
 
 
 
 
   render() {
   render() {
-
     const { t } = this.props;
     const { t } = this.props;
     if (this.state.attachments.length === 0) {
     if (this.state.attachments.length === 0) {
       return t('No_attachments_yet');
       return t('No_attachments_yet');
-
     }
     }
 
 
     let deleteAttachmentModal = '';
     let deleteAttachmentModal = '';

+ 11 - 10
src/client/js/components/PageHistory.jsx

@@ -14,9 +14,12 @@ import PaginationWrapper from './PaginationWrapper';
 
 
 const logger = loggerFactory('growi:PageHistory');
 const logger = loggerFactory('growi:PageHistory');
 
 
-
 function PageHistory(props) {
 function PageHistory(props) {
   const { pageHistoryContainer } = props;
   const { pageHistoryContainer } = props;
+  const { getPreviousRevision, onDiffOpenClicked } = pageHistoryContainer;
+  const {
+    activePage, totalPages, pagingLimit, revisions, diffOpened,
+  } = pageHistoryContainer.state;
 
 
   const handlePage = useCallback(async(selectedPage) => {
   const handlePage = useCallback(async(selectedPage) => {
     try {
     try {
@@ -50,27 +53,25 @@ function PageHistory(props) {
     });
     });
   }
   }
 
 
-
   function pager() {
   function pager() {
     return (
     return (
       <PaginationWrapper
       <PaginationWrapper
-        activePage={pageHistoryContainer.state.activePage}
+        activePage={activePage}
         changePage={handlePage}
         changePage={handlePage}
-        totalItemsCount={pageHistoryContainer.state.totalPages}
-        pagingLimit={pageHistoryContainer.state.pagingLimit}
+        totalItemsCount={totalPages}
+        pagingLimit={pagingLimit}
         align="center"
         align="center"
       />
       />
     );
     );
   }
   }
 
 
-
   return (
   return (
     <div>
     <div>
       <PageRevisionList
       <PageRevisionList
-        revisions={pageHistoryContainer.state.revisions}
-        diffOpened={pageHistoryContainer.state.diffOpened}
-        getPreviousRevision={pageHistoryContainer.getPreviousRevision}
-        onDiffOpenClicked={pageHistoryContainer.onDiffOpenClicked}
+        revisions={revisions}
+        diffOpened={diffOpened}
+        getPreviousRevision={getPreviousRevision}
+        onDiffOpenClicked={onDiffOpenClicked}
       />
       />
       {pager()}
       {pager()}
     </div>
     </div>

+ 4 - 6
src/client/js/components/PageList.jsx

@@ -18,23 +18,21 @@ const PageList = (props) => {
 
 
   const [activePage, setActivePage] = useState(1);
   const [activePage, setActivePage] = useState(1);
   const [totalPages, setTotalPages] = useState(0);
   const [totalPages, setTotalPages] = useState(0);
-  const [limit, setLimit] = useState(appContainer.getConfig().recentCreatedLimit);
-  const [offset, setOffset] = useState(0);
+  const [limit, setLimit] = useState(null);
 
 
   function setPageNumber(selectedPageNumber) {
   function setPageNumber(selectedPageNumber) {
     setActivePage(selectedPageNumber);
     setActivePage(selectedPageNumber);
-    setOffset((selectedPageNumber - 1) * limit);
   }
   }
 
 
   const updatePageList = useCallback(async() => {
   const updatePageList = useCallback(async() => {
-    const res = await appContainer.apiv3Get('/pages/list', { path, limit, offset });
+    const page = activePage;
+    const res = await appContainer.apiv3Get('/pages/list', { path, page });
 
 
     setPages(res.data.pages);
     setPages(res.data.pages);
     setIsLoading(true);
     setIsLoading(true);
     setTotalPages(res.data.totalCount);
     setTotalPages(res.data.totalCount);
     setLimit(res.data.limit);
     setLimit(res.data.limit);
-    setOffset(res.data.offset);
-  }, [appContainer, path, limit, offset]);
+  }, [appContainer, path, activePage]);
 
 
   useEffect(() => {
   useEffect(() => {
     updatePageList();
     updatePageList();

+ 6 - 7
src/client/js/components/PageTimeline.jsx

@@ -16,11 +16,10 @@ class PageTimeline extends React.Component {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
-    const { appContainer } = this.props;
     this.state = {
     this.state = {
       activePage: 1,
       activePage: 1,
       totalPageItems: 0,
       totalPageItems: 0,
-      limit: appContainer.getConfig().recentCreatedLimit,
+      limit: null,
 
 
       // TODO: remove after when timeline is implemented with React and inject data with props
       // TODO: remove after when timeline is implemented with React and inject data with props
       pages: this.props.pages,
       pages: this.props.pages,
@@ -33,17 +32,17 @@ class PageTimeline extends React.Component {
   async handlePage(selectedPage) {
   async handlePage(selectedPage) {
     const { appContainer, pageContainer } = this.props;
     const { appContainer, pageContainer } = this.props;
     const { path } = pageContainer.state;
     const { path } = pageContainer.state;
-    const { limit } = this.state;
-    const offset = (selectedPage - 1) * limit;
-    const activePage = selectedPage;
+    const page = selectedPage;
 
 
-    const res = await appContainer.apiv3Get('/pages/list', { path, limit, offset });
+    const res = await appContainer.apiv3Get('/pages/list', { path, page });
     const totalPageItems = res.data.totalCount;
     const totalPageItems = res.data.totalCount;
     const pages = res.data.pages;
     const pages = res.data.pages;
+    const pagingLimit = res.data.limit;
     this.setState({
     this.setState({
-      activePage,
+      activePage: selectedPage,
       totalPageItems,
       totalPageItems,
       pages,
       pages,
+      limit: pagingLimit,
     });
     });
   }
   }
 
 

+ 5 - 3
src/client/js/components/PaginationWrapper.jsx

@@ -14,10 +14,10 @@ class PaginationWrapper extends React.Component {
     super(props);
     super(props);
 
 
     this.state = {
     this.state = {
-      totalItemsCount: 0,
       activePage: 1,
       activePage: 1,
+      totalItemsCount: 0,
       paginationNumbers: {},
       paginationNumbers: {},
-      limit: Infinity,
+      limit: this.props.pagingLimit || Infinity,
     };
     };
 
 
     this.calculatePagination = this.calculatePagination.bind(this);
     this.calculatePagination = this.calculatePagination.bind(this);
@@ -189,11 +189,13 @@ PaginationWrapper.propTypes = {
   activePage: PropTypes.number.isRequired,
   activePage: PropTypes.number.isRequired,
   changePage: PropTypes.func.isRequired,
   changePage: PropTypes.func.isRequired,
   totalItemsCount: PropTypes.number.isRequired,
   totalItemsCount: PropTypes.number.isRequired,
-  pagingLimit: PropTypes.number.isRequired,
+  pagingLimit: PropTypes.number,
   align: PropTypes.string,
   align: PropTypes.string,
 };
 };
 PaginationWrapper.defaultProps = {
 PaginationWrapper.defaultProps = {
   align: 'left',
   align: 'left',
+  pagingLimit: PropTypes.number,
+
 };
 };
 
 
 export default withTranslation()(PaginationWrappered);
 export default withTranslation()(PaginationWrappered);

+ 7 - 8
src/client/js/components/RecentCreated/RecentCreated.jsx

@@ -17,7 +17,7 @@ class RecentCreated extends React.Component {
       pages: [],
       pages: [],
       activePage: 1,
       activePage: 1,
       totalPages: 0,
       totalPages: 0,
-      pagingLimit: Infinity,
+      pagingLimit: null,
     };
     };
 
 
     this.handlePage = this.handlePage.bind(this);
     this.handlePage = this.handlePage.bind(this);
@@ -32,19 +32,18 @@ class RecentCreated extends React.Component {
     await this.getRecentCreatedList(selectedPage);
     await this.getRecentCreatedList(selectedPage);
   }
   }
 
 
-  async getRecentCreatedList(selectPageNumber) {
+  async getRecentCreatedList(selectedPage) {
     const { appContainer, userId } = this.props;
     const { appContainer, userId } = this.props;
-
-    const limit = appContainer.getConfig().recentCreatedLimit;
-    const offset = (selectPageNumber - 1) * limit;
+    const page = selectedPage;
+    // const userId = appContainer.currentUserId;
 
 
     // pagesList get and pagination calculate
     // pagesList get and pagination calculate
-    const res = await appContainer.apiv3Get(`/users/${userId}/recent`, { offset, limit });
-    const { totalCount, pages } = res.data;
+    const res = await appContainer.apiv3Get(`/users/${userId}/recent`, { page });
+    const { totalCount, pages, limit } = res.data;
 
 
     this.setState({
     this.setState({
       pages,
       pages,
-      activePage: selectPageNumber,
+      activePage: selectedPage,
       totalPages: totalCount,
       totalPages: totalCount,
       pagingLimit: limit,
       pagingLimit: limit,
     });
     });

+ 47 - 7
src/client/js/services/AdminCustomizeContainer.js

@@ -27,7 +27,12 @@ export default class AdminCustomizeContainer extends Container {
       isEnabledTimeline: false,
       isEnabledTimeline: false,
       isSavedStatesOfTabChanges: false,
       isSavedStatesOfTabChanges: false,
       isEnabledAttachTitleHeader: false,
       isEnabledAttachTitleHeader: false,
-      currentRecentCreatedLimit: 10,
+
+      pageLimitationS: 20,
+      pageLimitationM: 10,
+      pageLimitationL: 50,
+      pageLimitationXL: 20,
+
       isEnabledStaleNotification: false,
       isEnabledStaleNotification: false,
       isAllReplyShown: false,
       isAllReplyShown: false,
       currentHighlightJsStyleId: '',
       currentHighlightJsStyleId: '',
@@ -51,6 +56,10 @@ export default class AdminCustomizeContainer extends Container {
       },
       },
       /* eslint-enable quote-props, no-multi-spaces */
       /* eslint-enable quote-props, no-multi-spaces */
     };
     };
+    this.switchPageListLimitationS = this.switchPageListLimitationS.bind(this);
+    this.switchPageListLimitationM = this.switchPageListLimitationM.bind(this);
+    this.switchPageListLimitationL = this.switchPageListLimitationL.bind(this);
+    this.switchPageListLimitationXL = this.switchPageListLimitationXL.bind(this);
 
 
   }
   }
 
 
@@ -74,7 +83,10 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledTimeline: customizeParams.isEnabledTimeline,
         isEnabledTimeline: customizeParams.isEnabledTimeline,
         isSavedStatesOfTabChanges: customizeParams.isSavedStatesOfTabChanges,
         isSavedStatesOfTabChanges: customizeParams.isSavedStatesOfTabChanges,
         isEnabledAttachTitleHeader: customizeParams.isEnabledAttachTitleHeader,
         isEnabledAttachTitleHeader: customizeParams.isEnabledAttachTitleHeader,
-        currentRecentCreatedLimit: customizeParams.recentCreatedLimit,
+        pageLimitationS: customizeParams.pageLimitationS,
+        pageLimitationM: customizeParams.pageLimitationM,
+        pageLimitationL: customizeParams.pageLimitationL,
+        pageLimitationXL: customizeParams.pageLimitationXL,
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isAllReplyShown: customizeParams.isAllReplyShown,
         isAllReplyShown: customizeParams.isAllReplyShown,
         currentHighlightJsStyleId: customizeParams.styleName,
         currentHighlightJsStyleId: customizeParams.styleName,
@@ -128,11 +140,33 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ isEnabledAttachTitleHeader:  !this.state.isEnabledAttachTitleHeader });
     this.setState({ isEnabledAttachTitleHeader:  !this.state.isEnabledAttachTitleHeader });
   }
   }
 
 
+
+  /**
+   * S: Switch pageListLimitationS
+   */
+  switchPageListLimitationS(value) {
+    this.setState({ pageLimitationS: value });
+  }
+
+  /**
+   * M: Switch pageListLimitationM
+   */
+  switchPageListLimitationM(value) {
+    this.setState({ pageLimitationM: value });
+  }
+
+  /**
+   * L: Switch pageListLimitationL
+   */
+  switchPageListLimitationL(value) {
+    this.setState({ pageLimitationL: value });
+  }
+
   /**
   /**
-   * Switch recentCreatedLimit
+   * XL: Switch pageListLimitationXL
    */
    */
-  switchRecentCreatedLimit(value) {
-    this.setState({ currentRecentCreatedLimit: value });
+  switchPageListLimitationXL(value) {
+    this.setState({ pageLimitationXL: value });
   }
   }
 
 
   /**
   /**
@@ -255,7 +289,10 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledTimeline: this.state.isEnabledTimeline,
         isEnabledTimeline: this.state.isEnabledTimeline,
         isSavedStatesOfTabChanges: this.state.isSavedStatesOfTabChanges,
         isSavedStatesOfTabChanges: this.state.isSavedStatesOfTabChanges,
         isEnabledAttachTitleHeader: this.state.isEnabledAttachTitleHeader,
         isEnabledAttachTitleHeader: this.state.isEnabledAttachTitleHeader,
-        recentCreatedLimit: this.state.currentRecentCreatedLimit,
+        pageLimitationS: this.state.pageLimitationS,
+        pageLimitationM: this.state.pageLimitationM,
+        pageLimitationL: this.state.pageLimitationL,
+        pageLimitationXL: this.state.pageLimitationXL,
         isEnabledStaleNotification: this.state.isEnabledStaleNotification,
         isEnabledStaleNotification: this.state.isEnabledStaleNotification,
         isAllReplyShown: this.state.isAllReplyShown,
         isAllReplyShown: this.state.isAllReplyShown,
       });
       });
@@ -264,7 +301,10 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledTimeline: customizedParams.isEnabledTimeline,
         isEnabledTimeline: customizedParams.isEnabledTimeline,
         isSavedStatesOfTabChanges: customizedParams.isSavedStatesOfTabChanges,
         isSavedStatesOfTabChanges: customizedParams.isSavedStatesOfTabChanges,
         isEnabledAttachTitleHeader: customizedParams.isEnabledAttachTitleHeader,
         isEnabledAttachTitleHeader: customizedParams.isEnabledAttachTitleHeader,
-        recentCreatedLimit: customizedParams.currentRecentCreatedLimit,
+        pageLimitationS: customizedParams.pageLimitationS,
+        pageLimitationM: customizedParams.pageLimitationM,
+        pageLimitationL: customizedParams.pageLimitationL,
+        pageLimitationXL: customizedParams.pageLimitationXL,
         isEnabledStaleNotification: customizedParams.isEnabledStaleNotification,
         isEnabledStaleNotification: customizedParams.isEnabledStaleNotification,
         isAllReplyShown: customizedParams.isAllReplyShown,
         isAllReplyShown: customizedParams.isAllReplyShown,
       });
       });

+ 4 - 0
src/client/js/services/PageContainer.js

@@ -497,4 +497,8 @@ export default class PageContainer extends Container {
 
 
   }
   }
 
 
+  /* TODO GW-325 */
+  retrieveMyBookmarkList() {
+  }
+
 }
 }

+ 6 - 4
src/client/js/services/PageHistoryContainer.js

@@ -17,7 +17,6 @@ export default class PageHistoryContainer extends Container {
 
 
     this.appContainer = appContainer;
     this.appContainer = appContainer;
     this.pageContainer = pageContainer;
     this.pageContainer = pageContainer;
-
     this.dummyRevisions = 0;
     this.dummyRevisions = 0;
 
 
     this.state = {
     this.state = {
@@ -29,7 +28,7 @@ export default class PageHistoryContainer extends Container {
 
 
       totalPages: 0,
       totalPages: 0,
       activePage: 1,
       activePage: 1,
-      pagingLimit: Infinity,
+      pagingLimit: null,
     };
     };
 
 
     this.retrieveRevisions = this.retrieveRevisions.bind(this);
     this.retrieveRevisions = this.retrieveRevisions.bind(this);
@@ -51,13 +50,16 @@ export default class PageHistoryContainer extends Container {
    */
    */
   async retrieveRevisions(selectedPage) {
   async retrieveRevisions(selectedPage) {
     const { pageId, shareLinkId } = this.pageContainer.state;
     const { pageId, shareLinkId } = this.pageContainer.state;
+    const page = selectedPage;
+
     if (!pageId) {
     if (!pageId) {
       return;
       return;
     }
     }
 
 
-    const res = await this.appContainer.apiv3Get('/revisions/list', { pageId, shareLinkId, selectedPage });
+    const res = await this.appContainer.apiv3Get('/revisions/list', {
+      pageId, shareLinkId, page,
+    });
     const rev = res.data.docs;
     const rev = res.data.docs;
-
     // set Pagination state
     // set Pagination state
     this.setState({
     this.setState({
       activePage: selectedPage,
       activePage: selectedPage,

+ 2 - 0
src/server/models/bookmark.js

@@ -2,6 +2,7 @@
 
 
 const debug = require('debug')('growi:models:bookmark');
 const debug = require('debug')('growi:models:bookmark');
 const mongoose = require('mongoose');
 const mongoose = require('mongoose');
+const mongoosePaginate = require('mongoose-paginate-v2');
 const uniqueValidator = require('mongoose-unique-validator');
 const uniqueValidator = require('mongoose-unique-validator');
 
 
 const ObjectId = mongoose.Schema.Types.ObjectId;
 const ObjectId = mongoose.Schema.Types.ObjectId;
@@ -18,6 +19,7 @@ module.exports = function(crowi) {
     createdAt: { type: Date, default: Date.now },
     createdAt: { type: Date, default: Date.now },
   });
   });
   bookmarkSchema.index({ page: 1, user: 1 }, { unique: true });
   bookmarkSchema.index({ page: 1, user: 1 }, { unique: true });
+  bookmarkSchema.plugin(mongoosePaginate);
   bookmarkSchema.plugin(uniqueValidator);
   bookmarkSchema.plugin(uniqueValidator);
 
 
   bookmarkSchema.statics.countByPageId = async function(pageId) {
   bookmarkSchema.statics.countByPageId = async function(pageId) {

+ 4 - 2
src/server/models/config.js

@@ -111,7 +111,10 @@ module.exports = function(crowi) {
       'customize:isEnabledTimeline' : true,
       'customize:isEnabledTimeline' : true,
       'customize:isSavedStatesOfTabChanges' : true,
       'customize:isSavedStatesOfTabChanges' : true,
       'customize:isEnabledAttachTitleHeader' : false,
       'customize:isEnabledAttachTitleHeader' : false,
-      'customize:showRecentCreatedNumber' : 10,
+      'customize:showPageLimitationS' : 20,
+      'customize:showPageLimitationM' : 10,
+      'customize:showPageLimitationL' : 50,
+      'customize:showPageLimitationXL' : 20,
       'customize:isEnabledStaleNotification': false,
       'customize:isEnabledStaleNotification': false,
       'customize:isAllReplyShown': false,
       'customize:isAllReplyShown': false,
 
 
@@ -218,7 +221,6 @@ module.exports = function(crowi) {
         MATHJAX: env.MATHJAX || null,
         MATHJAX: env.MATHJAX || null,
         NO_CDN: env.NO_CDN || null,
         NO_CDN: env.NO_CDN || null,
       },
       },
-      recentCreatedLimit: crowi.configManager.getConfig('crowi', 'customize:showRecentCreatedNumber'),
       isEnabledStaleNotification: crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       isEnabledStaleNotification: crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       isAclEnabled: crowi.aclService.isAclEnabled(),
       isAclEnabled: crowi.aclService.isAclEnabled(),
       isSearchServiceConfigured: crowi.searchService.isConfigured,
       isSearchServiceConfigured: crowi.searchService.isConfigured,

+ 7 - 5
src/server/routes/apiv3/attachment.js

@@ -23,12 +23,10 @@ module.exports = (crowi) => {
   const Attachment = crowi.model('Attachment');
   const Attachment = crowi.model('Attachment');
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
 
 
-
   const validator = {
   const validator = {
     retrieveAttachments: [
     retrieveAttachments: [
       query('pageId').isMongoId().withMessage('pageId is required'),
       query('pageId').isMongoId().withMessage('pageId is required'),
-      query('limit').isInt({ min: 1 }),
-      query('offset').isInt({ min: 0 }),
+      query('limit').if(value => value != null).isInt({ max: 100 }).withMessage('You should set less than 100 or not to set limit.'),
     ],
     ],
   };
   };
   /**
   /**
@@ -50,8 +48,10 @@ module.exports = (crowi) => {
    *              type: string
    *              type: string
    */
    */
   router.get('/list', accessTokenParser, loginRequired, validator.retrieveAttachments, apiV3FormValidator, async(req, res) => {
   router.get('/list', accessTokenParser, loginRequired, validator.retrieveAttachments, apiV3FormValidator, async(req, res) => {
-    const offset = +req.query.offset || 0;
-    const limit = +req.query.limit || 30;
+
+    const limit = req.query.limit || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS') || 10;
+    const page = req.query.page;
+    const offset = (page - 1) * limit;
 
 
     try {
     try {
       const pageId = req.query.pageId;
       const pageId = req.query.pageId;
@@ -62,6 +62,8 @@ module.exports = (crowi) => {
         return res.apiv3Err(new ErrorV3(msg, 'attachment-list-failed'), 403);
         return res.apiv3Err(new ErrorV3(msg, 'attachment-list-failed'), 403);
       }
       }
 
 
+      // directly get paging-size from db. not to delivery from client side.
+
       const paginateResult = await Attachment.paginate(
       const paginateResult = await Attachment.paginate(
         { page: pageId },
         { page: pageId },
         {
         {

+ 85 - 2
src/server/routes/apiv3/bookmarks.js

@@ -3,7 +3,7 @@ const loggerFactory = require('@alias/logger');
 const logger = loggerFactory('growi:routes:apiv3:bookmarks'); // eslint-disable-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:bookmarks'); // eslint-disable-line no-unused-vars
 
 
 const express = require('express');
 const express = require('express');
-const { body } = require('express-validator');
+const { body, query } = require('express-validator');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
@@ -58,7 +58,7 @@ module.exports = (crowi) => {
   const csrf = require('../../middlewares/csrf')(crowi);
   const csrf = require('../../middlewares/csrf')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
 
 
-  const { Page, Bookmark } = crowi.models;
+  const { Page, Bookmark, User } = crowi.models;
 
 
   const validator = {
   const validator = {
     bookmarks: [
     bookmarks: [
@@ -103,6 +103,89 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  // select page from bookmark where userid = userid
+  /**
+   * @swagger
+   *
+   *    /bookmarks/{userId}:
+   *      get:
+   *        tags: [Bookmarks]
+   *        summary: /bookmarks/{userId}
+   *        description: Get my bookmarked status
+   *        operationId: getMyBookmarkedStatus
+   *        parameters:
+   *          - name: userId
+   *            in: path
+   *            required: true
+   *            description: user id
+   *            schema:
+   *              type: string
+   *          - name: page
+   *            in: query
+   *            description: selected page number
+   *            schema:
+   *              type: number
+   *          - name: limit
+   *            in: query
+   *            description: page item limit
+   *            schema:
+   *              type: number
+   *          - name: offset
+   *            in: query
+   *            description: page item offset
+   *            schema:
+   *              type: number
+   *        responses:
+   *          200:
+   *            description: Succeeded to get my bookmarked status.
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  $ref: '#/components/schemas/Bookmark'
+   */
+  validator.myBookmarkList = [
+    query('page').isInt({ min: 1 }),
+    query('limit').if(value => value != null).isInt({ max: 300 }).withMessage('You should set less than 300 or not to set limit.'),
+  ];
+
+  router.get('/:userId', accessTokenParser, loginRequired, validator.myBookmarkList, apiV3FormValidator, async(req, res) => {
+    const { userId } = req.params;
+    const page = req.query.page;
+    const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30;
+
+    if (userId == null) {
+      return res.apiv3Err('User id is not found or forbidden', 400);
+    }
+    if (limit == null) {
+      return res.apiv3Err('Could not catch page limit', 400);
+    }
+    try {
+      const paginationResult = await Bookmark.paginate(
+        {
+          user: { $in: userId },
+        },
+        {
+          populate: {
+            path: 'page',
+            model: 'Page',
+            populate: {
+              path: 'lastUpdateUser',
+              model: 'User',
+              select: User.USER_PUBLIC_FIELDS,
+            },
+          },
+          page,
+          limit,
+        },
+      );
+      return res.apiv3({ paginationResult });
+    }
+    catch (err) {
+      logger.error('get-bookmark-failed', err);
+      return res.apiv3Err(err, 500);
+    }
+  });
+
 
 
   /**
   /**
    * @swagger
    * @swagger

+ 19 - 5
src/server/routes/apiv3/customize-setting.js

@@ -37,7 +37,9 @@ const ErrorV3 = require('../../models/vo/error-apiv3');
  *            type: boolean
  *            type: boolean
  *          isEnabledAttachTitleHeader:
  *          isEnabledAttachTitleHeader:
  *            type: boolean
  *            type: boolean
- *          recentCreatedLimit:
+ *          pageLimitationS:
+ *            type: number
+ *          pageLimitationM:
  *            type: number
  *            type: number
  *          isEnabledStaleNotification:
  *          isEnabledStaleNotification:
  *            type: boolean
  *            type: boolean
@@ -99,7 +101,10 @@ module.exports = (crowi) => {
       body('isEnabledTimeline').isBoolean(),
       body('isEnabledTimeline').isBoolean(),
       body('isSavedStatesOfTabChanges').isBoolean(),
       body('isSavedStatesOfTabChanges').isBoolean(),
       body('isEnabledAttachTitleHeader').isBoolean(),
       body('isEnabledAttachTitleHeader').isBoolean(),
-      body('recentCreatedLimit').isInt().isInt({ min: 1, max: 1000 }),
+      body('pageLimitationS').isInt().isInt({ min: 1, max: 1000 }),
+      body('pageLimitationM').isInt().isInt({ min: 1, max: 1000 }),
+      body('pageLimitationL').isInt().isInt({ min: 1, max: 1000 }),
+      body('pageLimitationXL').isInt().isInt({ min: 1, max: 1000 }),
       body('isEnabledStaleNotification').isBoolean(),
       body('isEnabledStaleNotification').isBoolean(),
       body('isAllReplyShown').isBoolean(),
       body('isAllReplyShown').isBoolean(),
     ],
     ],
@@ -151,7 +156,10 @@ module.exports = (crowi) => {
       isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
       isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
       isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
       isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
       isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
       isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
-      recentCreatedLimit: await crowi.configManager.getConfig('crowi', 'customize:showRecentCreatedNumber'),
+      pageLimitationS: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS') || 20,
+      pageLimitationM: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 10,
+      pageLimitationL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationL') || 50,
+      pageLimitationXL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL') || 20,
       isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
       isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'),
       isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'),
       styleName: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'),
       styleName: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'),
@@ -273,7 +281,10 @@ module.exports = (crowi) => {
       'customize:isEnabledTimeline': req.body.isEnabledTimeline,
       'customize:isEnabledTimeline': req.body.isEnabledTimeline,
       'customize:isSavedStatesOfTabChanges': req.body.isSavedStatesOfTabChanges,
       'customize:isSavedStatesOfTabChanges': req.body.isSavedStatesOfTabChanges,
       'customize:isEnabledAttachTitleHeader': req.body.isEnabledAttachTitleHeader,
       'customize:isEnabledAttachTitleHeader': req.body.isEnabledAttachTitleHeader,
-      'customize:showRecentCreatedNumber': req.body.recentCreatedLimit,
+      'customize:showPageLimitationS': req.body.pageLimitationS,
+      'customize:showPageLimitationM': req.body.pageLimitationM,
+      'customize:showPageLimitationL': req.body.pageLimitationL,
+      'customize:showPageLimitationXL': req.body.pageLimitationXL,
       'customize:isEnabledStaleNotification': req.body.isEnabledStaleNotification,
       'customize:isEnabledStaleNotification': req.body.isEnabledStaleNotification,
       'customize:isAllReplyShown': req.body.isAllReplyShown,
       'customize:isAllReplyShown': req.body.isAllReplyShown,
     };
     };
@@ -284,7 +295,10 @@ module.exports = (crowi) => {
         isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
         isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
         isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
         isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
         isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
         isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
-        recentCreatedLimit: await crowi.configManager.getConfig('crowi', 'customize:showRecentCreatedNumber'),
+        pageLimitationS: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS'),
+        pageLimitationM: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM'),
+        pageLimitationL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationL'),
+        pageLimitationXL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL'),
         isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
         isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
         isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'),
         isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'),
       };
       };

+ 14 - 6
src/server/routes/apiv3/pages.js

@@ -1,12 +1,10 @@
 const loggerFactory = require('@alias/logger');
 const loggerFactory = require('@alias/logger');
 
 
 const logger = loggerFactory('growi:routes:apiv3:pages'); // eslint-disable-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:pages'); // eslint-disable-line no-unused-vars
-
 const express = require('express');
 const express = require('express');
 
 
-
 const router = express.Router();
 const router = express.Router();
-
+const { query } = require('express-validator');
 
 
 /**
 /**
  * @swagger
  * @swagger
@@ -18,9 +16,13 @@ module.exports = (crowi) => {
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const csrf = require('../../middlewares/csrf')(crowi);
   const csrf = require('../../middlewares/csrf')(crowi);
+  const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
+
 
 
   const Page = crowi.model('Page');
   const Page = crowi.model('Page');
 
 
+  const validator = {};
+
   /**
   /**
    * @swagger
    * @swagger
    *
    *
@@ -84,10 +86,16 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
-  router.get('/list', accessTokenParser, loginRequired, async(req, res) => {
+  validator.displayList = [
+    query('limit').if(value => value != null).isInt({ max: 100 }).withMessage('You should set less than 100 or not to set limit.'),
+  ];
+
+  router.get('/list', accessTokenParser, loginRequired, validator.displayList, apiV3FormValidator, async(req, res) => {
+    const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS') || 10;
     const { path } = req.query;
     const { path } = req.query;
-    const limit = +req.query.limit || 30;
-    const offset = +req.query.offset || 0;
+    const page = req.query.page;
+    const offset = (page - 1) * limit;
+
     const queryOptions = { offset, limit };
     const queryOptions = { offset, limit };
 
 
     try {
     try {

+ 6 - 5
src/server/routes/apiv3/revisions.js

@@ -9,8 +9,6 @@ const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
-const PAGE_ITEMS = 30;
-
 /**
 /**
  * @swagger
  * @swagger
  *  tags:
  *  tags:
@@ -71,7 +69,9 @@ module.exports = (crowi) => {
   const validator = {
   const validator = {
     retrieveRevisions: [
     retrieveRevisions: [
       query('pageId').isMongoId().withMessage('pageId is required'),
       query('pageId').isMongoId().withMessage('pageId is required'),
-      query('selectedPage').isInt({ min: 0 }).withMessage('selectedPage must be int'),
+      query('page').isInt({ min: 0 }).withMessage('page must be int'),
+      query('limit').if(value => value != null).isInt({ max: 100 }).withMessage('You should set less than 100 or not to set limit.'),
+
     ],
     ],
     retrieveRevisionById: [
     retrieveRevisionById: [
       query('pageId').isMongoId().withMessage('pageId is required'),
       query('pageId').isMongoId().withMessage('pageId is required'),
@@ -99,9 +99,10 @@ module.exports = (crowi) => {
    */
    */
   router.get('/list', certifySharedPage, accessTokenParser, loginRequired, validator.retrieveRevisions, apiV3FormValidator, async(req, res) => {
   router.get('/list', certifySharedPage, accessTokenParser, loginRequired, validator.retrieveRevisions, apiV3FormValidator, async(req, res) => {
     const pageId = req.query.pageId;
     const pageId = req.query.pageId;
+    const limit = req.query.limit || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS') || 10;
     const { isSharedPage } = req;
     const { isSharedPage } = req;
 
 
-    const selectedPage = parseInt(req.query.selectedPage) || 1;
+    const selectedPage = parseInt(req.query.page) || 1;
 
 
     // check whether accessible
     // check whether accessible
     if (!isSharedPage && !(await Page.isAccessiblePageByViewer(pageId, req.user))) {
     if (!isSharedPage && !(await Page.isAccessiblePageByViewer(pageId, req.user))) {
@@ -115,7 +116,7 @@ module.exports = (crowi) => {
         { path: page.path },
         { path: page.path },
         {
         {
           page: selectedPage,
           page: selectedPage,
-          limit: PAGE_ITEMS,
+          limit,
           sort: { createdAt: -1 },
           sort: { createdAt: -1 },
           populate: {
           populate: {
             path: 'author',
             path: 'author',

+ 8 - 3
src/server/routes/apiv3/users.js

@@ -105,6 +105,10 @@ module.exports = (crowi) => {
     query('page').isInt({ min: 1 }),
     query('page').isInt({ min: 1 }),
   ];
   ];
 
 
+  validator.recentCreatedByUser = [
+    query('limit').if(value => value != null).isInt({ max: 300 }).withMessage('You should set less than 300 or not to set limit.'),
+  ];
+
   /**
   /**
    * @swagger
    * @swagger
    *
    *
@@ -224,7 +228,7 @@ module.exports = (crowi) => {
    *                    paginateResult:
    *                    paginateResult:
    *                      $ref: '#/components/schemas/PaginateResult'
    *                      $ref: '#/components/schemas/PaginateResult'
    */
    */
-  router.get('/:id/recent', accessTokenParser, loginRequired, async(req, res) => {
+  router.get('/:id/recent', accessTokenParser, loginRequired, validator.recentCreatedByUser, apiV3FormValidator, async(req, res) => {
     const { id } = req.params;
     const { id } = req.params;
 
 
     let user;
     let user;
@@ -242,8 +246,9 @@ module.exports = (crowi) => {
       return res.apiv3Err(new ErrorV3('find-user-is-not-found'));
       return res.apiv3Err(new ErrorV3('find-user-is-not-found'));
     }
     }
 
 
-    const limit = parseInt(req.query.limit) || 50;
-    const offset = parseInt(req.query.offset) || 0;
+    const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30;
+    const page = req.query.page;
+    const offset = (page - 1) * limit;
     const queryOptions = { offset, limit };
     const queryOptions = { offset, limit };
 
 
     try {
     try {

+ 3 - 8
src/server/views/widget/user_page_content.html

@@ -12,13 +12,8 @@
         <span class="d-none d-sm-inline">Recently Created</span>
         <span class="d-none d-sm-inline">Recently Created</span>
       </a>
       </a>
     </li>
     </li>
+
     {% if user._id.toString() == pageUser._id.toString() %}
     {% if user._id.toString() == pageUser._id.toString() %}
-    <li class="nav-item">
-      <a class="nav-link" href="#user-draft-list" role="tab" data-toggle="tab">
-        <i class="icon-docs"></i>
-        <span class="d-none d-sm-inline">My Drafts</span>
-      </a>
-    </li>
     <li class="nav-item">
     <li class="nav-item">
       <a class="nav-link" href="/me" role="tab">
       <a class="nav-link" href="/me" role="tab">
         <i class="icon-wrench"></i>
         <i class="icon-wrench"></i>
@@ -35,9 +30,9 @@
         {{t('No bookmarks yet')}}.
         {{t('No bookmarks yet')}}.
       {% else %}
       {% else %}
         <div class="page-list-container">
         <div class="page-list-container">
-          {% include 'page_list.html' with { pages: bookmarkList, pagePropertyName: 'page' } %}
+          {# {% include 'page_list.html' with { pages: bookmarkList, pagePropertyName: 'page' } %} #}
         </div>
         </div>
-      {% endif %}
+        {# {% endif %} #}
     </div>
     </div>
 
 
     <div class="tab-pane user-created-list page-list" id="user-created-list">
     <div class="tab-pane user-created-list page-list" id="user-created-list">