Explorar el Código

Merge branch 'support/apply-bootstrap4' into imprv/path-isnot-broken

ryohek hace 6 años
padre
commit
a712c503b1

+ 6 - 18
src/client/js/components/LikeButton.jsx

@@ -19,25 +19,13 @@ class LikeButton extends React.Component {
 
   async handleClick() {
     const { appContainer, pageId } = this.props;
-    const { isLiked } = this.state;
-
-    if (!isLiked) {
-      try {
-        await appContainer.apiPost('/likes.add', { page_id: pageId });
-        this.setState({ isLiked: true });
-      }
-      catch (err) {
-        toastError(err);
-      }
+    const bool = !this.state.isLiked;
+    try {
+      await appContainer.apiv3.put('/page/likes', { pageId, bool });
+      this.setState({ isLiked: bool });
     }
-    else {
-      try {
-        await appContainer.apiPost('/likes.remove', { page_id: pageId });
-        this.setState({ isLiked: false });
-      }
-      catch (err) {
-        toastError(err);
-      }
+    catch (err) {
+      toastError(err);
     }
   }
 

+ 8 - 12
src/client/js/components/Me/ImageCropModal.jsx

@@ -94,19 +94,15 @@ class ImageCropModal extends React.Component {
           <ReactCrop circularCrop src={this.props.src} crop={this.state.crop} onImageLoaded={this.onImageLoaded} onChange={this.onCropChange} />
         </ModalBody>
         <ModalFooter>
-          <div className="d-flex justify-content-between">
-            <button type="button" className="btn btn-sm bg-danger" onClick={this.reset}>
+          <button type="button" className="btn btn-outline-danger rounded-pill mr-auto" onClick={this.reset}>
               Reset
-            </button>
-            <div className="d-flex">
-              <button type="button" className="btn btn-sm bg-light" onClick={this.props.onModalClose}>
-                Cancel
-              </button>
-              <button type="button" className="btn btn-sm bg-primary" onClick={this.crop}>
-                Crop
-              </button>
-            </div>
-          </div>
+          </button>
+          <button type="button" className="btn btn-outline-secondary rounded-pill mr-2" onClick={this.props.onModalClose}>
+                  Cancel
+          </button>
+          <button type="button" className="btn btn-outline-primary rounded-pill" onClick={this.crop}>
+                  Crop
+          </button>
         </ModalFooter>
       </Modal>
     );

+ 1 - 1
src/client/js/components/Navbar/GrowiSubNavigation.jsx

@@ -48,7 +48,7 @@ const GrowiSubNavigation = (props) => {
 
       {/* Header Button */}
       <div className="mr-2">
-        <LikeButton pageId={pageId} />
+        <LikeButton pageId={pageId} isLiked={pageContainer.state.isLiked} />
       </div>
       <div>
         <BookmarkButton pageId={pageId} crowi={appContainer} />

+ 1 - 1
src/client/js/components/PageEditor/Editor.jsx

@@ -339,7 +339,7 @@ export default class Editor extends AbstractEditor {
           && (
           <button
             type="button"
-            className="btn btn-default btn-block btn-open-dropzone"
+            className="btn btn-light btn-block btn-open-dropzone"
             onClick={() => { this.dropzone.open() }}
           >
             <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;

+ 1 - 1
src/client/js/components/PageEditorByHackmd.jsx

@@ -294,7 +294,7 @@ class PageEditorByHackmd extends React.Component {
 
           <div className="text-center hackmd-discard-button-container mb-3">
             <button
-              className="btn btn-default btn-lg waves-effect waves-light"
+              className="btn btn-light btn-lg waves-effect waves-light"
               type="button"
               onClick={() => { return this.discardChanges() }}
             >

+ 1 - 1
src/client/js/services/PageContainer.js

@@ -41,7 +41,7 @@ export default class PageContainer extends Container {
       revisionAuthor: JSON.parse(mainContent.getAttribute('data-page-revision-author')),
       path: mainContent.getAttribute('data-path'),
       tocHtml: '',
-      isLiked: mainContent.getAttribute('data-page-is-liked'),
+      isLiked: JSON.parse(mainContent.getAttribute('data-page-is-liked')),
       seenUserIds: [],
       likerUserIds: [],
       createdAt: mainContent.getAttribute('data-page-created-at'),

+ 5 - 0
src/client/styles/scss/_override-bootstrap.scss

@@ -82,6 +82,11 @@ h6 {
   .dropdown-toggle.btn.disabled {
     cursor: not-allowed;
   }
+
+  // hide caret
+  .dropdown-toggle.dropdown-toggle-no-caret::after {
+    content: none;
+  }
 }
 
 // agile-admin style

+ 4 - 3
src/server/models/page.js

@@ -2,6 +2,7 @@
 /* eslint-disable no-return-await */
 
 /* eslint-disable no-use-before-define */
+const logger = require('@alias/logger')('growi:models:page');
 
 const debug = require('debug')('growi:models:page');
 const nodePath = require('path');
@@ -367,12 +368,12 @@ module.exports = function(crowi) {
           if (err) {
             return reject(err);
           }
-          debug('liker updated!', added);
+          logger.debug('liker updated!', added);
           return resolve(data);
         });
       }
       else {
-        this.logger.warn('liker not updated');
+        logger.debug('liker not updated');
         return reject(self);
       }
     }));
@@ -393,7 +394,7 @@ module.exports = function(crowi) {
         });
       }
       else {
-        debug('liker not updated');
+        logger.debug('liker not updated');
         return reject(self);
       }
     }));

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

