itizawa 6 лет назад
Родитель
Сommit
c96cc87901

+ 27 - 33
src/client/js/components/BookmarkButton.jsx

@@ -1,13 +1,17 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
-export default class BookmarkButton extends React.Component {
+import { toastError } from '../util/apiNotification';
+import AppContainer from '../services/AppContainer';
+import { createSubscribedElement } from './UnstatedUtils';
+
+class BookmarkButton extends React.Component {
 
 
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
     this.state = {
     this.state = {
-      bookmarked: false,
+      isBookmarked: false,
     };
     };
 
 
     this.handleClick = this.handleClick.bind(this);
     this.handleClick = this.handleClick.bind(this);
@@ -23,38 +27,24 @@ export default class BookmarkButton extends React.Component {
     this.props.crowi.apiGet('/bookmarks.get', { page_id: this.props.pageId })
     this.props.crowi.apiGet('/bookmarks.get', { page_id: this.props.pageId })
       .then((res) => {
       .then((res) => {
         if (res.bookmark) {
         if (res.bookmark) {
-          this.markBookmarked();
+          this.setState({ isBookmarked: true });
         }
         }
       });
       });
   }
   }
 
 
-  handleClick(event) {
-    event.preventDefault();
-
-    const pageId = this.props.pageId;
+  async handleClick() {
+    const { appContainer, pageId } = this.props;
+    const { isBookmarked } = this.state;
 
 
-    if (!this.state.bookmarked) {
-      this.props.crowi.apiPost('/bookmarks.add', { page_id: pageId })
-        .then((res) => {
-          this.markBookmarked();
-        });
+    try {
+      this.setState({ isBookmarked: !isBookmarked });
+      await appContainer.apiv3.put('/bookmarks', { pageId, isBookmarked });
     }
     }
-    else {
-      this.props.crowi.apiPost('/bookmarks.remove', { page_id: pageId })
-        .then((res) => {
-          this.markUnBookmarked();
-        });
+    catch (err) {
+      toastError(err);
     }
     }
   }
   }
 
 
-  markBookmarked() {
-    this.setState({ bookmarked: true });
-  }
-
-  markUnBookmarked() {
-    this.setState({ bookmarked: false });
-  }
-
   isUserLoggedIn() {
   isUserLoggedIn() {
     return this.props.crowi.currentUserId != null;
     return this.props.crowi.currentUserId != null;
   }
   }
@@ -65,20 +55,13 @@ export default class BookmarkButton extends React.Component {
       return <div></div>;
       return <div></div>;
     }
     }
 
 
-    const btnSizeClassName = this.props.size ? `btn-${this.props.size}` : 'btn-md';
-    const addedClassNames = [
-      this.state.bookmarked ? 'active' : '',
-      btnSizeClassName,
-    ];
-    const addedClassName = addedClassNames.join(' ');
-
     return (
     return (
       <button
       <button
         type="button"
         type="button"
         href="#"
         href="#"
         title="Bookmark"
         title="Bookmark"
         onClick={this.handleClick}
         onClick={this.handleClick}
-        className={`btn btn-circle btn-outline-warning btn-bookmark border-0 ${addedClassName}`}
+        className={`btn btn-circle btn-outline-warning btn-bookmark border-0 ${this.state.bookmarked ? 'active' : ''}`}
       >
       >
         <i className="icon-star"></i>
         <i className="icon-star"></i>
       </button>
       </button>
@@ -88,7 +71,18 @@ export default class BookmarkButton extends React.Component {
 }
 }
 
 
 BookmarkButton.propTypes = {
 BookmarkButton.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+
   pageId: PropTypes.string,
   pageId: PropTypes.string,
   crowi: PropTypes.object.isRequired,
   crowi: PropTypes.object.isRequired,
   size: PropTypes.string,
   size: PropTypes.string,
 };
 };
+
+/**
+ * Wrapper component for using unstated
+ */
+const BookmarkButtonWrapper = (props) => {
+  return createSubscribedElement(BookmarkButton, props, [AppContainer]);
+};
+
+export default BookmarkButtonWrapper;

+ 228 - 0
src/server/routes/apiv3/bookmarks.js

