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

Merge remote-tracking branch 'origin/support/share-link-for-outside-for-merge' into feat/show-alert-for-shared-page

itizawa 5 лет назад
Родитель
Сommit
7d7ee52533

+ 17 - 6
src/client/js/components/OutsideShareLinkModal.jsx

@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 
 import {
-  Modal, ModalHeader, ModalBody, ModalFooter,
+  Modal, ModalHeader, ModalBody,
 } from 'reactstrap';
 
 import { withTranslation } from 'react-i18next';
@@ -12,20 +12,31 @@ import { createSubscribedElement } from './UnstatedUtils';
 import AppContainer from '../services/AppContainer';
 import PageContainer from '../services/PageContainer';
 
+import ShareLinkList from './ShareLinkList';
+import ShareLinkForm from './ShareLinkForm';
+
 const OutsideShareLinkModal = (props) => {
 
   /* const { t } = props; */
 
-
   return (
     <Modal size="lg" isOpen={props.isOpen} toggle={props.onClose} className="grw-create-page">
-      <ModalHeader tag="h4" toggle={props.onClose} className="bg-primary text-light">Hi there!
+      <ModalHeader tag="h4" toggle={props.onClose} className="bg-primary text-light">Title
       </ModalHeader>
       <ModalBody>
-        <h1>Hi there</h1>
+        <div className="container">
+          <div className="row align-items-center mb-3">
+            <h4 className="col-10">Shared Link List</h4>
+            <button className="col btn btn-danger" type="button">Delete all links</button>
+          </div>
+
+          <div>
+            <ShareLinkList />
+            <button className="btn btn-outline-secondary d-block mx-auto px-5 mb-3" type="button">+</button>
+            <ShareLinkForm />
+          </div>
+        </div>
       </ModalBody>
-      <ModalFooter>
-      </ModalFooter>
     </Modal>
   );
 };

+ 64 - 0
src/client/js/components/ShareLinkForm.jsx

@@ -0,0 +1,64 @@
+import React from 'react';
+
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from './UnstatedUtils';
+
+
+import AppContainer from '../services/AppContainer';
+import PageContainer from '../services/PageContainer';
+
+const ShareLinkForm = (props) => {
+  return (
+    <div className="share-link-form border">
+      <h4 className="ml-3">Expiration Date</h4>
+      <form>
+        <div className="form-group">
+          <div className="custom-control custom-radio offset-4 mb-2">
+            <input id="customRadio1" name="customRadio" type="radio" className="custom-control-input"></input>
+            <label className="custom-control-label" htmlFor="customRadio1">Unlimited</label>
+          </div>
+
+          <div className="custom-control custom-radio offset-4 mb-2">
+            <input id="customRadio2" name="customRadio" type="radio" className="custom-control-input"></input>
+            <label className="custom-control-label" htmlFor="customRadio2">
+              <div className="row align-items-center m-0">
+                <input className="form-control col-2" type="number" min="1" max="7" value="7"></input>
+                <span className="col-auto">Days</span>
+              </div>
+            </label>
+          </div>
+
+          <div className="custom-control custom-radio offset-4 mb-2">
+            <input id="customRadio3" name="customRadio" type="radio" className="custom-control-input"></input>
+            <label className="custom-control-label" htmlFor="customRadio3">
+              Custom
+              <div className="date-picker">Date Picker</div>
+            </label>
+          </div>
+
+          <hr />
+
+          <div className="form-group row">
+            <label htmlFor="inputDesc" className="col-md-4 col-form-label">Description</label>
+            <div className="col-md-4">
+              <input type="text" className="form-control" id="inputDesc" placeholder="Enter description"></input>
+            </div>
+          </div>
+
+          <div className="form-group row">
+            <div className="offset-8 col">
+              <button type="button" className="btn btn-primary">Issue</button>
+            </div>
+          </div>
+        </div>
+      </form>
+    </div>
+  );
+};
+
+const ShareLinkFormWrapper = (props) => {
+  return createSubscribedElement(ShareLinkForm, props, [AppContainer, PageContainer]);
+};
+
+export default withTranslation()(ShareLinkFormWrapper);

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

@@ -0,0 +1,49 @@
+import React from 'react';
+
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from './UnstatedUtils';
+
+import AppContainer from '../services/AppContainer';
+
+const ShareLinkList = (props) => {
+
+  function getShareLinkList() {
+    return ['Replace with API'];
+  }
+
+  return (
+    <div className="table-responsive">
+      <table className="table table-bordered">
+        <thead>
+          <tr>
+            <th>Link</th>
+            <th>Expiration</th>
+            <th>Description</th>
+            <th>Order</th>
+          </tr>
+        </thead>
+        <tbody>
+          {
+            getShareLinkList().map((shareLink) => {
+              return (
+                <>
+                  <td>{ shareLink }</td>
+                  <td>{ shareLink }</td>
+                  <td>{ shareLink }</td>
+                  <td>{ shareLink }</td>
+                </>
+              );
+            })
+          }
+        </tbody>
+      </table>
+    </div>
+  );
+};
+
+const ShareLinkListWrapper = (props) => {
+  return createSubscribedElement(ShareLinkList, props, [AppContainer]);
+};
+
+export default withTranslation()(ShareLinkListWrapper);

+ 5 - 5
src/client/styles/scss/_search.scss