@@ -18,9 +18,7 @@ module.exports = (crowi) => {
   router.use('/markdown-setting', require('./markdown-setting')(crowi));
   router.use('/app-settings', require('./app-settings')(crowi));
   router.use('/customize-setting', require('./customize-setting')(crowi));
-
   router.use('/notification-setting', require('./notification-setting')(crowi));
-
   router.use('/users', require('./users')(crowi));
   router.use('/user-groups', require('./user-group')(crowi));
   router.use('/export', require('./export')(crowi));
@@ -39,5 +37,7 @@ module.exports = (crowi) => {
 
   router.use('/search', require('./search')(crowi));
 
+  router.use('/page', require('./page')(crowi));
+
   return router;
 };

+ 187 - 0
src/server/routes/apiv3/page.js

@@ -0,0 +1,187 @@
+const loggerFactory = require('@alias/logger');
+
+const logger = loggerFactory('growi:routes:apiv3:page'); // eslint-disable-line no-unused-vars
+
+const express = require('express');
+const { body } = require('express-validator');
+
+const router = express.Router();
+
+// const ErrorV3 = require('../../models/vo/error-apiv3');
+
+/**
+ * @swagger
+ *  tags:
+ *    name: Page
+ */
+
+/**
+ * @swagger
+ *
+ *  components:
+ *    schemas:
+ *      Page:
+ *        description: Page
+ *        type: object
+ *        properties:
+ *          _id:
+ *            type: string
+ *            description: page ID
+ *            example: 5e07345972560e001761fa63
+ *          __v:
+ *            type: number
+ *            description: DB record version
+ *            example: 0
+ *          commentCount:
+ *            type: number
+ *            description: count of comments
+ *            example: 3
+ *          createdAt:
+ *            type: string
+ *            description: date created at
+ *            example: 2010-01-01T00:00:00.000Z
+ *          creator:
+ *            $ref: '#/components/schemas/User'
+ *          extended:
+ *            type: object
+ *            description: extend data
+ *            example: {}
+ *          grant:
+ *            type: number
+ *            description: grant
+ *            example: 1
+ *          grantedUsers:
+ *            type: array
+ *            description: granted users
+ *            items:
+ *              type: string
+ *              description: user ID
+ *            example: ["5ae5fccfc5577b0004dbd8ab"]
+ *          lastUpdateUser:
+ *            $ref: '#/components/schemas/User'
+ *          liker:
+ *            type: array
+ *            description: granted users
+ *            items:
+ *              type: string
+ *              description: user ID
+ *            example: []
+ *          path:
+ *            type: string
+ *            description: page path
+ *            example: /
+ *          redirectTo:
+ *            type: string
+ *            description: redirect path
+ *            example: ""
+ *          revision:
+ *            type: string
+ *            description: page revision
+ *          seenUsers:
+ *            type: array
+ *            description: granted users
+ *            items:
+ *              type: string
+ *              description: user ID
+ *            example: ["5ae5fccfc5577b0004dbd8ab"]
+ *          status:
+ *            type: string
+ *            description: status
+ *            enum:
+ *              - 'wip'
+ *              - 'published'
+ *              - 'deleted'
+ *              - 'deprecated'
+ *            example: published
+ *          updatedAt:
+ *            type: string
+ *            description: date updated at
+ *            example: 2010-01-01T00:00:00.000Z
+ *
+ *      LikeParams:
+ *        description: LikeParams
+ *        type: object
+ *        properties:
+ *          pageId:
+ *            type: string
+ *            description: page ID
+ *            example: 5e07345972560e001761fa63
+ *          bool:
+ *            type: boolean
+ *            description: boolean for like status
+ */
+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 globalNotificationService = crowi.getGlobalNotificationService();
+  const { Page, GlobalNotificationSetting } = crowi.models;
+  const { ApiV3FormValidator } = crowi.middlewares;
+
+
+  const validator = {
+    likes: [
+      body('pageId').isString(),
+      body('bool').isBoolean(),
+    ],
+  };
+
+  /**
+   * @swagger
+   *
+   *    /page/likes:
+   *      put:
+   *        tags: [Page]
+   *        summary: /page/likes
+   *        description: Update liked status
+   *        operationId: updateLikedStatus
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/LikeParams'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update liked status.
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  $ref: '#/components/schemas/Page'
+   */
+  router.put('/likes', accessTokenParser, loginRequired, csrf, validator.likes, ApiV3FormValidator, async(req, res) => {
+    const { pageId, bool } = req.body;
+
+    let page;
+    try {
+      page = await Page.findByIdAndViewer(pageId, req.user);
+      if (page == null) {
+        return res.apiv3Err(`Page '${pageId}' is not found or forbidden`);
+      }
+      if (bool) {
+        page = await page.like(req.user);
+      }
+      else {
+        page = await page.unlike(req.user);
+      }
+    }
+    catch (err) {
+      logger.error('update-like-failed', err);
+      return res.apiv3Err(err, 500);
+    }
+
+    try {
+      // global notification
+      await globalNotificationService.fire(GlobalNotificationSetting.EVENT.PAGE_LIKE, page, req.user);
+    }
+    catch (err) {
+      logger.error('Like notification failed', err);
+    }
+
+    const result = { page };
+    result.seenUser = page.seenUsers;
+    return res.apiv3({ result });
+  });
+
+  return router;
+};

