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

Merge branch 'support/share-link-for-outside-for-merge' into support/not-found-page

itizawa 5 лет назад
Родитель
Сommit
180e833c2a

+ 2 - 1
resource/locales/en-US/translation.json

@@ -245,7 +245,8 @@
       "unlinked": "Redirect pages to this page have been deleted.",
       "unlinked": "Redirect pages to this page have been deleted.",
       "restricted": "Access to this page is restricted",
       "restricted": "Access to this page is restricted",
       "stale": "More than {{count}} year has passed since last update.",
       "stale": "More than {{count}} year has passed since last update.",
-      "stale_plural": "More than {{count}} years has passed since last update."
+      "stale_plural": "More than {{count}} years has passed since last update.",
+      "expiration": "This share link will expire <strong>{{expiredAt}}</strong>."
     }
     }
   },
   },
   "page_edit": {
   "page_edit": {

+ 2 - 1
resource/locales/ja/translation.json

@@ -243,7 +243,8 @@
       "duplicated": "このページは <code>%s</code> から複製されました。",
       "duplicated": "このページは <code>%s</code> から複製されました。",
       "unlinked": "このページへのリダイレクトは削除されました。",
       "unlinked": "このページへのリダイレクトは削除されました。",
       "restricted": "このページの閲覧は制限されています",
       "restricted": "このページの閲覧は制限されています",
-      "stale": "このページは最終更新日から{{count}}年以上が経過しています。"
+      "stale": "このページは最終更新日から{{count}}年以上が経過しています。",
+      "expiration": "この共有パーマリンクの有効期限は <strong>{{expiredAt}}</strong> です。"
     }
     }
   },
   },
   "page_edit": {
   "page_edit": {

+ 3 - 0
src/client/js/bootstrap.jsx

@@ -7,6 +7,7 @@ import SearchTop from './components/Navbar/SearchTop';
 import NavbarToggler from './components/Navbar/NavbarToggler';
 import NavbarToggler from './components/Navbar/NavbarToggler';
 import PersonalDropdown from './components/Navbar/PersonalDropdown';
 import PersonalDropdown from './components/Navbar/PersonalDropdown';
 import Sidebar from './components/Sidebar';
 import Sidebar from './components/Sidebar';
+import ShareLinkAlert from './components/Page/ShareLinkAlert';
 import StaffCredit from './components/StaffCredit/StaffCredit';
 import StaffCredit from './components/StaffCredit/StaffCredit';
 
 
 import AppContainer from './services/AppContainer';
 import AppContainer from './services/AppContainer';
@@ -51,6 +52,8 @@ const componentMappings = {
 
 
   'grw-sidebar-wrapper': <Sidebar />,
   'grw-sidebar-wrapper': <Sidebar />,
 
 
+  'share-link-alert': <ShareLinkAlert />,
+
   'staff-credit': <StaffCredit />,
   'staff-credit': <StaffCredit />,
 };
 };
 
 

+ 46 - 0
src/client/js/components/Page/ShareLinkAlert.jsx

@@ -0,0 +1,46 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+const ShareLinkAlert = (props) => {
+  const { t } = props;
+  const shareContent = document.getElementById('is-shared-page');
+  const expiredAt = shareContent.getAttribute('data-share-link-expired-at');
+  const createdAt = shareContent.getAttribute('data-share-link-created-at');
+  const wholeTime = new Date(expiredAt).getTime() - new Date(createdAt).getTime();
+  const remainingTime = new Date(expiredAt).getTime() - new Date().getTime();
+  const ratio = remainingTime / wholeTime;
+
+  function specifyColor() {
+    let color;
+    if (ratio >= 0.75) {
+      color = 'success';
+    }
+    else if (ratio < 0.75 && ratio >= 0.5) {
+      color = 'info';
+    }
+    else if (ratio < 0.5 && ratio >= 0.25) {
+      color = 'warning';
+    }
+    else {
+      color = 'danger';
+    }
+    return color;
+  }
+
+  return (
+    <p className={`alert alert-${specifyColor()} py-3 px-4`}>
+      <i className="icon-fw icon-link"></i>
+      {/* eslint-disable-next-line react/no-danger */}
+      <span dangerouslySetInnerHTML={{ __html: t('page_page.notice.expiration', { expiredAt }) }} />
+    </p>
+  );
+};
+
+
+ShareLinkAlert.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+};
+
+export default withTranslation()(ShareLinkAlert);