@@ -0,0 +1,228 @@
+const loggerFactory = require('@alias/logger');
+
+const logger = loggerFactory('growi:routes:apiv3:bookmark'); // eslint-disable-line no-unused-vars
+
+const express = require('express');
+const { body } = require('express-validator');
+
+const router = express.Router();
+
+/**
+ * @swagger
+ *  tags:
+ *    name: Bookmarks
+ */
+
+/**
+ * @swagger
+ *
+ *  components:
+ *    schemas:
+ *      Bookmark:
+ *        description: Bookmark
+ *        type: object
+ *        properties:
+ *          _id:
+ *            type: string
+ *            description: page ID
+ *            example: 5e07345972560e001761fa63
+ *          __v:
+ *            type: number
+ *            description: DB record version
+ *            example: 0
+ *          createdAt:
+ *            type: string
+ *            description: date created at
+ *            example: 2010-01-01T00:00:00.000Z
+ *          page:
+ *            $ref: '#/components/schemas/Page/properties/_id'
+ *          user:
+ *            $ref: '#/components/schemas/User/properties/_id'
+ */
+
+module.exports = (crowi) => {
+  const accessTokenParser = require('../../middleware/access-token-parser')(crowi);
+  const loginRequired = require('../../middleware/login-required')(crowi);
+  const csrf = require('../../middleware/csrf')(crowi);
+
+  const { Page, Bookmark } = crowi.models;
+  const { ApiV3FormValidator } = crowi.middlewares;
+
+  const validator = {
+    bookmarks: [
+      body('pageId').isString(),
+      body('isBookmarked').isBoolean(),
+    ],
+  };
+  /**
+   * @swagger
+   *
+   *    /bookmarks.get:
+   *      get:
+   *        tags: [Bookmarks, CrowiCompatibles]
+   *        operationId: getBookmark
+   *        summary: /bookmarks.get
+   *        description: Get bookmark of the page with the user
+   *        parameters:
+   *          - in: query
+   *            name: page_id
+   *            required: true
+   *            schema:
+   *              $ref: '#/components/schemas/Page/properties/_id'
+   *        responses:
+   *          200:
+   *            description: Succeeded to get bookmark of the page with the user.
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    ok:
+   *                      $ref: '#/components/schemas/V1Response/properties/ok'
+   *                    bookmark:
+   *                      $ref: '#/components/schemas/Bookmark'
+   *          403:
+   *            $ref: '#/components/responses/403'
+   *          500:
+   *            $ref: '#/components/responses/500'
+   */
+  // actions.api.get = function(req, res) {
+  //   const pageId = req.query.page_id;
+
+  //   Bookmark.findByPageIdAndUserId(pageId, req.user)
+  //     .then((data) => {
+  //       debug('bookmark found', pageId, data);
+  //       const result = {};
+
+  //       result.bookmark = data;
+  //       return res.json(ApiResponse.success(result));
+  //     })
+  //     .catch((err) => {
+  //       return res.json(ApiResponse.error(err));
+  //     });
+  // };
+
+  // actions.api.list = function(req, res) {
+  //   const paginateOptions = ApiPaginate.parseOptions(req.query);
+
+  //   const options = Object.assign(paginateOptions, { populatePage: true });
+  //   Bookmark.findByUserId(req.user._id, options)
+  //     .then((result) => {
+  //       return res.json(ApiResponse.success(result));
+  //     })
+  //     .catch((err) => {
+  //       return res.json(ApiResponse.error(err));
+  //     });
+  // };
+
+  /**
+   * @swagger
+   *
+   *    /bookmarks.add:
+   *      post:
+   *        tags: [Bookmarks, CrowiCompatibles]
+   *        operationId: addBookmark
+   *        summary: /bookmarks.add
+   *        description: Add bookmark of the page
+   *        parameters:
+   *          - in: query
+   *            name: page_id
+   *            schema:
+   *              $ref: '#/components/schemas/Page/properties/_id'
+   *            required: true
+   *        responses:
+   *          200:
+   *            description: Succeeded to add bookmark of the page.
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    ok:
+   *                      $ref: '#/components/schemas/V1Response/properties/ok'
+   *                    bookmark:
+   *                      $ref: '#/components/schemas/Bookmark'
+   *          403:
+   *            $ref: '#/components/responses/403'
+   *          500:
+   *            $ref: '#/components/responses/500'
+   */
+  router.put('/bookmarks', accessTokenParser, loginRequired, csrf, validator.bookmarks, ApiV3FormValidator, async(req, res) => {
+    const { pageId, isBookmarked } = req.body;
+
+    let bookmark;
+    try {
+      const page = await Page.findByIdAndViewer(pageId, req.user);
+      if (page == null) {
+        return res.apiv3Err(`Page '${pageId}' is not found or forbidden`);
+      }
+      if (isBookmarked) {
+        bookmark = await Bookmark.removeBookmark(page, req.user);
+      }
+      else {
+        bookmark = await Bookmark.add(page, req.user);
+      }
+    }
+    catch (err) {
+      logger.error('update-bookmark-failed', err);
+      return res.apiv3Err(err, 500);
+    }
+
+    bookmark.depopulate('page');
+    bookmark.depopulate('user');
+
+    return res.apiv3({ bookmark });
+  });
+
+  /**
+   * @swagger
+   *
+   *    /bookmarks.remove:
+   *      post:
+   *        tags: [Bookmarks, CrowiCompatibles]
+   *        operationId: removeBookmark
+   *        summary: /bookmarks.remove
+   *        description: Remove bookmark of the page
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  page_id:
+   *                    $ref: '#/components/schemas/Page/properties/_id'
+   *                required:
+   *                  - page_id
+   *        responses:
+   *          200:
+   *            description: Succeeded to remove bookmark of the page.
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    ok:
+   *                      $ref: '#/components/schemas/V1Response/properties/ok'
+   *          403:
+   *            $ref: '#/components/responses/403'
+   *          500:
+   *            $ref: '#/components/responses/500'
+   */
+  /**
+   * @api {post} /bookmarks.remove Remove bookmark of the page
+   * @apiName RemoveBookmark
+   * @apiGroup Bookmark
+   *
+   * @apiParam {String} page_id Page Id.
+   */
+  // actions.api.remove = function(req, res) {
+  //   const pageId = req.body.page_id;
+
+  //   Bookmark.removeBookmark(pageId, req.user)
+  //     .then((data) => {
+  //       debug('Bookmark removed.', data); // if the bookmark is not exists, this 'data' is null
+  //       return res.json(ApiResponse.success());
+  //     })
+  //     .catch((err) => {
+  //       return res.json(ApiResponse.error(err));
+  //     });
+  // };
+
+  return router;
+};

+ 2 - 0
src/server/routes/apiv3/index.js

@@ -39,5 +39,7 @@ module.exports = (crowi) => {
 
 
   router.use('/page', require('./page')(crowi));
   router.use('/page', require('./page')(crowi));
 
 
+  router.use('/bookmarks', require('./bookmarks')(crowi));
+
   return router;
   return router;
 };
 };