+ 1 - 1
src/server/views/layout-crowi/page_list.html

@@ -46,7 +46,7 @@
     {% include '../widget/page_content.html' %}
   </div>
 
-  <div class="row page-list hidden-print {% if page.isPortal() %}mt-5{% endif %}">
+  <div class="row page-list d-print-none {% if page.isPortal() %}mt-5{% endif %}">
     <div class="col-md-12">
       {% include '../widget/page_list_and_timeline.html' %}
     </div>

+ 1 - 1
src/server/views/layout-growi/page.html

@@ -35,7 +35,7 @@
   </div>
 
   {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
-  <div class="row page-list hidden-print mt-5">
+  <div class="row page-list d-print-none mt-5">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}
     </div>

+ 1 - 1
src/server/views/layout-growi/page_list.html

@@ -34,7 +34,7 @@
 
   </div>
 
-  <div class="row page-list hidden-print {% if page.isPortal() %}mt-5{% endif %}">
+  <div class="row page-list d-print-none {% if page.isPortal() %}mt-5{% endif %}">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}
     </div>

+ 1 - 1
src/server/views/layout-growi/user_page.html

@@ -59,7 +59,7 @@
   </div>
 
   {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
-  <div class="row page-list hidden-print mt-5">
+  <div class="row page-list d-print-none mt-5">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}
     </div>