@@ -241,25 +241,25 @@
     th {
       text-align: right;
     }
-  
+
     td {
       overflow-wrap: anywhere;
       white-space: normal !important;
     }
-  
+
     @include media-breakpoint-down(xs) {
       th,
       td {
         display: block;
       }
-      
+
       th {
         text-align: left;
       }
-      
+
       td {
-        border-top: none !important;
         padding-top: 0 !important;
+        border-top: none !important;
       }
     }
   }

+ 12 - 0
src/client/styles/scss/_sharelink.scss

@@ -0,0 +1,12 @@
+.share-link-form {
+  /* Chrome/Safari */
+  input[type='number']::-webkit-outer-spin-button,
+  input[type='number']::-webkit-inner-spin-button {
+    -webkit-appearance: none;
+  }
+
+  /* Firefox */
+  input[type='number'] {
+    -moz-appearance: textfield;
+  }
+}

+ 1 - 0
src/client/styles/scss/style-app.scss

@@ -59,6 +59,7 @@
 @import 'staff_credit';
 @import 'waves';
 @import 'wiki';
+@import 'sharelink';
 
 /*
  * for Guest User Mode

+ 1 - 1
src/server/models/share-link.js

@@ -16,7 +16,7 @@ const schema = new mongoose.Schema({
     required: true,
     index: true,
   },
-  expiration: { type: Date },
+  expiredAt: { type: Date },
   description: { type: String },
   createdAt: { type: Date, default: Date.now, required: true },
 });

+ 93 - 12
src/server/routes/apiv3/share-links.js

@@ -8,38 +8,119 @@ const express = require('express');
 
 const router = express.Router();
 
-const { body } = require('express-validator/check');
+const { body, query } = require('express-validator/check');
 
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
+const validator = {};
+
+const today = new Date();
+
 /**
  * @swagger
  *  tags:
- *    name: ShareLinks
+ *    name: ShareLink
  */
 
 module.exports = (crowi) => {
   const loginRequired = require('../../middleware/login-required')(crowi);
   const csrf = require('../../middleware/csrf')(crowi);
-
+  const { ApiV3FormValidator } = crowi.middlewares;
   const ShareLink = crowi.model('ShareLink');
 
-  // TDOO write swagger
-  router.get('/', loginRequired, async(req, res) => {
-    const { pageId } = req.query;
-    // TODO GW-2616 get all share links associated with the page
+
+  /**
+   * @swagger
+   *
+   *  paths:
+   *    /share-links/:
+   *      post:
+   *        tags: [ShareLink]
+   *        description: get share links
+   *        parameters:
+   *          - name: relatedPage
+   *            in: query
+   *            required: true
+   *            description: page id of share link
+   *            schema:
+   *              type: string
+   *        responses:
+   *          200:
+   *            description: Succeeded to get share links
+   */
+  router.get('/', loginRequired, csrf, ApiV3FormValidator, async(req, res) => {
+    const { relatedPage } = req.query;
+    try {
+      const paginateResult = await ShareLink.find({ relatedPage: { $in: relatedPage } });
+      return res.apiv3({ paginateResult });
+    }
+    catch (err) {
+      const msg = 'Error occurred in get share link';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'get-shareLink-failed'));
+    }
   });
 
+  validator.shareLinkStatus = [
+    // validate the page id is null
+    body('relatedPage').not().isEmpty().withMessage('Page Id is null'),
 
-  // TDOO write swagger
-  router.post('/', loginRequired, async(req, res) => {
-    const { pageId } = req.body;
-    // TODO GW-2609 publish the share link
+    // validate expireation date is not empty, is not before today and is date.
+    body('expiredAt').isAfter(today.toString()).withMessage('Your Selected date is past'),
+
+    // validate the length of description is max 100.
+    body('description').isLength({ min: 0, max: 100 }).withMessage('Max length is 100'),
+
+  ];
+
+  /**
+   * @swagger
+   *
+   *  paths:
+   *    /share-links/:
+   *      post:
+   *        tags: [ShareLink]
+   *        description: Create new share link
+   *        parameters:
+   *          - name: relatedPage
+   *            in: query
+   *            required: true
+   *            description: page id of share link
+   *            schema:
+   *              type: string
+   *          - name: expiredAt
+   *            in: query
+   *            description: expiration date of share link
+   *            schema:
+   *              type: string
+   *          - name: description
+   *            in: query
+   *            description: description of share link
+   *            schema:
+   *              type: string
+   *        responses:
+   *          200:
+   *            description: Succeeded to create one share link
+   */
+
+  router.post('/', loginRequired, csrf, validator.shareLinkStatus, ApiV3FormValidator, async(req, res) => {
+    const { relatedPage, expiredAt, description } = req.body;
+    const ShareLink = crowi.model('ShareLink');
+
+    try {
+      const postedShareLink = await ShareLink.create({ relatedPage, expiredAt, description });
+      return res.apiv3(postedShareLink);
+    }
+    catch (err) {
+      const msg = 'Error occured in post share link';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'post-shareLink-failed'));
+    }
   });
 
   // TDOO write swagger
   router.delete('/all', loginRequired, async(req, res) => {
-    const { pageId } = req.body;
+    const { relatedPage } = req.body;
     // TODO GW-2694 Delete all share links
   });