+ 49 - 14
src/client/js/components/ShareLinkList.jsx

@@ -1,4 +1,5 @@
 import React from 'react';
 import React from 'react';
+import * as toastr from 'toastr';
 
 
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
@@ -8,8 +9,53 @@ import AppContainer from '../services/AppContainer';
 
 
 const ShareLinkList = (props) => {
 const ShareLinkList = (props) => {
 
 
-  function getShareLinkList() {
-    return ['Replace with API'];
+  function deleteLinkHandler(shareLink) {
+    try {
+      // call api
+      toastr.success(`Successfully deleted ${shareLink._id}`);
+    }
+    catch (err) {
+      toastr.error(new Error(`Failed to delete ${shareLink._id}`));
+    }
+  }
+
+  function GetShareLinkList() {
+    // dummy data
+    const dummyDate = new Date().toString();
+    const shareLinks = [
+      {
+        _id: '507f1f77bcf86cd799439011', link: '/507f1f77bcf86cd799439011', expiration: dummyDate, description: 'foobar',
+      },
+      {
+        _id: '52fcebd19a5c4ea066dbfa12', link: '/52fcebd19a5c4ea066dbfa12', expiration: dummyDate, description: 'test',
+      },
+      {
+        _id: '54759eb3c090d83494e2d804', link: '/54759eb3c090d83494e2d804', expiration: dummyDate, description: 'hoge',
+      },
+      {
+        _id: '5349b4ddd2781d08c09890f3', link: '/5349b4ddd2781d08c09890f3', expiration: dummyDate, description: 'fuga',
+      },
+      {
+        _id: '5349b4ddd2781d08c09890f4', link: '/5349b4ddd2781d08c09890f4', expiration: dummyDate, description: 'piyo',
+      },
+    ];
+
+    return (
+      <>
+        {shareLinks.map(shareLink => (
+          <tr>
+            <td>{shareLink.link}</td>
+            <td>{shareLink.expiration}</td>
+            <td>{shareLink.description}</td>
+            <td>
+              <button className="btn btn-outline-warning" type="button" onClick={() => deleteLinkHandler(shareLink)}>
+                <i className="icon-trash"></i>Delete
+              </button>
+            </td>
+          </tr>
+        ))}
+      </>
+    );
   }
   }
 
 
   return (
   return (
@@ -24,18 +70,7 @@ const ShareLinkList = (props) => {
           </tr>
           </tr>
         </thead>
         </thead>
         <tbody>
         <tbody>
-          {
-            getShareLinkList().map((shareLink) => {
-              return (
-                <>
-                  <td>{ shareLink }</td>
-                  <td>{ shareLink }</td>
-                  <td>{ shareLink }</td>
-                  <td>{ shareLink }</td>
-                </>
-              );
-            })
-          }
+          <GetShareLinkList />
         </tbody>
         </tbody>
       </table>
       </table>
     </div>
     </div>

+ 33 - 3
src/server/routes/apiv3/share-links.js

@@ -118,10 +118,40 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
-  // TDOO write swagger
-  router.delete('/all', loginRequired, async(req, res) => {
+  /**
+  * @swagger
+  *
+  *    /share-links/:
+  *      delete:
+  *        tags: [ShareLinks]
+  *        summary: /share-links/
+  *        description: delete all share links related one page
+  *        requestBody:
+  *           required: true
+  *           content:
+  *             application/json:
+  *               schema:
+  *                 properties:
+  *                   relatedPage:
+  *                     type: string
+  *                     description: delete all share links that related one page
+  *        responses:
+  *          200:
+  *            description: Succeeded to delete o all share links related one page
+  */
+  router.delete('/', loginRequired, csrf, async(req, res) => {
     const { relatedPage } = req.body;
     const { relatedPage } = req.body;
-    // TODO GW-2694 Delete all share links
+    const ShareLink = crowi.model('ShareLink');
+
+    try {
+      const deletedShareLink = await ShareLink.remove({ relatedPage });
+      return res.apiv3(deletedShareLink);
+    }
+    catch (err) {
+      const msg = 'Error occured in delete share link';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
+    }
   });
   });
 
 
   /**
   /**

+ 6 - 2
src/server/routes/page.js

@@ -446,8 +446,8 @@ module.exports = function(crowi, app) {
     const layoutName = configManager.getConfig('crowi', 'customize:layout');
     const layoutName = configManager.getConfig('crowi', 'customize:layout');
     const view = `layout-${layoutName}/shared_page`;
     const view = `layout-${layoutName}/shared_page`;
 
 
-    const shareLink = await ShareLink.find({ _id: linkId }).populate('Page');
-    const page = shareLink.relatedPage;
+    const shareLink = await ShareLink.findOne({ _id: linkId }).populate('relatedPage');
+    let page = shareLink.relatedPage;
 
 
     if (page == null) {
     if (page == null) {
       // page is not found
       // page is not found
@@ -456,6 +456,10 @@ module.exports = function(crowi, app) {
 
 
     const renderVars = {};
     const renderVars = {};
 
 
+    renderVars.sharelink = shareLink;
+
+    // populate
+    page = await page.populateDataToShowRevision();
     addRendarVarsForPage(renderVars, page);
     addRendarVarsForPage(renderVars, page);
     addRendarVarsForScope(renderVars, page);
     addRendarVarsForScope(renderVars, page);
 
 

+ 14 - 13
src/server/views/layout-growi/shared_page.html

@@ -14,24 +14,25 @@
 {% endblock %}
 {% endblock %}
 
 
 {% block content_main %}
 {% block content_main %}
-  <div class="row" id="is-shared-page">
+  <div class="row" id="is-shared-page" data-share-link-expired-at="{{ sharelink.expiredAt|datetz('Y/m/d H:i:s')}}" data-share-link-created-at="{{ sharelink.createdAt|datetz('Y/m/d H:i:s')}}">
     {% block content_page %}
     {% block content_page %}
-    <div class="col grw-page-content-container">
+      <div class="col grw-page-content-container">
+      <div id="share-link-alert"></div>
 
 
-      {% include '../widget/page_content.html' %}
-      {# force remove #revision-toc from #content_main of parent #}
-      <script>
-        $('#revision-toc').remove();
-      </script>
+        {% include '../widget/page_content.html' %}
+        {# force remove #revision-toc from #content_main of parent #}
+        <script>
+          $('#revision-toc').remove();
+        </script>
 
 
-    </div>
+      </div>
 
 
-    {# relocate #revision-toc #}
-    <div class="col-xl-2 col-lg-3 d-none d-lg-block revision-toc-container">
-      <div id="revision-toc" class="revision-toc mt-3 sps sps--abv" data-sps-offset="123">
-        <div id="revision-toc-content" class="revision-toc-content"></div>
+      {# relocate #revision-toc #}
+      <div class="col-xl-2 col-lg-3 d-none d-lg-block revision-toc-container">
+        <div id="revision-toc" class="revision-toc mt-3 sps sps--abv" data-sps-offset="123">
+          <div id="revision-toc-content" class="revision-toc-content"></div>
+        </div>
       </div>
       </div>
-    </div>
     {% endblock %}
     {% endblock %}
 
 
   </div>
   </div>

+ 2 - 1
src/server/views/layout-kibela/shared_page.html

@@ -14,8 +14,9 @@
 {% endblock %}
 {% endblock %}
 
 
 {% block content_main %}
 {% block content_main %}
-  <div class="row" id="is-shared-page">
+  <div class="row" id="is-shared-page" data-share-link-expired-at="{{ sharelink.expiredAt|datetz('Y/m/d H:i:s')}}"  data-share-link-created-at="{{ sharelink.createdAt|datetz('Y/m/d H:i:s')}}">
     <div class="col-12 col-xl-9 col-lg-8 bg-white round-corner">
     <div class="col-12 col-xl-9 col-lg-8 bg-white round-corner">
+      <div id="share-link-alert"></div>
 
 
       {% include '../widget/page_content.html' %}
       {% include '../widget/page_content.html' %}
       {# force remove #revision-toc from #content_main of parent #}
       {# force remove #revision-toc from #content_main of parent #}