+ 1 - 1
src/server/views/modal/duplicate.html

@@ -20,7 +20,7 @@
                 <span class="input-group-text">{{ baseUrl }}</span>
               </div>
                 {% if isSearchServiceConfigured() %}
-                <div id="duplicate-page-name-input" class="page-name-input"></div>
+                <div id="duplicate-page-name-input" class="page-name-input flex-fill"></div>
                 {% else %}
                 <input type="text" class="form-control" name="new_path" id="duplicatePageName" value="{{ page.path }}">
                 {% endif %}

+ 49 - 41
src/server/views/widget/page_tabs_kibela.html

@@ -1,21 +1,21 @@
 {% if page %}
-<ul class="nav nav-tabs hidden-print">
+<ul class="nav nav-tabs d-print-none">
 
   {#
     Left Tabs
   #}
-  <li class="nav-item grw-nav-main-left-tab active">
+  <li class="nav-item active">
     <a class="nav-link active" href="#revision-body" data-toggle="tab">
       <i class="icon-control-play"></i> View
     </a>
   </li>
 
   {% if !isTrashPage() %}
-  <li class="grw-nav-main-left-tab nav-tab-edit">
+  <li class="nav-item nav-tab-edit">
     <a
-      {% if user %} href="#edit" data-toggle="tab" class="edit-button" {% endif %}
+      {% if user %} href="#edit" data-toggle="tab" class="nav-link edit-button" {% endif %}
       {% if not user %}
-        class="edit-button edit-button-disabled"
+        class="nav-link edit-button edit-button-disabled"
         data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
       {% endif %}
     >
@@ -23,11 +23,11 @@
     </a>
   </li>
   {% if isHackmdSetup() %}
-  <li class="grw-nav-main-left-tab nav-tab-hackmd">
+  <li class="nav-item nav-tab-hackmd">
     <a
-      {% if user %} href="#hackmd" data-toggle="tab" class="edit-button" {% endif %}
+      {% if user %} href="#hackmd" data-toggle="tab" class="nav-link edit-button" {% endif %}
       {% if not user %}
-        class="edit-button edit-button-disabled"
+        class="nav-link edit-button edit-button-disabled"
         data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
       {% endif %}
     >
@@ -40,77 +40,85 @@
   {#
     Right Tabs
   #}
+  {# to place right side #}
+  <div class="mr-auto"></div>
+
+  {% if not page.isPortal() %}
+  <li class="nav-item">
+    <a href="?presentation=1" class="nav-link toggle-presentation">
+      <i class="icon-film"></i><span class="d-none d-sm-inline"> {{ t('Presentation Mode') }}</span>
+    </a>
+  </li>
+  {% endif %}
+
+  <li class="nav-item">
+    <a href="#revision-history" class="nav-link" data-toggle="tab">
+      <i class="icon-layers"></i><span class="d-none d-sm-inline"> {{ t('History') }}</span>
+    </a>
+  </li>
+  
   {% if !isTrashPage() %}
     {% if page.isPortal() %}
-    <li class="float-right dropdown">
+    <li class="nav-item dropdown">
       <a
-        {% if user %} role="button" class="dropdown-toggle" data-toggle="dropdown" {% endif %}
+        {% if user %} role="button" class="nav-link dropdown-toggle dropdown-toggle-no-caret" data-toggle="dropdown" {% endif %}
         {% if not user %}
-          class="dropdown-toggle dropdown-toggle-disabled"
+          class="nav-link dropdown-toggle dropdown-toggle-disabled dropdown-toggle-no-caret"
           data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
         {% endif %}
       >
         <i class="icon-options-vertical"></i>
       </a>
-      <ul class="dropdown-menu">
-        <li><a href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a></li>
+      <ul class="dropdown-menu dropdown-menu-right">
+        <li class="dropdown-item">
+          <a href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a>
+        </li>
         {% if ('/' !== path) %}
-        <li class="divider"></li>
-        <li><a href="#" data-target="#unportalize" data-toggle="modal"><i class="fa fa-share"></i> {{ t('Unportalize') }}</a></li>
+        <li class="dropdown-divider"></li>
+        <li class="dropdown-item"><a href="#" data-target="#unportalize" data-toggle="modal"><i class="fa fa-share"></i> {{ t('Unportalize') }}</a></li>
         {% endif %}
       </ul>
     </li>
     {% else %}
-    <li class="dropdown float-right">
+    <li class="nav-item dropdown">
       <a
-        {% if user %} role="button" class="dropdown-toggle" data-toggle="dropdown" {% endif %}
+        {% if user %} role="button" class="nav-link dropdown-toggle dropdown-toggle-no-caret" data-toggle="dropdown" {% endif %}
         {% if not user %}
-          class="dropdown-toggle dropdown-toggle-disabled"
+          class="nav-link dropdown-toggle dropdown-toggle-disabled dropdown-toggle-no-caret"
           data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
         {% endif %}
       >
         <i class="icon-options-vertical"></i>
       </a>
-      <ul class="dropdown-menu">
-        <li><a href="#" data-target="#renamePage" data-toggle="modal"><i class="icon-fw icon-action-redo"></i> {{ t('Move/Rename') }}</a></li>
-        <li><a href="#" data-target="#duplicatePage" data-toggle="modal"><i class="icon-fw icon-docs"></i> {{ t('Duplicate') }}</a></li>
-        <li class="divider"></li>
-        <li><a href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a></li>
+      <ul class="dropdown-menu dropdown-menu-right">
+        <li class="dropdown-item"><a href="#" data-target="#renamePage" data-toggle="modal"><i class="icon-fw icon-action-redo"></i> {{ t('Move/Rename') }}</a></li>
+        <li class="dropdown-item"><a href="#" data-target="#duplicatePage" data-toggle="modal"><i class="icon-fw icon-docs"></i> {{ t('Duplicate') }}</a></li>
+        <li class="dropdown-divider"></li>
+        <li class="dropdown-item">
+          <a href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a>
+        </li>
         {% if isDeletablePage() %}
-        <li class="divider"></li>
-        <li><a href="#" data-target="#deletePage" data-toggle="modal"><i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}</a></li>
+        <li class="dropdown-divider"></li>
+        <li class="dropdown-item"><a href="#" data-target="#deletePage" data-toggle="modal"><i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}</a></li>
         {% endif %}
       </ul>
     </li>
     {% endif %}
   {% endif %}
 
-  <li class="float-right">
-    <a href="#revision-history" data-toggle="tab">
-      <i class="icon-layers"></i><span class="hidden-xs"> {{ t('History') }}</span>
-    </a>
-  </li>
-  {% if not page.isPortal() %}
-    <li class="float-right">
-      <a href="?presentation=1" class="toggle-presentation">
-        <i class="icon-film"></i><span class="hidden-xs"> {{ t('Presentation Mode') }}</span>
-      </a>
-    </li>
-  {% endif %}
-
 </ul>
 
 {% else %} {# for creating portal #}
 
-<ul class="nav nav-tabs nav-tabs-create-portal hidden-print">
+<ul class="nav nav-tabs nav-tabs-create-portal d-print-none">
 
-  <li class="nav-item grw-nav-main-left-tab">
+  <li class="nav-item ">
     <a id="portal-form-close" class="nav-link" href="#" data-toggle="tab">
       <i class="icon-action-undo"></i> {{ t('Cancel') }}
     </a>
   </li>
 
-  <li class="nav-item grw-nav-main-left-tab active">
+  <li class="nav-item  active">
     <a class="nav-link">
       <i class="icon-note"></i> {{ t('Create') }}
     </a>