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

Merge branch 'feat/transplant-tabs-to-modal-for-master-merge' into imprv/transplant-gw3727

# Conflicts:
#	src/client/js/components/Page/PageManagement.jsx
白石誠 5 лет назад
Родитель
Сommit
eea0bf4f99

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

@@ -19,7 +19,6 @@ import PageComments from './components/PageComments';
 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 PageManagement from './components/Page/PageManagement';
-import PageShareManagement from './components/Page/PageShareManagement';
 import TrashPageAlert from './components/Page/TrashPageAlert';
 import TrashPageAlert from './components/Page/TrashPageAlert';
 import PageAttachment from './components/PageAttachment';
 import PageAttachment from './components/PageAttachment';
 import PageStatusAlert from './components/PageStatusAlert';
 import PageStatusAlert from './components/PageStatusAlert';
@@ -90,7 +89,6 @@ if (pageContainer.state.pageId != null) {
     'page-comment-write': <CommentEditorLazyRenderer />,
     'page-comment-write': <CommentEditorLazyRenderer />,
     'page-attachment': <PageAttachment />,
     'page-attachment': <PageAttachment />,
     'page-management': <PageManagement />,
     'page-management': <PageManagement />,
-    'page-share-management': <PageShareManagement />,
 
 
     'revision-toc': <TableOfContents />,
     'revision-toc': <TableOfContents />,
     'seen-user-list': <SeenUserList />,
     'seen-user-list': <SeenUserList />,

+ 1 - 1
src/client/js/components/Admin/Security/ShareLinkSetting.jsx

@@ -11,7 +11,7 @@ import AppContainer from '../../../services/AppContainer';
 import AdminGeneralSecurityContainer from '../../../services/AdminGeneralSecurityContainer';
 import AdminGeneralSecurityContainer from '../../../services/AdminGeneralSecurityContainer';
 
 
 import DeleteAllShareLinksModal from './DeleteAllShareLinksModal';
 import DeleteAllShareLinksModal from './DeleteAllShareLinksModal';
-import ShareLinkList from '../../ShareLinkList';
+import ShareLinkList from '../../ShareLink/ShareLinkList';
 
 
 class ShareLinkSetting extends React.Component {
 class ShareLinkSetting extends React.Component {
 
 

+ 35 - 0
src/client/js/components/Icons/ShareLinkIcon.jsx

@@ -0,0 +1,35 @@
+import React from 'react';
+
+const ShareLink = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
+    <g transform="translate(-142 -502)">
+      <rect width="20" height="20" transform="translate(142 502)" fill="none" />
+      <g transform="translate(16 286.938)">
+        <path
+          d="M-1.813-3.563a2.711,2.711,0,0,0-1.274.308,2.8,2.8,0,0,0-.976.835L-11.48-6.2a2.676,2.676,
+          0,0,0,.105-.738,2.555,2.555,0,0,0-.044-.466,3.34,3.34,0,0,0-.114-.448l7.453-3.621a2.71,2.71,
+          0,0,0,.984.853,2.764,2.764,0,0,0,1.283.308,2.708,2.708,0,0,0,1.986-.826A2.708,2.708,0,0,
+          0,1-13.125a2.751,2.751,0,0,0-.378-1.406A2.793,2.793,0,0,0-.406-15.56a2.751,2.751,0,0,
+          0-1.406-.378,2.751,2.751,0,0,0-1.406.378,2.793,2.793,0,0,0-1.028,1.028,2.751,2.751,0,0,0-.378,
+          1.406v.105a.64.64,0,0,0,.009.105.641.641,0,0,1,.009.105A.641.641,0,0,0-4.6-12.7a.694.694,0,0,0,
+          .026.105.332.332,0,0,1,.018.105l-7.559,3.674a2.735,2.735,0,0,0-.923-.686,2.727,2.727,0,0,
+          0-1.151-.246,2.708,2.708,0,0,0-1.986.826A2.708,2.708,0,0,0-17-6.937a2.708,2.708,0,0,0,
+          .826,1.986,2.708,2.708,0,0,0,1.986.826A2.666,2.666,0,0,0-11.99-5.2l7.453,3.8a1.388,1.388,0,0,
+          0-.053.211q-.018.105-.026.22t-.009.22A2.751,2.751,0,0,0-4.247.656,2.792,2.792,0,0,0-3.219,
+          1.685a2.751,2.751,0,0,0,1.406.378A2.708,2.708,0,0,0,.174,1.236,2.708,2.708,0,0,0,1-.75,2.708,
+          2.708,0,0,0,.174-2.736,2.708,2.708,0,0,0-1.813-3.563Zm-1.2-10.758a1.627,1.627,0,0,1,1.2-.492,
+          1.627,1.627,0,0,1,1.2.492,1.627,1.627,0,0,1,.492,1.2,1.627,1.627,0,0,1-.492,1.2,1.627,1.627,
+          0,0,1-1.2.492,1.627,1.627,0,0,1-1.2-.492,1.627,1.627,0,0,1-.492-1.2A1.627,1.627,0,0,
+          1-3.008-14.32Zm-9.984,8.578a1.627,1.627,0,0,1-1.2.492,1.627,1.627,0,0,1-1.2-.492,1.627,
+          1.627,0,0,1-.492-1.2,1.627,1.627,0,0,1,.492-1.2,1.627,1.627,0,0,1,1.2-.492,1.627,1.627,
+          0,0,1,1.2.492,1.627,1.627,0,0,1,.492,1.2A1.627,1.627,0,0,1-12.992-5.742ZM-.617.445a1.627,
+          1.627,0,0,1-1.2.492,1.627,1.627,0,0,1-1.2-.492A1.627,1.627,0,0,1-3.5-.75a1.627,1.627,0,0,
+          1,.492-1.2,1.627,1.627,0,0,1,1.2-.492,1.627,1.627,0,0,1,1.2.492A1.627,1.627,0,0,1-.125-.75,1.627,1.627,0,0,1-.617.445Z"
+          transform="translate(144 232)"
+        />
+      </g>
+    </g>
+  </svg>
+);
+
+export default ShareLink;

+ 38 - 0
src/client/js/components/Page/PageManagement.jsx

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { UncontrolledTooltip } from 'reactstrap';
 import { UncontrolledTooltip } from 'reactstrap';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
+import urljoin from 'url-join';
 
 
 import { isTopPage } from '@commons/util/path-utils';
 import { isTopPage } from '@commons/util/path-utils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
@@ -58,6 +59,36 @@ const PageManagement = (props) => {
   }
   }
 
 
 
 
+  // 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 { pageId, revisionId } = pageContainer.state;
+    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 renderDropdownItemForNotTopPage() {
   function renderDropdownItemForNotTopPage() {
     return (
     return (
       <>
       <>
@@ -72,6 +103,13 @@ const PageManagement = (props) => {
             <i className="icon-film icon-fw"></i><span className="d-none d-sm-inline">{ t('Presentation Mode') }</span>
             <i className="icon-film icon-fw"></i><span className="d-none d-sm-inline">{ t('Presentation Mode') }</span>
           </a>
           </a>
         </button>
         </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>
+        {/* 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>
         <div className="dropdown-divider"></div>
       </>
       </>
     );
     );

+ 0 - 159
src/client/js/components/Page/PageShareManagement.jsx

@@ -1,159 +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 { withUnstatedContainers } from '../UnstatedUtils';
-
-import AppContainer from '../../services/AppContainer';
-import PageContainer from '../../services/PageContainer';
-import OutsideShareLinkModal from '../OutsideShareLinkModal';
-
-// TODO GW-2746 bulk export pages
-// import ArchiveCreateModal from '../ArchiveCreateModal';
-
-const PageShareManagement = (props) => {
-  const { t, appContainer, pageContainer } = props;
-
-  // TODO GW-2746 bulk export pages
-  // eslint-disable-next-line no-unused-vars
-  const { path, pageId } = pageContainer.state;
-  const { currentUser } = appContainer;
-
-  const [isOutsideShareLinkModalShown, setIsOutsideShareLinkModalShown] = useState(false);
-
-  // TODO GW-2746 bulk export pages
-  // const [isArchiveCreateModalShown, setIsArchiveCreateModalShown] = useState(false);
-  // const [totalPages, setTotalPages] = useState(null);
-  // const [errorMessage, setErrorMessage] = useState(null);
-
-  function openOutsideShareLinkModalHandler() {
-    setIsOutsideShareLinkModalShown(true);
-  }
-
-  function closeOutsideShareLinkModalHandler() {
-    setIsOutsideShareLinkModalShown(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 { pageId, revisionId } = pageContainer.state;
-    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 renderModals() {
-    if (currentUser == null) {
-      return null;
-    }
-
-    return (
-      <>
-        <OutsideShareLinkModal
-          isOpen={isOutsideShareLinkModalShown}
-          onClose={closeOutsideShareLinkModalHandler}
-        />
-
-        {/* TODO GW-2746 bulk export pages */}
-        {/* <ArchiveCreateModal
-          isOpen={isArchiveCreateModalShown}
-          onClose={closeArchiveCreateModalHandler}
-          path={path}
-          errorMessage={errorMessage}
-          totalPages={totalPages}
-        /> */}
-      </>
-    );
-  }
-
-
-  function renderCurrentUser() {
-    return (
-      <>
-        <button
-          type="button"
-          className="btn-link nav-link bg-transparent dropdown-toggle dropdown-toggle-no-caret"
-          data-toggle="dropdown"
-        >
-          <i className="icon-share"></i>
-        </button>
-      </>
-    );
-  }
-
-  function renderGuestUser() {
-    return (
-      <>
-        <button
-          type="button"
-          className="btn nav-link bg-transparent dropdown-toggle dropdown-toggle-no-caret disabled"
-          id="auth-guest-tltips"
-        >
-          <i className="icon-share"></i>
-        </button>
-        <UncontrolledTooltip placement="top" target="auth-guest-tltips">
-          {t('Not available for guest')}
-        </UncontrolledTooltip>
-      </>
-    );
-  }
-
-  return (
-    <>
-      {currentUser == null ? renderGuestUser() : renderCurrentUser()}
-      <div className="dropdown-menu dropdown-menu-right">
-        <button className="dropdown-item" type="button" onClick={openOutsideShareLinkModalHandler}>
-          <i className="icon-fw icon-link"></i>{t('share_links.Shere this page link to public')}
-          <span className="ml-2 badge badge-info badge-pill">{pageContainer.state.shareLinksNumber}</span>
-        </button>
-        <button type="button" className="dropdown-item" onClick={() => { exportPageHandler('md') }}>
-          <span>{t('export_bulk.export_page_markdown')}</span>
-        </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>
-      {renderModals()}
-    </>
-  );
-
-};
-
-/**
- * Wrapper component for using unstated
- */
-const PageShareManagementWrapper = withUnstatedContainers(PageShareManagement, [AppContainer, PageContainer]);
-
-
-PageShareManagement.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-};
-
-export default withTranslation()(PageShareManagementWrapper);

+ 16 - 1
src/client/js/components/PageAccessoriesModal.jsx

@@ -11,6 +11,7 @@ import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import RecentChangesIcon from './Icons/RecentChangesIcon';
 import RecentChangesIcon from './Icons/RecentChangesIcon';
 import AttachmentIcon from './Icons/AttachmentIcon';
 import AttachmentIcon from './Icons/AttachmentIcon';
+import ShareLinkIcon from './Icons/ShareLinkIcon';
 
 
 import { withUnstatedContainers } from './UnstatedUtils';
 import { withUnstatedContainers } from './UnstatedUtils';
 import PageAccessoriesContainer from '../services/PageAccessoriesContainer';
 import PageAccessoriesContainer from '../services/PageAccessoriesContainer';
@@ -18,6 +19,7 @@ import PageAttachment from './PageAttachment';
 import PageTimeline from './PageTimeline';
 import PageTimeline from './PageTimeline';
 import PageList from './PageList';
 import PageList from './PageList';
 import PageHistory from './PageHistory';
 import PageHistory from './PageHistory';
+import ShareLink from './ShareLink/ShareLink';
 
 
 const PageAccessoriesModal = (props) => {
 const PageAccessoriesModal = (props) => {
   const { t, pageAccessoriesContainer } = props;
   const { t, pageAccessoriesContainer } = props;
@@ -33,7 +35,7 @@ const PageAccessoriesModal = (props) => {
 
 
   return (
   return (
     <React.Fragment>
     <React.Fragment>
-      <Modal size="lg" isOpen={props.isOpen} toggle={closeModalHandler} className="grw-page-accessories-modal">
+      <Modal size="xl" isOpen={props.isOpen} toggle={closeModalHandler} className="grw-page-accessories-modal">
         <ModalBody>
         <ModalBody>
           <Nav className="nav-title border-bottom">
           <Nav className="nav-title border-bottom">
             <NavItem type="button" className={`nav-link ${activeTab === 'pagelist' && 'active active-border'}`}>
             <NavItem type="button" className={`nav-link ${activeTab === 'pagelist' && 'active active-border'}`}>
@@ -76,6 +78,16 @@ const PageAccessoriesModal = (props) => {
                 {t('attachment_data')}
                 {t('attachment_data')}
               </NavLink>
               </NavLink>
             </NavItem>
             </NavItem>
+            <NavItem type="button" className={`nav-link ${activeTab === 'share-link' && 'active active-border'}`}>
+              <NavLink
+                onClick={() => {
+                  switchActiveTab('share-link');
+                }}
+              >
+                <ShareLinkIcon />
+                {t('share_links.share_link_management')}
+              </NavLink>
+            </NavItem>
           </Nav>
           </Nav>
           <TabContent activeTab={activeTab}>
           <TabContent activeTab={activeTab}>
 
 
@@ -93,6 +105,9 @@ const PageAccessoriesModal = (props) => {
             <TabPane tabId="attachment" className="p-4">
             <TabPane tabId="attachment" className="p-4">
               {pageAccessoriesContainer.state.activeComponents.has('attachment') && <PageAttachment />}
               {pageAccessoriesContainer.state.activeComponents.has('attachment') && <PageAttachment />}
             </TabPane>
             </TabPane>
+            <TabPane tabId="share-link" className="p-4">
+              {pageAccessoriesContainer.state.activeComponents.has('share-link') && <ShareLink />}
+            </TabPane>
           </TabContent>
           </TabContent>
         </ModalBody>
         </ModalBody>
       </Modal>
       </Modal>

+ 14 - 13
src/client/js/components/PageAttachment.jsx

@@ -25,27 +25,28 @@ class PageAttachment extends React.Component {
     this.onAttachmentDeleteClickedConfirm = this.onAttachmentDeleteClickedConfirm.bind(this);
     this.onAttachmentDeleteClickedConfirm = this.onAttachmentDeleteClickedConfirm.bind(this);
   }
   }
 
 
-  componentDidMount() {
+  async componentDidMount() {
     const { pageId } = this.props.pageContainer.state;
     const { pageId } = this.props.pageContainer.state;
 
 
     if (!pageId) {
     if (!pageId) {
       return;
       return;
     }
     }
 
 
-    this.props.appContainer.apiGet('/attachments.list', { page_id: pageId })
-      .then((res) => {
-        const attachments = res.attachments;
-        const inUse = {};
+    const limit = 10;
+    // offset値は、dummy data この後のタスクで実装
+    const offset = 0;
+    const res = await this.props.appContainer.apiv3Get('/attachment/list', { pageId, limit, offset });
+    const attachments = res.data.paginateResult.docs;
+    const inUse = {};
 
 
-        for (const attachment of attachments) {
-          inUse[attachment._id] = this.checkIfFileInUse(attachment);
-        }
+    for (const attachment of attachments) {
+      inUse[attachment._id] = this.checkIfFileInUse(attachment);
+    }
 
 
-        this.setState({
-          attachments,
-          inUse,
-        });
-      });
+    this.setState({
+      attachments,
+      inUse,
+    });
   }
   }
 
 
   checkIfFileInUse(attachment) {
   checkIfFileInUse(attachment) {

+ 29 - 40
src/client/js/components/OutsideShareLinkModal.jsx → src/client/js/components/ShareLink/ShareLink.jsx

@@ -1,23 +1,18 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
-import {
-  Modal, ModalHeader, ModalBody,
-} from 'reactstrap';
-
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
+import { withUnstatedContainers } from '../UnstatedUtils';
 
 
-import { withUnstatedContainers } from './UnstatedUtils';
-
-import AppContainer from '../services/AppContainer';
-import PageContainer from '../services/PageContainer';
+import AppContainer from '../../services/AppContainer';
+import PageContainer from '../../services/PageContainer';
 
 
 import ShareLinkList from './ShareLinkList';
 import ShareLinkList from './ShareLinkList';
 import ShareLinkForm from './ShareLinkForm';
 import ShareLinkForm from './ShareLinkForm';
 
 
-import { toastSuccess, toastError } from '../util/apiNotification';
+import { toastSuccess, toastError } from '../../util/apiNotification';
 
 
-class OutsideShareLinkModal extends React.Component {
+class ShareLink extends React.Component {
 
 
   constructor() {
   constructor() {
     super();
     super();
@@ -90,33 +85,27 @@ class OutsideShareLinkModal extends React.Component {
     const { t } = this.props;
     const { t } = this.props;
 
 
     return (
     return (
-      <Modal size="xl" isOpen={this.props.isOpen} toggle={this.props.onClose}>
-        <ModalHeader tag="h4" toggle={this.props.onClose} className="bg-primary text-light">{t('share_links.Shere this page link to public')}
-        </ModalHeader>
-        <ModalBody>
-          <div className="container">
-            <h3 className="grw-modal-head  d-flex  pb-2">
-              { t('share_links.share_link_list') }
-              <button className="btn btn-danger ml-auto " type="button" onClick={this.deleteAllLinksButtonHandler}>{t('delete_all')}</button>
-            </h3>
-
-            <div>
-              <ShareLinkList
-                shareLinks={this.state.shareLinks}
-                onClickDeleteButton={this.deleteLinkById}
-              />
-              <button
-                className="btn btn-outline-secondary d-block mx-auto px-5 mb-3"
-                type="button"
-                onClick={this.toggleShareLinkFormHandler}
-              >
-                {this.state.isOpenShareLinkForm ? t('Close') : t('New')}
-              </button>
-              {this.state.isOpenShareLinkForm && <ShareLinkForm onCloseForm={this.toggleShareLinkFormHandler} />}
-            </div>
-          </div>
-        </ModalBody>
-      </Modal>
+      <div className="container">
+        <h3 className="grw-modal-head  d-flex  pb-2">
+          { t('share_links.share_link_list') }
+          <button className="btn btn-danger ml-auto " type="button" onClick={this.deleteAllLinksButtonHandler}>{t('delete_all')}</button>
+        </h3>
+
+        <div>
+          <ShareLinkList
+            shareLinks={this.state.shareLinks}
+            onClickDeleteButton={this.deleteLinkById}
+          />
+          <button
+            className="btn btn-outline-secondary d-block mx-auto px-5 mb-3"
+            type="button"
+            onClick={this.toggleShareLinkFormHandler}
+          >
+            {this.state.isOpenShareLinkForm ? t('Close') : t('New')}
+          </button>
+          {this.state.isOpenShareLinkForm && <ShareLinkForm onCloseForm={this.toggleShareLinkFormHandler} />}
+        </div>
+      </div>
     );
     );
   }
   }
 
 
@@ -125,9 +114,9 @@ class OutsideShareLinkModal extends React.Component {
 /**
 /**
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
-const ModalControlWrapper = withUnstatedContainers(OutsideShareLinkModal, [AppContainer, PageContainer]);
+const ShareLinkWrapper = withUnstatedContainers(ShareLink, [AppContainer, PageContainer]);
 
 
-OutsideShareLinkModal.propTypes = {
+ShareLink.propTypes = {
   t: PropTypes.func.isRequired, //  i18next
   t: PropTypes.func.isRequired, //  i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
@@ -136,4 +125,4 @@ OutsideShareLinkModal.propTypes = {
   onClose: PropTypes.func.isRequired,
   onClose: PropTypes.func.isRequired,
 };
 };
 
 
-export default withTranslation()(ModalControlWrapper);
+export default withTranslation()(ShareLinkWrapper);

+ 4 - 4
src/client/js/components/ShareLinkForm.jsx → src/client/js/components/ShareLink/ShareLinkForm.jsx

@@ -6,12 +6,12 @@ import dateFnsFormat from 'date-fns/format';
 import parse from 'date-fns/parse';
 import parse from 'date-fns/parse';
 
 
 import { isInteger } from 'core-js/fn/number';
 import { isInteger } from 'core-js/fn/number';
-import { withUnstatedContainers } from './UnstatedUtils';
+import { withUnstatedContainers } from '../UnstatedUtils';
 
 
-import { toastSuccess, toastError } from '../util/apiNotification';
+import { toastSuccess, toastError } from '../../util/apiNotification';
 
 
-import AppContainer from '../services/AppContainer';
-import PageContainer from '../services/PageContainer';
+import AppContainer from '../../services/AppContainer';
+import PageContainer from '../../services/PageContainer';
 
 
 class ShareLinkForm extends React.Component {
 class ShareLinkForm extends React.Component {
 
 

+ 3 - 3
src/client/js/components/ShareLinkList.jsx → src/client/js/components/ShareLink/ShareLinkList.jsx

@@ -5,10 +5,10 @@ import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 import dateFnsFormat from 'date-fns/format';
 import dateFnsFormat from 'date-fns/format';
 
 
-import { withUnstatedContainers } from './UnstatedUtils';
+import { withUnstatedContainers } from '../UnstatedUtils';
 
 
-import AppContainer from '../services/AppContainer';
-import CopyDropdown from './Page/CopyDropdown';
+import AppContainer from '../../services/AppContainer';
+import CopyDropdown from '../Page/CopyDropdown';
 
 
 const ShareLinkList = (props) => {
 const ShareLinkList = (props) => {
 
 

+ 9 - 0
src/client/js/components/TopOfTableContents.jsx

@@ -9,6 +9,7 @@ import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import RecentChangesIcon from './Icons/RecentChangesIcon';
 import RecentChangesIcon from './Icons/RecentChangesIcon';
 import AttachmentIcon from './Icons/AttachmentIcon';
 import AttachmentIcon from './Icons/AttachmentIcon';
+import ShareLinkIcon from './Icons/ShareLinkIcon';
 
 
 import PageAccessoriesModal from './PageAccessoriesModal';
 import PageAccessoriesModal from './PageAccessoriesModal';
 
 
@@ -63,6 +64,14 @@ const TopOfTableContents = (props) => {
           <AttachmentIcon />
           <AttachmentIcon />
         </button>
         </button>
 
 
+        <button
+          type="button"
+          className="btn btn-link grw-btn-top-of-table"
+          onClick={() => pageAccessoriesContainer.openPageAccessoriesModal('share-link')}
+        >
+          <ShareLinkIcon />
+        </button>
+
         <div
         <div
           id="seen-user-list"
           id="seen-user-list"
           data-user-ids-str="{{ page.seenUsers|slice(-15)|default([])|reverse|join(',') }}"
           data-user-ids-str="{{ page.seenUsers|slice(-15)|default([])|reverse|join(',') }}"

+ 3 - 0
src/server/models/attachment.js

@@ -7,6 +7,7 @@ const path = require('path');
 
 
 const mongoose = require('mongoose');
 const mongoose = require('mongoose');
 const uniqueValidator = require('mongoose-unique-validator');
 const uniqueValidator = require('mongoose-unique-validator');
+const mongoosePaginate = require('mongoose-paginate-v2');
 
 
 const ObjectId = mongoose.Schema.Types.ObjectId;
 const ObjectId = mongoose.Schema.Types.ObjectId;
 
 
@@ -29,6 +30,7 @@ module.exports = function(crowi) {
     createdAt: { type: Date, default: Date.now },
     createdAt: { type: Date, default: Date.now },
   });
   });
   attachmentSchema.plugin(uniqueValidator);
   attachmentSchema.plugin(uniqueValidator);
+  attachmentSchema.plugin(mongoosePaginate);
 
 
   attachmentSchema.virtual('filePathProxied').get(function() {
   attachmentSchema.virtual('filePathProxied').get(function() {
     return `/attachment/${this._id}`;
     return `/attachment/${this._id}`;
@@ -63,5 +65,6 @@ module.exports = function(crowi) {
     return attachment;
     return attachment;
   };
   };
 
 
+
   return mongoose.model('Attachment', attachmentSchema);
   return mongoose.model('Attachment', attachmentSchema);
 };
 };

+ 11 - 4
src/server/routes/apiv3/attachment.js

@@ -39,10 +39,12 @@ module.exports = (crowi) => {
    *              type: string
    *              type: string
    */
    */
   router.get('/list', accessTokenParser, loginRequired, async(req, res) => {
   router.get('/list', accessTokenParser, loginRequired, async(req, res) => {
+    const offset = +req.query.offset || 0;
+    const limit = +req.query.limit || 30;
+    const queryOptions = { offset, limit };
 
 
     try {
     try {
-      const pageId = req.query.page;
-
+      const pageId = req.query.pageId;
       // check whether accessible
       // check whether accessible
       const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
       const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
       if (!isAccessible) {
       if (!isAccessible) {
@@ -50,8 +52,13 @@ module.exports = (crowi) => {
         return res.apiv3Err(new ErrorV3(msg, 'attachment-list-failed'), 403);
         return res.apiv3Err(new ErrorV3(msg, 'attachment-list-failed'), 403);
       }
       }
 
 
-      const attachments = await Attachment.find({ page: pageId });
-      return res.apiv3({ attachments });
+      const paginateResult = await Attachment.paginate(
+        { page: pageId },
+        queryOptions,
+      );
+
+      return res.apiv3({ paginateResult });
+
     }
     }
     catch (err) {
     catch (err) {
       logger.error('Attachment not found', err);
       logger.error('Attachment not found', err);

+ 0 - 58
src/server/routes/attachment.js

@@ -127,7 +127,6 @@ const ApiResponse = require('../util/apiResponse');
 
 
 module.exports = function(crowi, app) {
 module.exports = function(crowi, app) {
   const Attachment = crowi.model('Attachment');
   const Attachment = crowi.model('Attachment');
-  const User = crowi.model('User');
   const Page = crowi.model('Page');
   const Page = crowi.model('Page');
   const { fileUploadService, attachmentService } = crowi;
   const { fileUploadService, attachmentService } = crowi;
 
 
@@ -295,63 +294,6 @@ module.exports = function(crowi, app) {
     return responseForAttachment(req, res, attachment);
     return responseForAttachment(req, res, attachment);
   };
   };
 
 
-  /**
-   * @swagger
-   *
-   *    /attachments.list:
-   *      get:
-   *        tags: [Attachments, CrowiCompatibles]
-   *        operationId: listAttachments
-   *        summary: /attachments.list
-   *        description: Get list of attachments in page
-   *        parameters:
-   *          - in: query
-   *            name: page_id
-   *            schema:
-   *              $ref: '#/components/schemas/Page/properties/_id'
-   *            required: true
-   *        responses:
-   *          200:
-   *            description: Succeeded to get list of attachments.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *                    attachments:
-   *                      type: array
-   *                      items:
-   *                        $ref: '#/components/schemas/Attachment'
-   *                      description: attachment list
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * @api {get} /attachments.list Get attachments of the page
-   * @apiName ListAttachments
-   * @apiGroup Attachment
-   *
-   * @apiParam {String} page_id
-   */
-  api.list = async function(req, res) {
-    const id = req.query.page_id || null;
-    if (!id) {
-      return res.json(ApiResponse.error('Parameters page_id is required.'));
-    }
-
-    let attachments = await Attachment.find({ page: id })
-      .sort({ updatedAt: 1 })
-      .populate({ path: 'creator', select: User.USER_PUBLIC_FIELDS });
-
-    attachments = attachments.map((attachment) => {
-      return attachment.toObject({ virtuals: true });
-    });
-
-    return res.json(ApiResponse.success({ attachments }));
-  };
 
 
   /**
   /**
    * @swagger
    * @swagger

+ 0 - 1
src/server/routes/index.js

@@ -156,7 +156,6 @@ module.exports = function(crowi, app) {
   app.post('/_api/comments.add'       , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , csrf, comment.api.add);
   app.post('/_api/comments.add'       , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , csrf, comment.api.add);
   app.post('/_api/comments.update'    , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , csrf, comment.api.update);
   app.post('/_api/comments.update'    , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , csrf, comment.api.update);
   app.post('/_api/comments.remove'    , accessTokenParser , loginRequiredStrictly , csrf, comment.api.remove);
   app.post('/_api/comments.remove'    , accessTokenParser , loginRequiredStrictly , csrf, comment.api.remove);
-  app.get('/_api/attachments.list'    , accessTokenParser , loginRequired , attachment.api.list);
   app.post('/_api/attachments.add'                  , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly ,csrf, attachment.api.add);
   app.post('/_api/attachments.add'                  , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly ,csrf, attachment.api.add);
   app.post('/_api/attachments.uploadProfileImage'   , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly ,csrf, attachment.api.uploadProfileImage);
   app.post('/_api/attachments.uploadProfileImage'   , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly ,csrf, attachment.api.uploadProfileImage);
   app.post('/_api/attachments.remove'               , accessTokenParser , loginRequiredStrictly , csrf, attachment.api.remove);
   app.post('/_api/attachments.remove'               , accessTokenParser , loginRequiredStrictly , csrf, attachment.api.remove);

+ 0 - 5
src/server/views/widget/page_tabs.html

@@ -54,11 +54,6 @@
     </a>
     </a>
   </li>
   </li>
 
 
-  <!-- Outside-share-link -->
-  {% if !isTrashPage() %}
-    <li id="page-share-management" class="nav-item dropdown d-edit-none"></li>
-  {% endif %}
-
   <!-- icon-options-vertical -->
   <!-- icon-options-vertical -->
   {% if !isTrashPage() %}
   {% if !isTrashPage() %}
     <li id="page-management" class="nav-item dropdown d-edit-none"></li>
     <li id="page-management" class="nav-item dropdown d-edit-none"></li>