Browse Source

Merge pull request #2299 from weseek/master

release v4.0.1
Yuki Takei 6 years ago
parent
commit
86eb06a6da
39 changed files with 751 additions and 759 deletions
  1. 0 95
      .github/workflows/build.yml
  2. 0 0
      .github/workflows/release-rc.yml
  3. 80 1
      .github/workflows/release.yml
  4. 19 1
      CHANGES.md
  5. 2 0
      config/webpack.dev.dll.js
  6. 3 3
      package.json
  7. 12 11
      src/client/js/components/Admin/Notification/GlobalNotificationList.jsx
  8. 43 0
      src/client/js/components/Admin/Notification/NotificationTypeIcon.jsx
  9. 5 2
      src/client/js/components/Admin/Notification/UserNotificationRow.jsx
  10. 5 1
      src/client/js/components/MyDraftList/MyDraftList.jsx
  11. 9 16
      src/client/js/components/PageComment/Comment.jsx
  12. 174 116
      src/client/js/components/PageComment/CommentEditor.jsx
  13. 10 64
      src/client/js/components/PageComment/CommentEditorLazyRenderer.jsx
  14. 9 9
      src/client/js/components/PageComment/ReplayComments.jsx
  15. 16 4
      src/client/js/components/PageComments.jsx
  16. 40 27
      src/client/js/components/PageCreateModal.jsx
  17. 1 1
      src/client/js/components/PageDeleteModal.jsx
  18. 1 1
      src/client/js/components/PageDuplicateModal.jsx
  19. 1 1
      src/client/js/components/PageEditor/HandsontableModal.jsx
  20. 11 4
      src/client/js/components/PageRenameModal.jsx
  21. 19 5
      src/client/js/components/Sidebar.jsx
  22. 11 24
      src/client/js/components/Sidebar/CustomSidebar.jsx
  23. 14 27
      src/client/js/components/Sidebar/RecentChanges.jsx
  24. 33 55
      src/client/js/components/Sidebar/SidebarNav.jsx
  25. 10 2
      src/client/js/components/User/UserPicture.jsx
  26. 8 4
      src/client/styles/scss/_admin.scss
  27. 2 8
      src/client/styles/scss/_layout.scss
  28. 4 0
      src/client/styles/scss/_layout_kibela.scss
  29. 2 0
      src/client/styles/scss/_on-edit.scss
  30. 1 0
      src/client/styles/scss/_override-bootstrap-variables.scss
  31. 6 0
      src/client/styles/scss/_override-bootstrap.scss
  32. 45 62
      src/client/styles/scss/_sidebar.scss
  33. 8 0
      src/client/styles/scss/theme/_apply-colors-dark.scss
  34. 7 0
      src/client/styles/scss/theme/_apply-colors-light.scss
  35. 39 19
      src/client/styles/scss/theme/_apply-colors.scss
  36. 8 5
      src/client/styles/scss/theme/wood.scss
  37. 7 7
      src/server/routes/page.js
  38. 7 6
      src/server/views/widget/page_alerts.html
  39. 79 178
      yarn.lock

+ 0 - 95
.github/workflows/build.yml

@@ -1,95 +0,0 @@
-name: Release Docker Images
-
-on:
-  push:
-    branches:
-      - tmp/release-**
-
-
-jobs:
-
-  build:
-
-    runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        flavor: [default, nocdn]
-
-    steps:
-    - uses: actions/checkout@v2
-
-    - name: Determine suffix
-      run: |
-        [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
-        echo ::set-env name=SUFFIX::$suffix
-
-    - name: Cache/Restore yarn cache
-      uses: actions/cache@v1
-      with:
-        path: /usr/local/share/.cache/yarn
-        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          ${{ runner.os }}-yarn-${{ matrix.node-version }}-
-
-    - name: Set up Docker Buildx
-      uses: crazy-max/ghaction-docker-buildx@v1.0.4
-
-    - name: Login to docker.io registry
-      run: |
-        echo ${{ secrets. DOCKER_REGISTRY_PASSWORD }} | docker login --username wsmoogle --password-stdin
-
-    - name: Build Docker Image
-      run: |
-        CACHE_REF=weseek/growi-cache:3${{ env.SUFFIX }}
-        docker buildx build \
-          --tag growi${{ env.SUFFIX }} \
-          --build-arg flavor=${{ matrix.flavor }} \
-          --platform linux/amd64 \
-          --load \
-          --cache-from=type=registry,ref=$CACHE_REF \
-          --cache-to=type=registry,ref=$CACHE_REF,mode=max \
-          --file ./docker/Dockerfile .
-
-    - name: Get SemVer
-      run: |
-        semver=`npm run version --silent`
-        echo ::set-env name=SEMVER::$semver
-
-    - name: Docker Tags by SemVer
-      uses: weseek/ghaction-docker-tags-by-semver@v1.0.5
-      with:
-        source: growi${{ env.SUFFIX }}
-        target: weseek/growi
-        semver: ${{ env.SEMVER }}
-        suffix: ${{ env.SUFFIX }}
-        additional-tags: 'latest'
-        publish: true
-
-    - name: Slack Notification
-      uses: weseek/ghaction-release-slack-notification@master
-      with:
-        channel: '#general'
-        url: ${{ secrets.SLACK_WEBHOOK_URL }}
-        created_tag: 'v${{ env.SEMVER }}${{ env.SUFFIX }}'
-
-    - name: Check whether workspace is clean
-      run: |
-        STATUS=`git status --porcelain`
-        if [ -z "$STATUS" ]; then exit 0; else exit 1; fi
-
-  publish-desc:
-
-    runs-on: ubuntu-latest
-    needs: build
-
-    steps:
-    - uses: actions/checkout@v2
-
-    - name: Update Docker Hub Description
-      uses: peter-evans/dockerhub-description@v2.1.0
-      env:
-        DOCKERHUB_USERNAME: wsmoogle
-        DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
-        DOCKERHUB_REPOSITORY: weseek/growi
-        README_FILEPATH: ./docker/README.md

+ 0 - 0
.github/workflows/build-rc.yml → .github/workflows/release-rc.yml


+ 80 - 1
.github/workflows/release.yml

@@ -6,7 +6,7 @@ on:
       - release/**
 
 jobs:
-  release:
+  github-release:
 
     runs-on: ubuntu-latest
 
@@ -42,3 +42,82 @@ jobs:
         changelog_file: CHANGES.md
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+
+  build-image:
+    needs: github-release
+
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        flavor: [default, nocdn]
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Checkout released tag
+      run: git checkout refs/tags/v${{ env.RELEASE_VERSION }}
+
+    - name: Determine suffix
+      run: |
+        [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
+        echo ::set-env name=SUFFIX::$suffix
+
+    - name: Cache/Restore yarn cache
+      uses: actions/cache@v1
+      with:
+        path: /usr/local/share/.cache/yarn
+        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
+        restore-keys: |
+          ${{ runner.os }}-yarn-${{ matrix.node-version }}-
+
+    - name: Set up Docker Buildx
+      uses: crazy-max/ghaction-docker-buildx@v1.0.4
+
+    - name: Login to docker.io registry
+      run: |
+        echo ${{ secrets. DOCKER_REGISTRY_PASSWORD }} | docker login --username wsmoogle --password-stdin
+
+    - name: Build Docker Image
+      run: |
+        CACHE_REF=weseek/growi-cache:3${{ env.SUFFIX }}
+        docker buildx build \
+          --tag growi${{ env.SUFFIX }} \
+          --build-arg flavor=${{ matrix.flavor }} \
+          --platform linux/amd64 \
+          --load \
+          --cache-from=type=registry,ref=$CACHE_REF \
+          --cache-to=type=registry,ref=$CACHE_REF,mode=max \
+          --file ./docker/Dockerfile .
+
+    - name: Docker Tags by SemVer
+      uses: weseek/ghaction-docker-tags-by-semver@v1.0.5
+      with:
+        source: growi${{ env.SUFFIX }}
+        target: weseek/growi
+        semver: ${{ env.RELEASE_VERSION }}
+        suffix: ${{ env.SUFFIX }}
+        additional-tags: 'latest'
+        publish: true
+
+    - name: Slack Notification
+      uses: weseek/ghaction-release-slack-notification@master
+      with:
+        channel: '#general'
+        url: ${{ secrets.SLACK_WEBHOOK_URL }}
+        created_tag: 'v${{ env.RELEASE_VERSION }}${{ env.SUFFIX }}'
+
+    - name: Update Docker Hub Description
+      uses: peter-evans/dockerhub-description@v2.1.0
+      env:
+        DOCKERHUB_USERNAME: wsmoogle
+        DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+        DOCKERHUB_REPOSITORY: weseek/growi
+        README_FILEPATH: ./docker/README.md
+
+    - name: Check whether workspace is clean
+      run: |
+        STATUS=`git status --porcelain`
+        if [ -z "$STATUS" ]; then exit 0; else exit 1; fi
+

+ 19 - 1
CHANGES.md

@@ -1,6 +1,22 @@
 # CHANGES
 
-## v4.0.0-RC
+## v4.0.1-RC
+
+* Improvement: Accessibility for Handsontable under dark mode
+* Improvement: Refactor '/pages.exist' API
+* Fix: Storing the state of sidebar
+* Fix: Comments order should be asc
+* Fix: Show/Hide replies button doesn't work
+* Fix: Tooltip doesn't work
+* Fix: Change the display of the scroll bar when modal is shown
+* Fix: Submit with enter key on Create/Rename modals
+* Fix: Show/Hide Unlink redirection button conditions
+* Fix: Link color in alerts
+* Support: Upgrade libs
+    * @atlaskit/drawer
+    * @atlaskit/navigation-next
+
+## v4.0.0
 
 ### BREAKING CHANGES
 
@@ -9,6 +25,8 @@
 * 'default-dark' theme is now merged as a dark mode variant of 'default' theme
 * 'blue-night' theme is now merged as a dark mode variant of 'mono-blue' theme
 
+Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/40x.html>
+
 ### Updates
 
 * Feature: Sidebar

+ 2 - 0
config/webpack.dev.dll.js

@@ -10,6 +10,8 @@ module.exports = {
   entry: {
     dlls: [
       // Libraries
+      '@atlaskit/drawer',
+      '@atlaskit/navigation-next',
       'axios',
       'browser-bunyan', 'bunyan-format',
       'codemirror', 'react-codemirror2',

+ 3 - 3
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "4.0.0-RC",
+  "version": "4.0.1-RC",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -154,8 +154,8 @@
       "handsontable: v7.0.0 or above is no loger MIT lisence."
     ],
     "@alienfast/i18next-loader": "^1.0.16",
-    "@atlaskit/drawer": "^5.3.5",
-    "@atlaskit/navigation-next": "^8.0.2",
+    "@atlaskit/drawer": "^5.3.7",
+    "@atlaskit/navigation-next": "^8.0.5",
     "@babel/core": "^7.4.5",
     "@babel/plugin-proposal-class-properties": "^7.8.3",
     "@babel/plugin-proposal-optional-chaining": "^7.9.0",

+ 12 - 11
src/client/js/components/Admin/Notification/GlobalNotificationList.jsx

@@ -9,7 +9,9 @@ import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminNotificationContainer from '../../../services/AdminNotificationContainer';
+
 import NotificationDeleteModal from './NotificationDeleteModal';
+import NotificationTypeIcon from './NotificationTypeIcon';
 
 const logger = loggerFactory('growi:GolobalNotificationList');
 
@@ -91,44 +93,43 @@ class GlobalNotificationList extends React.Component {
                 {notification.triggerPath}
               </td>
               <td>
-                <ul className="list-inline">
+                <ul className="list-inline mb-0">
                   {notification.triggerEvents.includes('pageCreate') && (
-                  <li className="list-inline-item badge badge-pill badge-success" data-toggle="tooltip" data-placement="top" title="Page Create">
+                  <li className="list-inline-item badge badge-pill badge-success">
                     <i className="icon-doc"></i> CREATE
                   </li>
                 )}
                   {notification.triggerEvents.includes('pageEdit') && (
-                  <li className="list-inline-item badge badge-pill badge-warning" data-toggle="tooltip" data-placement="top" title="Page Edit">
+                  <li className="list-inline-item badge badge-pill badge-warning">
                     <i className="icon-pencil"></i> EDIT
                   </li>
                 )}
                   {notification.triggerEvents.includes('pageMove') && (
-                  <li className="list-inline-item badge badge-pill badge-pink" data-toggle="tooltip" data-placement="top" title="Page Move">
+                  <li className="list-inline-item badge badge-pill badge-pink">
                     <i className="icon-action-redo"></i> MOVE
                   </li>
                 )}
                   {notification.triggerEvents.includes('pageDelete') && (
-                  <li className="list-inline-item badge badge-pill badge-danger" data-toggle="tooltip" data-placement="top" title="Page Delte">
+                  <li className="list-inline-item badge badge-pill badge-danger">
                     <i className="icon-fire"></i> DELETE
                   </li>
                 )}
                   {notification.triggerEvents.includes('pageLike') && (
-                  <li className="list-inline-item badge badge-pill badge-info" data-toggle="tooltip" data-placement="top" title="Page Like">
+                  <li className="list-inline-item badge badge-pill badge-info">
                     <i className="icon-like"></i> LIKE
                   </li>
                 )}
                   {notification.triggerEvents.includes('comment') && (
-                  <li className="list-inline-item badge badge-pill badge-secondary" data-toggle="tooltip" data-placement="top" title="New Comment">
+                  <li className="list-inline-item badge badge-pill badge-secondary">
                     <i className="icon-fw icon-bubble"></i> POST
                   </li>
                 )}
                 </ul>
               </td>
               <td>
-                {notification.__t === 'mail'
-                  && <span data-toggle="tooltip" data-placement="top" title="Email"><i className="ti-email"></i> {notification.toEmail}</span>}
-                {notification.__t === 'slack'
-                  && <span data-toggle="tooltip" data-placement="top" title="Slack"><i className="fa fa-hashtag"></i> {notification.slackChannels}</span>}
+                <NotificationTypeIcon notification={notification} />
+                { notification.__t === 'mail' && notification.toEmail }
+                { notification.__t === 'slack' && notification.slackChannels }
               </td>
               <td className="td-abs-center">
                 <div className="dropdown">

+ 43 - 0
src/client/js/components/Admin/Notification/NotificationTypeIcon.jsx

@@ -0,0 +1,43 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { UncontrolledTooltip } from 'reactstrap';
+
+const SlackIcon = (props) => {
+  const { __t, _id, provider } = props.notification;
+
+  let type = 'slack';
+
+  // User trigger notification
+  if (provider != null) {
+    // only slack type
+  }
+
+  // Global notification
+  if (__t != null) {
+    if (__t === 'mail') {
+      type = 'mail';
+    }
+  }
+
+  const elemId = `notification-${type}-${_id}`;
+  const className = type === 'mail'
+    ? 'icon-fw fa fa-envelope-o'
+    : 'icon-fw fa fa-hashtag';
+
+  return (
+    <>
+      <i id={elemId} className={className}></i>
+      <UncontrolledTooltip target={elemId}>Slack</UncontrolledTooltip>
+    </>
+  );
+};
+
+SlackIcon.propTypes = {
+  // supports 2 types:
+  //   User trigger notification -> has 'provider: slack'
+  //   Global notification -> has '__t: slack|mail'
+  notification: PropTypes.object.isRequired,
+};
+
+export default SlackIcon;

+ 5 - 2
src/client/js/components/Admin/Notification/UserNotificationRow.jsx

@@ -7,19 +7,22 @@ import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AdminNotificationContainer from '../../../services/AdminNotificationContainer';
 
+import NotificationTypeIcon from './NotificationTypeIcon';
 
 class UserNotificationRow extends React.PureComponent {
 
   render() {
     const { t, notification } = this.props;
+    const id = `user-notification-${notification._id}`;
+
     return (
       <React.Fragment>
-        <tr className="admin-notif-row" key={notification._id}>
+        <tr className="admin-notif-row" key={id}>
           <td className="px-4">
             {notification.pathPattern}
           </td>
           <td className="px-4">
-            <span data-toggle="tooltip" data-placement="top" title="Slack"><i className="fa fa-hashtag"></i> {notification.channel}</span>
+            <NotificationTypeIcon notification={notification} />{notification.channel}
           </td>
           <td>
             <button type="submit" className="btn btn-outline-danger" onClick={() => { this.props.onClickDeleteBtn(notification._id) }}>{t('Delete')}</button>

+ 5 - 1
src/client/js/components/MyDraftList/MyDraftList.jsx

@@ -45,8 +45,12 @@ class MyDraftList extends React.Component {
   async getDraftsFromLocalStorage() {
     const draftsAsObj = this.props.editorContainer.drafts;
 
+    if (draftsAsObj == null) {
+      return;
+    }
+
     const res = await this.props.appContainer.apiGet('/pages.exist', {
-      pages: draftsAsObj,
+      pagePaths: JSON.stringify(Object.keys(draftsAsObj)),
     });
 
     // {'/a': '#a', '/b': '#b'} => [{path: '/a', markdown: '#a'}, {path: '/b', markdown: '#b'}]

+ 9 - 16
src/client/js/components/PageComment/Comment.jsx

@@ -35,17 +35,13 @@ class Comment extends React.PureComponent {
       isReEdit: false,
     };
 
-    this.growiRenderer = this.props.appContainer.getRenderer('comment');
-
     this.isCurrentUserIsAuthor = this.isCurrentUserEqualsToAuthor.bind(this);
     this.isCurrentRevision = this.isCurrentRevision.bind(this);
     this.getRootClassName = this.getRootClassName.bind(this);
     this.getRevisionLabelClassName = this.getRevisionLabelClassName.bind(this);
-    this.editBtnClickedHandler = this.editBtnClickedHandler.bind(this);
     this.deleteBtnClickedHandler = this.deleteBtnClickedHandler.bind(this);
     this.renderText = this.renderText.bind(this);
     this.renderHtml = this.renderHtml.bind(this);
-    this.commentButtonClickedHandler = this.commentButtonClickedHandler.bind(this);
   }
 
 
@@ -114,14 +110,6 @@ class Comment extends React.PureComponent {
       this.isCurrentRevision() ? 'badge-primary' : 'badge-secondary'}`;
   }
 
-  editBtnClickedHandler() {
-    this.setState({ isReEdit: !this.state.isReEdit });
-  }
-
-  commentButtonClickedHandler() {
-    this.editBtnClickedHandler();
-  }
-
   deleteBtnClickedHandler() {
     this.props.deleteBtnClicked(this.props.comment);
   }
@@ -187,12 +175,13 @@ class Comment extends React.PureComponent {
 
         {this.state.isReEdit ? (
           <CommentEditor
-            growiRenderer={this.growiRenderer}
+            growiRenderer={this.props.growiRenderer}
             currentCommentId={commentId}
             commentBody={comment.comment}
             replyTo={undefined}
-            commentButtonClickedHandler={this.commentButtonClickedHandler}
             commentCreator={creator.username}
+            onCancelButtonClicked={() => this.setState({ isReEdit: false })}
+            onCommentButtonClicked={() => this.setState({ isReEdit: false })}
           />
         ) : (
           <div id={commentId} className={rootClassName}>
@@ -216,8 +205,12 @@ class Comment extends React.PureComponent {
                 ) }
                 <span className="ml-2"><a className={revisionLavelClassName} href={revHref}>{revFirst8Letters}</a></span>
               </div>
-              {this.checkPermissionToControlComment()
-                  && <CommentControl onClickDeleteBtn={this.deleteBtnClickedHandler} onClickEditBtn={this.editBtnClickedHandler} />}
+              { this.checkPermissionToControlComment() && (
+                <CommentControl
+                  onClickDeleteBtn={this.deleteBtnClickedHandler}
+                  onClickEditBtn={() => this.setState({ isReEdit: true })}
+                />
+              ) }
             </div>
           </div>
           )

+ 174 - 116
src/client/js/components/PageComment/CommentEditor.jsx

@@ -38,6 +38,7 @@ class CommentEditor extends React.Component {
     const isUploadableFile = config.upload.file;
 
     this.state = {
+      isReadyToUse: !this.props.isForNewComment,
       comment: this.props.commentBody || '',
       isMarkdown: true,
       html: '',
@@ -51,14 +52,16 @@ class CommentEditor extends React.Component {
     this.updateState = this.updateState.bind(this);
     this.updateStateCheckbox = this.updateStateCheckbox.bind(this);
 
-    this.postHandler = this.postHandler.bind(this);
+    this.cancelButtonClickedHandler = this.cancelButtonClickedHandler.bind(this);
+    this.commentButtonClickedHandler = this.commentButtonClickedHandler.bind(this);
+    this.ctrlEnterHandler = this.ctrlEnterHandler.bind(this);
+    this.postComment = this.postComment.bind(this);
     this.uploadHandler = this.uploadHandler.bind(this);
 
     this.renderHtml = this.renderHtml.bind(this);
     this.handleSelect = this.handleSelect.bind(this);
     this.onSlackEnabledFlagChange = this.onSlackEnabledFlagChange.bind(this);
     this.onSlackChannelsChange = this.onSlackChannelsChange.bind(this);
-    this.toggleEditor = this.toggleEditor.bind(this);
   }
 
   updateState(value) {
@@ -85,11 +88,6 @@ class CommentEditor extends React.Component {
     this.props.commentContainer.setState({ slackChannels });
   }
 
-  toggleEditor() {
-    const targetId = this.props.replyTo || this.props.currentCommentId;
-    this.props.commentButtonClickedHandler(targetId);
-  }
-
   initializeEditor() {
     this.setState({
       comment: '',
@@ -100,36 +98,65 @@ class CommentEditor extends React.Component {
     });
     // reset value
     this.editor.setValue('');
-    this.toggleEditor();
   }
 
-  /**
-   * Post comment with CommentContainer and update state
-   */
-  async postHandler(event) {
+  cancelButtonClickedHandler() {
+    const { isForNewComment, onCancelButtonClicked } = this.props;
+
+    // change state to not ready
+    // when this editor is for the new comment mode
+    if (isForNewComment) {
+      this.setState({ isReadyToUse: false });
+    }
+
+    if (onCancelButtonClicked != null) {
+      const { replyTo, currentCommentId } = this.props;
+      onCancelButtonClicked(replyTo || currentCommentId);
+    }
+  }
+
+  commentButtonClickedHandler() {
+    this.postComment();
+  }
+
+  ctrlEnterHandler(event) {
     if (event != null) {
       event.preventDefault();
     }
 
+    this.postComment();
+  }
+
+  /**
+   * Post comment with CommentContainer and update state
+   */
+  async postComment() {
+    const {
+      commentContainer, replyTo, currentCommentId, commentCreator, onCommentButtonClicked,
+    } = this.props;
     try {
-      if (this.props.currentCommentId != null) {
-        await this.props.commentContainer.putComment(
+      if (currentCommentId != null) {
+        await commentContainer.putComment(
           this.state.comment,
           this.state.isMarkdown,
-          this.props.currentCommentId,
-          this.props.commentCreator,
+          currentCommentId,
+          commentCreator,
         );
       }
       else {
         await this.props.commentContainer.postComment(
           this.state.comment,
           this.state.isMarkdown,
-          this.props.replyTo,
-          this.props.commentContainer.state.isSlackEnabled,
-          this.props.commentContainer.state.slackChannels,
+          replyTo,
+          commentContainer.state.isSlackEnabled,
+          commentContainer.state.slackChannels,
         );
       }
       this.initializeEditor();
+
+      if (onCommentButtonClicked != null) {
+        onCommentButtonClicked(replyTo || currentCommentId);
+      }
     }
     catch (err) {
       const errorMessage = err.message || 'An unknown error occured when posting comment';
@@ -212,7 +239,21 @@ class CommentEditor extends React.Component {
     return { __html: html };
   }
 
-  render() {
+  renderBeforeReady() {
+    return (
+      <div className="text-center">
+        <button
+          type="button"
+          className="btn btn-lg btn-link"
+          onClick={() => this.setState({ isReadyToUse: true })}
+        >
+          <i className="icon-bubble"></i> Add Comment
+        </button>
+      </div>
+    );
+  }
+
+  renderReady() {
     const { appContainer, commentContainer } = this.props;
     const { activeTab } = this.state;
 
@@ -221,7 +262,7 @@ class CommentEditor extends React.Component {
 
     const errorMessage = <span className="text-danger text-right mr-2">{this.state.errorMessage}</span>;
     const cancelButton = (
-      <Button outline color="danger" size="xs" className="btn btn-outline-danger rounded-pill" onClick={this.toggleEditor}>
+      <Button outline color="danger" size="xs" className="btn btn-outline-danger rounded-pill" onClick={this.cancelButtonClickedHandler}>
         Cancel
       </Button>
     );
@@ -230,120 +271,128 @@ class CommentEditor extends React.Component {
         outline
         color="primary"
         className="btn btn-outline-primary rounded-pill"
-        onClick={this.postHandler}
+        onClick={this.commentButtonClickedHandler}
       >
         Comment
       </Button>
     );
 
     return (
-      <div className="form page-comment-form">
-        <div className="comment-form">
-          <div className="comment-form-user">
-            <UserPicture user={appContainer.currentUser} />
-          </div>
-          <div className="comment-form-main">
-            <div className="comment-write">
-              <Nav tabs>
-                <NavItem>
-                  <NavLink type="button" className={activeTab === 1 ? 'active' : ''} onClick={() => this.handleSelect(1)}>
+      <>
+        <div className="comment-write">
+          <Nav tabs>
+            <NavItem>
+              <NavLink type="button" className={activeTab === 1 ? 'active' : ''} onClick={() => this.handleSelect(1)}>
                     Write
-                  </NavLink>
-                </NavItem>
-                { this.state.isMarkdown && (
-                  <NavItem>
-                    <NavLink type="button" className={activeTab === 2 ? 'active' : ''} onClick={() => this.handleSelect(2)}>
+              </NavLink>
+            </NavItem>
+            { this.state.isMarkdown && (
+            <NavItem>
+              <NavLink type="button" className={activeTab === 2 ? 'active' : ''} onClick={() => this.handleSelect(2)}>
                       Preview
-                    </NavLink>
-                  </NavItem>
+              </NavLink>
+            </NavItem>
                 ) }
-              </Nav>
-              <TabContent activeTab={activeTab}>
-                <TabPane tabId={1}>
-                  <Editor
-                    ref={(c) => { this.editor = c }}
-                    value={this.state.comment}
-                    isGfmMode={this.state.isMarkdown}
-                    lineNumbers={false}
-                    isMobile={appContainer.isMobile}
-                    isUploadable={this.state.isUploadable}
-                    isUploadableFile={this.state.isUploadableFile}
-                    emojiStrategy={emojiStrategy}
-                    onChange={this.updateState}
-                    onUpload={this.uploadHandler}
-                    onCtrlEnter={this.postHandler}
-                  />
-                </TabPane>
-                <TabPane tabId={2}>
-                  <div className="comment-form-preview">
-                    {commentPreview}
-                  </div>
-                </TabPane>
-              </TabContent>
-            </div>
-            <div className="comment-submit">
-              <div className="d-flex">
-                <label className="mr-2">
-                  {activeTab === 1 && (
-                    <span className="custom-control custom-checkbox">
-                      <input
-                        type="checkbox"
-                        className="custom-control-input"
-                        id="comment-form-is-markdown"
-                        name="isMarkdown"
-                        checked={this.state.isMarkdown}
-                        value="1"
-                        onChange={this.updateStateCheckbox}
-                      />
-                      <label
-                        className="ml-2 custom-control-label"
-                        htmlFor="comment-form-is-markdown"
-                      >
-                        Markdown
-                      </label>
-                    </span>
-                  ) }
-                </label>
-                <span className="flex-grow-1" />
-                <span className="d-none d-sm-inline">{ this.state.errorMessage && errorMessage }</span>
-                { this.state.hasSlackConfig
-                  && (
-                  <div className="form-inline align-self-center mr-md-2">
-                    <SlackNotification
-                      isSlackEnabled={commentContainer.state.isSlackEnabled}
-                      slackChannels={commentContainer.state.slackChannels}
-                      onEnabledFlagChange={this.onSlackEnabledFlagChange}
-                      onChannelChange={this.onSlackChannelsChange}
-                    />
-                  </div>
-                  )
-                }
-                <div className="d-none d-sm-block">
-                  <span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
-                </div>
+          </Nav>
+          <TabContent activeTab={activeTab}>
+            <TabPane tabId={1}>
+              <Editor
+                ref={(c) => { this.editor = c }}
+                value={this.state.comment}
+                isGfmMode={this.state.isMarkdown}
+                lineNumbers={false}
+                isMobile={appContainer.isMobile}
+                isUploadable={this.state.isUploadable}
+                isUploadableFile={this.state.isUploadableFile}
+                emojiStrategy={emojiStrategy}
+                onChange={this.updateState}
+                onUpload={this.uploadHandler}
+                onCtrlEnter={this.ctrlEnterHandler}
+              />
+            </TabPane>
+            <TabPane tabId={2}>
+              <div className="comment-form-preview">
+                {commentPreview}
               </div>
-              <div className="d-block d-sm-none mt-2">
-                <div className="d-flex justify-content-end">
-                  { this.state.errorMessage && errorMessage }
-                  <span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
-                </div>
+            </TabPane>
+          </TabContent>
+        </div>
+
+        <div className="comment-submit">
+          <div className="d-flex">
+            <label className="mr-2">
+              {activeTab === 1 && (
+              <span className="custom-control custom-checkbox">
+                <input
+                  type="checkbox"
+                  className="custom-control-input"
+                  id="comment-form-is-markdown"
+                  name="isMarkdown"
+                  checked={this.state.isMarkdown}
+                  value="1"
+                  onChange={this.updateStateCheckbox}
+                />
+                <label
+                  className="ml-2 custom-control-label"
+                  htmlFor="comment-form-is-markdown"
+                >
+                  Markdown
+                </label>
+              </span>
+                  ) }
+            </label>
+            <span className="flex-grow-1" />
+            <span className="d-none d-sm-inline">{ this.state.errorMessage && errorMessage }</span>
+            { this.state.hasSlackConfig
+              && (
+              <div className="form-inline align-self-center mr-md-2">
+                <SlackNotification
+                  isSlackEnabled={commentContainer.state.isSlackEnabled}
+                  slackChannels={commentContainer.state.slackChannels}
+                  onEnabledFlagChange={this.onSlackEnabledFlagChange}
+                  onChannelChange={this.onSlackChannelsChange}
+                />
               </div>
+              )
+            }
+            <div className="d-none d-sm-block">
+              <span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
+            </div>
+          </div>
+          <div className="d-block d-sm-none mt-2">
+            <div className="d-flex justify-content-end">
+              { this.state.errorMessage && errorMessage }
+              <span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
             </div>
           </div>
         </div>
+      </>
+    );
+  }
+
+  render() {
+    const { appContainer } = this.props;
+    const { isReadyToUse } = this.state;
+
+    return (
+      <div className="form page-comment-form">
+        <div className="comment-form">
+          <div className="comment-form-user">
+            <UserPicture user={appContainer.currentUser} noLink noTooltip />
+          </div>
+          <div className="comment-form-main">
+            { !isReadyToUse
+              ? this.renderBeforeReady()
+              : this.renderReady()
+            }
+          </div>
+        </div>
       </div>
     );
   }
 
 }
 
-/**
- * Wrapper component for using unstated
- */
-const CommentEditorWrapper = (props) => {
-  return createSubscribedElement(CommentEditor, props, [AppContainer, PageContainer, EditorContainer, CommentContainer]);
-};
-
 CommentEditor.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
@@ -351,11 +400,20 @@ CommentEditor.propTypes = {
   commentContainer: PropTypes.instanceOf(CommentContainer).isRequired,
 
   growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
+  isForNewComment: PropTypes.bool,
   replyTo: PropTypes.string,
   currentCommentId: PropTypes.string,
   commentBody: PropTypes.string,
   commentCreator: PropTypes.string,
-  commentButtonClickedHandler: PropTypes.func.isRequired,
+  onCancelButtonClicked: PropTypes.func,
+  onCommentButtonClicked: PropTypes.func,
+};
+
+/**
+ * Wrapper component for using unstated
+ */
+const CommentEditorWrapper = (props) => {
+  return createSubscribedElement(CommentEditor, props, [AppContainer, PageContainer, EditorContainer, CommentContainer]);
 };
 
 export default CommentEditorWrapper;

+ 10 - 64
src/client/js/components/PageComment/CommentEditorLazyRenderer.jsx

@@ -3,75 +3,21 @@ import PropTypes from 'prop-types';
 
 import { createSubscribedElement } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
-import UserPicture from '../User/UserPicture';
 
 import CommentEditor from './CommentEditor';
 
-class CommentEditorLazyRenderer extends React.Component {
+const CommentEditorLazyRenderer = (props) => {
 
-  constructor(props) {
-    super(props);
+  const growiRenderer = props.appContainer.getRenderer('comment');
 
-    this.state = {
-      isEditorShown: false,
-    };
-
-    this.growiRenderer = this.props.appContainer.getRenderer('comment');
-
-    this.showCommentFormBtnClickHandler = this.showCommentFormBtnClickHandler.bind(this);
-  }
-
-  showCommentFormBtnClickHandler() {
-    this.setState({ isEditorShown: !this.state.isEditorShown });
-  }
-
-  render() {
-    const { appContainer } = this.props;
-    const user = appContainer.currentUser;
-    const isLoggedIn = user != null;
-
-    if (!isLoggedIn) {
-      return <React.Fragment></React.Fragment>;
-    }
-
-    return (
-      <React.Fragment>
-
-        { !this.state.isEditorShown && (
-          <div className="form page-comment-form">
-            <div className="comment-form">
-              <div className="comment-form-user">
-                <UserPicture user={user} />
-              </div>
-              <div className="comment-form-main">
-                { !this.state.isEditorShown && (
-                  <button
-                    type="button"
-                    className="btn btn-lg btn-link center-block"
-                    onClick={this.showCommentFormBtnClickHandler}
-                  >
-                    <i className="icon-bubble"></i> Add Comment
-                  </button>
-                ) }
-              </div>
-            </div>
-          </div>
-        ) }
-
-        { this.state.isEditorShown && (
-          <CommentEditor
-            growiRenderer={this.growiRenderer}
-            replyTo={undefined}
-            commentButtonClickedHandler={this.showCommentFormBtnClickHandler}
-          >
-          </CommentEditor>
-        ) }
-
-      </React.Fragment>
-    );
-  }
-
-}
+  return (
+    <CommentEditor
+      growiRenderer={growiRenderer}
+      replyTo={undefined}
+      isForNewComment
+    />
+  );
+};
 
 /**
  * Wrapper component for using unstated

+ 9 - 9
src/client/js/components/PageComment/ReplayComments.jsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import { Button, Collapse } from 'reactstrap';
+import { Collapse } from 'reactstrap';
 
 import AppContainer from '../../services/AppContainer';
 import PageContainer from '../../services/PageContainer';
@@ -19,10 +19,10 @@ class ReplayComments extends React.PureComponent {
       isOlderRepliesShown: false,
     };
 
-    this.toggleIsOlderRepliesShown = this.toggleIsOlderRepliesShown.bind(this);
+    this.toggleOlderReplies = this.toggleOlderReplies.bind(this);
   }
 
-  toggleIsOlderRepliesShown() {
+  toggleOlderReplies() {
     this.setState({ isOlderRepliesShown: !this.state.isOlderRepliesShown });
   }
 
@@ -75,17 +75,17 @@ class ReplayComments extends React.PureComponent {
       <React.Fragment>
         {areThereHiddenReplies && (
           <div className="page-comments-hidden-replies">
-            <Collapse in={this.state.isOlderRepliesShown}>
+            <Collapse isOpen={this.state.isOlderRepliesShown}>
               <div>{hiddenElements}</div>
             </Collapse>
             <div className="text-center">
-              <Button
-                bsStyle="link"
-                className="page-comments-list-toggle-older"
-                onClick={this.toggleIsOlderRepliesShown}
+              <button
+                type="button"
+                className="btn btn-link"
+                onClick={this.toggleOlderReplies}
               >
                 {toggleButtonIcon} {toggleButtonLabel}
-              </Button>
+              </button>
             </div>
           </div>
         )}

+ 16 - 4
src/client/js/components/PageComments.jsx

@@ -50,7 +50,9 @@ class PageComments extends React.Component {
     this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this);
     this.closeDeleteConfirmModal = this.closeDeleteConfirmModal.bind(this);
     this.replyButtonClickedHandler = this.replyButtonClickedHandler.bind(this);
-    this.commentButtonClickedHandler = this.commentButtonClickedHandler.bind(this);
+    this.editorCancelHandler = this.editorCancelHandler.bind(this);
+    this.editorCommentHandler = this.editorCommentHandler.bind(this);
+    this.resetEditor = this.resetEditor.bind(this);
   }
 
   componentWillMount() {
@@ -99,7 +101,15 @@ class PageComments extends React.Component {
     this.setState({ showEditorIds: ids });
   }
 
-  commentButtonClickedHandler(commentId) {
+  editorCancelHandler(commentId) {
+    this.resetEditor(commentId);
+  }
+
+  editorCommentHandler(commentId) {
+    this.resetEditor(commentId);
+  }
+
+  resetEditor(commentId) {
     this.setState((prevState) => {
       prevState.showEditorIds.delete(commentId);
       return {
@@ -169,7 +179,8 @@ class PageComments extends React.Component {
             <CommentEditor
               growiRenderer={this.growiRenderer}
               replyTo={commentId}
-              commentButtonClickedHandler={this.commentButtonClickedHandler}
+              onCancelButtonClicked={this.editorCancelHandler}
+              onCommentButtonClicked={this.editorCommentHandler}
             />
           </div>
         )}
@@ -180,7 +191,8 @@ class PageComments extends React.Component {
   render() {
     const topLevelComments = [];
     const allReplies = [];
-    const comments = this.props.commentContainer.state.comments;
+    const comments = this.props.commentContainer.state.comments
+      .slice().reverse(); // create shallow copy and reverse
 
     comments.forEach((comment) => {
       if (comment.replyTo === undefined) {

+ 40 - 27
src/client/js/components/PageCreateModal.jsx

@@ -20,7 +20,7 @@ const PageCreateModal = (props) => {
 
   const config = appContainer.getConfig();
   const isReachable = config.isSearchServiceReachable;
-  const { pathname } = window.location;
+  const pathname = decodeURI(window.location.pathname);
   const userPageRootPath = userPageRoot(appContainer.currentUser);
   const parentPath = pathUtils.addTrailingSlash(pathname);
   const now = format(new Date(), 'yyyy/MM/dd');
@@ -30,6 +30,12 @@ const PageCreateModal = (props) => {
   const [pageNameInput, setPageNameInput] = useState(parentPath);
   const [template, setTemplate] = useState(null);
 
+  function transitBySubmitEvent(e, transitHandler) {
+    // prevent page transition by submit
+    e.preventDefault();
+    transitHandler();
+  }
+
   /**
    * change todayInput1
    * @param {string} value
@@ -91,9 +97,9 @@ const PageCreateModal = (props) => {
   /**
    * access template page
    */
-  function createTemplatePage() {
+  function createTemplatePage(e) {
     const pageName = (template === 'children') ? '_template' : '__template';
-    window.location.href = encodeURI(urljoin(parentPath, pageName, '#edit'));
+    window.location.href = encodeURI(urljoin(pathname, pageName, '#edit'));
   }
 
   function renderCreateTodayForm() {
@@ -107,22 +113,26 @@ const PageCreateModal = (props) => {
             <div className="d-flex align-items-center flex-fill flex-wrap flex-lg-nowrap">
               <div className="d-flex align-items-center">
                 <span>{userPageRootPath}/</span>
+                <form onSubmit={e => transitBySubmitEvent(e, createTodayPage)}>
+                  <input
+                    type="text"
+                    className="page-today-input1 form-control text-center mx-2"
+                    value={todayInput1}
+                    onChange={e => onChangeTodayInput1Handler(e.target.value)}
+                  />
+                </form>
+                <span className="page-today-suffix">/{now}/</span>
+              </div>
+              <form className="mt-1 mt-lg-0 ml-lg-2 w-100" onSubmit={e => transitBySubmitEvent(e, createTodayPage)}>
                 <input
                   type="text"
-                  className="page-today-input1 form-control text-center mx-2"
-                  value={todayInput1}
-                  onChange={e => onChangeTodayInput1Handler(e.target.value)}
+                  className="page-today-input2 form-control w-100"
+                  id="page-today-input2"
+                  placeholder={t('Input page name (optional)')}
+                  value={todayInput2}
+                  onChange={e => onChangeTodayInput2Handler(e.target.value)}
                 />
-                <span className="page-today-suffix">/{now}/</span>
-              </div>
-              <input
-                type="text"
-                className="page-today-input2 form-control mt-1 mt-lg-0 mx-lg-2 flex-fill"
-                id="page-today-input2"
-                placeholder={t('Input page name (optional)')}
-                value={todayInput2}
-                onChange={e => onChangeTodayInput2Handler(e.target.value)}
-              />
+              </form>
             </div>
 
             <div className="d-flex justify-content-end mt-1 mt-sm-0">
@@ -151,21 +161,23 @@ const PageCreateModal = (props) => {
                 ? (
                   <PagePathAutoComplete
                     crowi={appContainer}
-                    initializedPath={decodeURI(pathname)}
+                    initializedPath={pathname}
                     addTrailingSlash
                     onSubmit={ppacSubmitHandler}
                     onInputChange={ppacInputChangeHandler}
                   />
                 )
                 : (
-                  <input
-                    type="text"
-                    value={pageNameInput}
-                    className="form-control flex-fill"
-                    placeholder={t('Input page name')}
-                    onChange={e => onChangePageNameInputHandler(e.target.value)}
-                    required
-                  />
+                  <form onSubmit={e => transitBySubmitEvent(e, createInputPage)}>
+                    <input
+                      type="text"
+                      value={pageNameInput}
+                      className="form-control flex-fill"
+                      placeholder={t('Input page name')}
+                      onChange={e => onChangePageNameInputHandler(e.target.value)}
+                      required
+                    />
+                  </form>
                 )}
             </div>
 
@@ -187,8 +199,9 @@ const PageCreateModal = (props) => {
       <div className="row">
         <fieldset className="col-12">
 
-          <h3 className="grw-modal-head pb-2">{ t('template.modal_label.Create template under')}<br />
-            <code>{decodeURI(pathname)}</code>
+          <h3 className="grw-modal-head pb-2">
+            { t('template.modal_label.Create template under')}<br />
+            <code className="h6">{pathname}</code>
           </h3>
 
           <div className="d-sm-flex align-items-center justify-items-between">

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

@@ -110,7 +110,7 @@ const PageDeleteModal = (props) => {
   }
 
   return (
-    <Modal isOpen={isOpen} toggle={onClose} className="grw-create-page">
+    <Modal size="lg" isOpen={isOpen} toggle={onClose} className="grw-create-page">
       <ModalHeader tag="h4" toggle={onClose} className={`bg-${deleteIconAndKey[deleteMode].color} text-light`}>
         <i className={`icon-fw icon-${deleteIconAndKey[deleteMode].icon}`}></i>
         { t(`modal_delete.delete_${deleteIconAndKey[deleteMode].translationKey}`) }

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

@@ -61,7 +61,7 @@ const PageDuplicateModal = (props) => {
   }
 
   return (
-    <Modal isOpen={props.isOpen} toggle={props.onClose} className="grw-duplicate-page">
+    <Modal size="lg" isOpen={props.isOpen} toggle={props.onClose} className="grw-duplicate-page">
       <ModalHeader tag="h4" toggle={props.onClose} className="bg-primary text-light">
         { t('modal_duplicate.label.Duplicate page') }
       </ModalHeader>

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

@@ -437,7 +437,7 @@ export default class HandsontableModal extends React.PureComponent {
           Edit Table
         </ModalHeader>
         <ModalBody className="p-0 d-flex flex-column">
-          <div className="px-4 py-3 border-bottom bg-light">
+          <div className="grw-hot-modal-navbar px-4 py-3 border-bottom">
             <button
               type="button"
               className="mr-4 data-import-button btn btn-secondary"

+ 11 - 4
src/client/js/components/PageRenameModal.jsx

@@ -61,8 +61,15 @@ const PageRenameModal = (props) => {
         isRenameRedirect,
         isRenameMetadata,
       );
+
       const { page } = response;
-      window.location.href = encodeURI(`${page.path}?renamed=${path}`);
+      const url = new URL(page.path, 'https://dummy');
+      url.searchParams.append('renamedFrom', path);
+      if (isRenameRedirect) {
+        url.searchParams.append('withRedirect', true);
+      }
+
+      window.location.href = `${url.pathname}${url.search}`;
     }
     catch (err) {
       setErrorCode(err.code);
@@ -71,7 +78,7 @@ const PageRenameModal = (props) => {
   }
 
   return (
-    <Modal isOpen={props.isOpen} toggle={props.onClose} className="grw-create-page">
+    <Modal size="lg" isOpen={props.isOpen} toggle={props.onClose} className="grw-create-page">
       <ModalHeader tag="h4" toggle={props.onClose} className="bg-primary text-light">
         { t('modal_rename.label.Move/Rename page') }
       </ModalHeader>
@@ -86,7 +93,7 @@ const PageRenameModal = (props) => {
             <div className="input-group-prepend">
               <span className="input-group-text">{crowi.url}</span>
             </div>
-            <div className="flex-fill">
+            <form className="flex-fill" onSubmit={(e) => { e.preventDefault(); rename() }}>
               <input
                 type="text"
                 value={pageNameInput}
@@ -94,7 +101,7 @@ const PageRenameModal = (props) => {
                 onChange={e => inputChangeHandler(e.target.value)}
                 required
               />
-            </div>
+            </form>
           </div>
         </div>
         <div className="custom-control custom-checkbox custom-checkbox-warning">

+ 19 - 5
src/client/js/components/Sidebar.jsx

@@ -1,8 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import { withTranslation } from 'react-i18next';
-
 import {
   withNavigationUIController,
   LayoutManager,
@@ -32,9 +30,26 @@ class Sidebar extends React.Component {
   };
 
   componentWillMount() {
+    this.hackUIController();
     this.initBreakpointEvents();
   }
 
+  /**
+   * hack and override UIController.storeState
+   *
+   * Since UIController is an unstated container, setState() in storeState method should be awaited before writing to cache.
+   */
+  hackUIController() {
+    const { navigationUIController } = this.props;
+
+    // see: @atlaskit/navigation-next/dist/esm/ui-controller/UIController.js
+    const orgStoreState = navigationUIController.storeState;
+    navigationUIController.storeState = async(state) => {
+      await navigationUIController.setState(state);
+      orgStoreState(state);
+    };
+  }
+
   initBreakpointEvents() {
     const { appContainer, navigationUIController } = this.props;
 
@@ -99,7 +114,7 @@ class Sidebar extends React.Component {
         break;
     }
 
-    return contents;
+    return <div className="grw-sidebar-content-container">{contents}</div>;
   }
 
   render() {
@@ -139,13 +154,12 @@ class Sidebar extends React.Component {
 }
 
 const SidebarWithNavigationUI = withNavigationUIController(Sidebar);
-const SidebarWithNavigationUIAndTranslation = withTranslation()(SidebarWithNavigationUI);
 
 /**
  * Wrapper component for using unstated
  */
 const SidebarWrapper = (props) => {
-  return createSubscribedElement(SidebarWithNavigationUIAndTranslation, props, [AppContainer]);
+  return createSubscribedElement(SidebarWithNavigationUI, props, [AppContainer]);
 };
 
 export default () => (

+ 11 - 24
src/client/js/components/Sidebar/CustomSidebar.jsx

@@ -3,11 +3,6 @@ import React from 'react';
 
 import { withTranslation } from 'react-i18next';
 
-import {
-  HeaderSection,
-  MenuSection,
-} from '@atlaskit/navigation-next';
-
 import { createSubscribedElement } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
 
@@ -25,25 +20,17 @@ class CustomSidebar extends React.Component {
 
   render() {
     return (
-      <div className="grw-sidebar-custom">
-        <HeaderSection>
-          { () => (
-            <div className="grw-sidebar-header-container p-3 d-flex">
-              <h3>Custom Sidebar</h3>
-              <button type="button" className="btn btn-xs btn-outline-secondary ml-auto" onClick={this.reloadData}>
-                <i className="icon icon-reload"></i>
-              </button>
-            </div>
-          ) }
-        </HeaderSection>
-        <MenuSection>
-          { () => (
-            <div className="grw-sidebar-content-container p-3">
-              (TBD) Under implementation
-            </div>
-          ) }
-        </MenuSection>
-      </div>
+      <>
+        <div className="grw-sidebar-content-header p-3 d-flex">
+          <h3 className="mb-0">Custom Sidebar</h3>
+          <button type="button" className="btn btn-sm btn-outline-secondary ml-auto" onClick={this.reloadData}>
+            <i className="icon icon-reload"></i>
+          </button>
+        </div>
+        <div className="grw-sidebar-content-header p-3">
+          (TBD) Under implementation
+        </div>
+      </>
     );
 
   }

+ 14 - 27
src/client/js/components/Sidebar/RecentChanges.jsx

@@ -3,11 +3,6 @@ import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
 
-import {
-  HeaderSection,
-  MenuSection,
-} from '@atlaskit/navigation-next';
-
 import loggerFactory from '@alias/logger';
 
 import DevidedPagePath from '@commons/models/devided-page-path';
@@ -85,28 +80,20 @@ class RecentChanges extends React.Component {
     const { recentlyUpdatedPages } = this.props.appContainer.state;
 
     return (
-      <div className="grw-sidebar-history">
-        <HeaderSection>
-          { () => (
-            <div className="grw-sidebar-header-container p-3 d-flex">
-              <h3>{t('Recent Changes')}</h3>
-              {/* <h3>{t('Recent Created')}</h3> */} {/* TODO: impl switching */}
-              <button type="button" className="btn btn-sm btn-outline-secondary ml-auto" onClick={this.reloadData}>
-                <i className="icon icon-reload"></i>
-              </button>
-            </div>
-          ) }
-        </HeaderSection>
-        <MenuSection>
-          { () => (
-            <div className="grw-sidebar-content-container p-3">
-              <ul className="list-group list-group-flush">
-                { recentlyUpdatedPages.map(page => <PageItem key={page.id} page={page} />) }
-              </ul>
-            </div>
-          ) }
-        </MenuSection>
-      </div>
+      <>
+        <div className="grw-sidebar-content-header p-3 d-flex">
+          <h3 className="mb-0">{t('Recent Changes')}</h3>
+          {/* <h3 className="mb-0">{t('Recent Created')}</h3> */} {/* TODO: impl switching */}
+          <button type="button" className="btn btn-sm btn-outline-secondary ml-auto" onClick={this.reloadData}>
+            <i className="icon icon-reload"></i>
+          </button>
+        </div>
+        <div className="grw-sidebar-content-body p-3">
+          <ul className="list-group list-group-flush">
+            { recentlyUpdatedPages.map(page => <PageItem key={page.id} page={page} />) }
+          </ul>
+        </div>
+      </>
     );
   }
 

+ 33 - 55
src/client/js/components/Sidebar/SidebarNav.jsx

@@ -3,10 +3,6 @@ import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
 
-import {
-  GlobalNav,
-} from '@atlaskit/navigation-next';
-
 import { createSubscribedElement } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
 
@@ -28,36 +24,28 @@ class SidebarNav extends React.Component {
     }
   }
 
-  generatePrimaryItemObj(id, label, iconName) {
+  PrimaryItem = ({ id, label, iconName }) => {
     const isSelected = this.props.currentContentsId === id;
 
-    return {
-      id,
-      component: ({ className }) => (
-        <div className={`${className} grw-global-item-container ${isSelected ? 'active' : ''}`}>
-          <button
-            type="button"
-            className={`btn btn-primary btn-lg ${isSelected ? 'active' : ''}`}
-            onClick={() => this.itemSelectedHandler(id)}
-          >
-            <i className="material-icons">{iconName}</i>
-          </button>
-        </div>
-      ),
-    };
+    return (
+      <button
+        type="button"
+        className={`d-block btn btn-primary ${isSelected ? 'active' : ''}`}
+        onClick={() => this.itemSelectedHandler(id)}
+      >
+        <i className="material-icons">{iconName}</i>
+      </button>
+    );
   }
 
-  generateSecondaryItemObj(id, label, iconName, href, isBlank) {
-    return {
-      id,
-      component: ({ className }) => (
-        <div className={`${className} grw-global-item-container`}>
-          <a href={href} className="btn btn-primary" target={`${isBlank ? '_blank' : ''}`}>
-            <i className="material-icons">{iconName}</i>
-          </a>
-        </div>
-      ),
-    };
+  SecondaryItem({
+    label, iconName, href, isBlank,
+  }) {
+    return (
+      <a href={href} className="d-block btn btn-primary" target={`${isBlank ? '_blank' : ''}`}>
+        <i className="material-icons">{iconName}</i>
+      </a>
+    );
   }
 
   generateIconFactory(classNames) {
@@ -68,33 +56,23 @@ class SidebarNav extends React.Component {
     const { isAdmin, currentUsername } = this.props.appContainer;
     const isLoggedIn = currentUsername != null;
 
-    const primaryItems = [
-      this.generatePrimaryItemObj('custom', 'Custom Sidebar', 'code'),
-      this.generatePrimaryItemObj('recent', 'Recent Changes', 'update'),
-      // this.generatePrimaryItemObj('tag', 'Tags', 'icon-tag'),
-      // this.generatePrimaryItemObj('favorite', 'Favorite', 'icon-star'),
-    ];
-
-    let secondaryItems = [
-      isAdmin && (
-        this.generateSecondaryItemObj('admin', 'Admin', 'settings', '/admin')
-      ),
-      isLoggedIn && (
-        this.generateSecondaryItemObj('draft', 'Draft', 'file_copy', `/user/${currentUsername}#user-draft-list`)
-      ),
-      this.generateSecondaryItemObj('help', 'Help', 'help', 'https://docs.growi.org', true),
-      isLoggedIn && (
-        this.generateSecondaryItemObj('trash', 'Trash', 'delete', '/trash')
-      ),
-    ];
-    // remove 'false' items
-    secondaryItems = secondaryItems.filter(item => item !== false);
+    const { PrimaryItem, SecondaryItem } = this;
 
     return (
-      <GlobalNav
-        primaryItems={primaryItems}
-        secondaryItems={secondaryItems}
-      />
+      <div className="grw-sidebar-nav d-flex flex-column justify-content-between pb-4">
+        <div className="grw-sidebar-nav-primary-container">
+          <PrimaryItem id="custom" label="Custom Sidebar" iconName="code" />
+          <PrimaryItem id="recent" label="Recent Changes" iconName="update" />
+          {/* <PrimaryItem id="tag" label="Tags" iconName="icon-tag" /> */}
+          {/* <PrimaryItem id="favorite" label="Favorite" iconName="icon-star" /> */}
+        </div>
+        <div className="grw-sidebar-nav-secondary-container">
+          {isAdmin && <SecondaryItem label="Admin" iconName="settings" href="/admin" />}
+          {isLoggedIn && <SecondaryItem label="Draft" iconName="file_copy" href={`/user/${currentUsername}#user-draft-list`} />}
+          <SecondaryItem label="Help" iconName="help" href="https://docs.growi.org" isBlank />
+          {isLoggedIn && <SecondaryItem label="Trash" iconName="delete" href="/trash" />}
+        </div>
+      </div>
     );
   }
 

+ 10 - 2
src/client/js/components/User/UserPicture.jsx

@@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
 
 import { userPageRoot } from '@commons/util/path-utils';
 
+import { UncontrolledTooltip } from 'reactstrap';
+
 const DEFAULT_IMAGE = '/images/icons/user.svg';
 
 // TODO UserComponent?
@@ -64,10 +66,16 @@ export default class UserPicture extends React.Component {
 
   withTooltip = (RootElm) => {
     const { user } = this.props;
-    const title = `@${user.username}<br />${user.name}`;
+    const id = `user-picture-${Math.random().toString(32).substring(2)}`;
 
     return props => (
-      <RootElm data-toggle="tooltip" data-placement="bottom" data-html="true" title={title}>{props.children}</RootElm>
+      <>
+        <RootElm id={id}>{props.children}</RootElm>
+        <UncontrolledTooltip placement="bottom" target={id}>
+          @{user.username}<br />
+          {user.name}
+        </UncontrolledTooltip>
+      </>
     );
   }
 

+ 8 - 4
src/client/styles/scss/_admin.scss

@@ -47,10 +47,14 @@
   }
 
   .admin-notification {
-    .td-abs-center {
-      width: 1px; // to keep the cell small
-      text-align: center;
-      vertical-align: middle;
+    table .admin-notif-list {
+      td {
+        vertical-align: middle;
+      }
+      .td-abs-center {
+        width: 1px; // to keep the cell small
+        text-align: center;
+      }
     }
   }
 

+ 2 - 8
src/client/styles/scss/_layout.scss

@@ -1,11 +1,5 @@
-// FIXME: replace with mt-2 or mt-3
-.grw-mt-10px {
-  margin-top: 10px !important;
-}
-
-// FIXME: replace with pt-2 or pt-3
-.grw-pt-10px {
-  padding-top: 10px !important;
+body {
+  overflow-y: scroll !important;
 }
 
 .grw-logo {

+ 4 - 0
src/client/styles/scss/_layout_kibela.scss

@@ -1,6 +1,10 @@
 $navbar-height-adjustment: 10px;
 
 body.kibela {
+  .grw-pt-10px {
+    padding-top: 10px !important;
+  }
+
   /* navbar for kibela */
   #page-wrapper {
     margin-top: $grw-navbar-height + $grw-navbar-border-width;

+ 2 - 0
src/client/styles/scss/_on-edit.scss

@@ -9,6 +9,8 @@ body:not(.on-edit) {
 }
 
 body.on-edit {
+  overflow-y: hidden !important;
+
   // calculate margin
   $editor-header-plus-footer: 42px // .nav-tabs height
     + 1px //                          .page-editor-footer border-top

+ 1 - 0
src/client/styles/scss/_override-bootstrap-variables.scss

@@ -62,6 +62,7 @@ $modal-header-padding-x: 1rem;
 //== Alerts
 $alert-bg-level: -2;
 $alert-border-level: 0;
+$alert-color-level: -10;
 
 //== Progress bar
 $progress-height: 4px;

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

@@ -89,6 +89,12 @@
   }
 
   //Modals
+  .modal-open {
+    position: fixed;
+    width: 100%;
+    padding-right: 0 !important;
+  }
+
   .modal-content {
     box-shadow: 0 0.3rem 1rem rgba(0, 0, 0, 0.1);
   }

+ 45 - 62
src/client/styles/scss/_sidebar.scss

@@ -12,6 +12,7 @@
       width: 0;
       content: '';
       border: 9px solid transparent;
+      border-right-color: white;
       border-left-width: 0;
       transform: translateY(-#{$sidebar-nav-button-height / 2});
     }
@@ -49,40 +50,33 @@
 
   // override @atlaskit/navigation-next styles
   $navbar-total-height: $grw-navbar-height + $grw-navbar-border-width;
-  div[class$='-LayoutContainer'] {
+  div[data-layout-container='true'] {
     height: calc(100vh - #{$navbar-total-height});
   }
-  div[class$='-NavigationContainer'] {
+  div[data-testid='Navigation'] {
     top: $navbar-total-height;
 
     // Adjust to be on top of the growi subnavigation
     z-index: $zindex-sticky + 5;
-  }
-  div[data-testid='GlobalNavigation'] {
-    > div {
-      height: calc(100vh - #{$navbar-total-height});
-      padding-top: 0;
+
+    // css-xxx-Outer
+    > div:nth-of-type(2) {
+      width: 0;
+
+      // css-xxx-Shadow
+      > div:first-child {
+        background: linear-gradient(to left, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, rgba(0, 0, 0, 0.1) 1px, rgba(0, 0, 0, 0) 100%);
+      }
     }
   }
-  div[class$='-Outer'] {
-    width: 0;
-  }
-  div[class$='-PrimaryItemsList'],
-  div[class$='-SecondaryItemsList'] {
-    > div {
-      padding: 0;
-    }
 
-    .grw-global-item-container {
-      width: unset;
-      height: unset;
-      background-color: transparent;
-      border-radius: 0;
+  .grw-sidebar-nav {
+    height: calc(100vh - #{$navbar-total-height});
 
-      .btn {
-        width: $grw-sidebar-nav-width;
-        border-radius: 0;
-      }
+    .btn {
+      width: $grw-sidebar-nav-width;
+      line-height: 1em;
+      border-radius: 0;
 
       // icon opacity
       &:not(.active) {
@@ -97,39 +91,29 @@
         }
       }
     }
-  }
-  div[class$='-PrimaryItemsList'] {
-    .grw-global-item-container {
-      .btn-lg {
-        padding: 0.8rem 1rem;
-        line-height: 1em;
+
+    .grw-sidebar-nav-primary-container {
+      .btn {
+        padding: 1em;
         i {
-          font-size: 1.7em;
+          font-size: 2.3em;
         }
-      }
 
-      &.active {
-        button {
+        &.active {
           @extend %fukidashi-for-active;
         }
       }
     }
-  }
-  div[class$='-ScrollableTransitionGroup'] {
-    // remove horizontal line
-    > div,
-    > div > div {
-      &:before,
-      &:after {
-        display: none;
+
+    .grw-sidebar-nav-secondary-container {
+      .btn {
+        padding: 0.9em;
+        i {
+          font-size: 1.5em;
+        }
       }
     }
   }
-  div[class$='-Outer'] {
-    div[class$='-Shadow'] {
-      background: linear-gradient(to left, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, rgba(0, 0, 0, 0.1) 1px, rgba(0, 0, 0, 0) 100%);
-    }
-  }
 }
 
 // Drawer Mode
@@ -139,23 +123,31 @@
     z-index: $zindex-fixed - 2;
 
     // override @atlaskit/navigation-next styles
-    div[class$='-Outer'],
-    div[class$='-teprsg'] {
-      display: none;
+    div[data-layout-container='true'] {
+      // css-teprsg
+      > div:nth-of-type(2) {
+        display: none;
+      }
+    }
+    div[data-testid='Navigation'] {
+      // css-xxx-Outer
+      > div:nth-of-type(2) {
+        display: none;
+      }
     }
 
     &:not(.open) {
-      div[class$='-NavigationContainer'] {
+      div[data-testid='Navigation'] {
         left: -#{$grw-sidebar-nav-width + $grw-sidebar-content-min-width};
       }
     }
     &.open {
-      div[class$='-NavigationContainer'] {
+      div[data-testid='Navigation'] {
         left: 0;
       }
     }
 
-    div[class$='-NavigationContainer'] {
+    div[data-testid='Navigation'] {
       transition: left 300ms cubic-bezier(0.25, 1, 0.5, 1);
     }
   }
@@ -164,12 +156,3 @@
     z-index: $zindex-fixed - 4;
   }
 }
-
-.grw-sidebar-header-container {
-  h3 {
-    margin-bottom: 0;
-  }
-}
-
-.grw-sidebar-content-container {
-}

+ 8 - 0
src/client/styles/scss/theme/_apply-colors-dark.scss

@@ -19,6 +19,7 @@ $table-dark-hover-bg: $bgcolor-table-hover;
   * Form
   */
 input.form-control,
+select.form-control,
 textarea.form-control {
   color: lighten($color-global, 30%);
   background-color: darken($bgcolor-global, 5%);
@@ -120,6 +121,13 @@ ul.pagination {
   }
 }
 
+/*
+ * GROWI HandsontableModal
+ */
+.grw-hot-modal-navbar {
+  background-color: $dark;
+}
+
 .wiki {
   h1 {
     border-color: lighten($border-color-theme, 10%);

+ 7 - 0
src/client/styles/scss/theme/_apply-colors-light.scss

@@ -69,6 +69,13 @@ $table-hover-bg: $bgcolor-table-hover;
   }
 }
 
+/*
+ * GROWI HandsontableModal
+ */
+.grw-hot-modal-navbar {
+  background-color: $light;
+}
+
 .wiki {
   h1 {
     border-color: darken($border-color-theme, 10%);

+ 39 - 19
src/client/styles/scss/theme/_apply-colors.scss

@@ -61,6 +61,16 @@ pre:not(.hljs):not(.CodeMirror-line) {
 //== Apply to Bootstrap Elements
 //
 
+// Alert link
+@each $color, $value in $theme-colors {
+  .alert-#{$color} {
+    .alert-link,
+    .alert-link:hover {
+      color: theme-color-level($color, $alert-color-level - 2);
+    }
+  }
+}
+
 // Link buttons
 .btn-link {
   color: $link-color;
@@ -105,6 +115,13 @@ pre:not(.hljs):not(.CodeMirror-line) {
   }
 }
 
+//
+//== Apply to Handsontable
+//
+.handsontable {
+  color: initial;
+}
+
 //
 //== Apply to GROWI Elements
 //
@@ -149,6 +166,7 @@ pre:not(.hljs):not(.CodeMirror-line) {
 }
 
 .grw-sidebar {
+  // override @atlaskit/navigation-next styles
   .ak-navigation-resize-button {
     $color-resize-button: $color-global !default;
     $bgcolor-resize-button: white !default;
@@ -161,8 +179,19 @@ pre:not(.hljs):not(.CodeMirror-line) {
       @include override-hexagon-color($color-resize-button-hover, $bgcolor-resize-button-hover);
     }
   }
+  div[data-testid='GlobalNavigation'] {
+    > div {
+      background-color: $bgcolor-sidebar;
+    }
+  }
+  div[data-testid='ContextualNavigation'] {
+    > div {
+      color: $color-sidebar-context;
+      background-color: $bgcolor-sidebar-context;
+    }
+  }
 
-  .grw-global-item-container {
+  .grw-sidebar-nav {
     .btn {
       @include button-variant(
         $bgcolor-sidebar,
@@ -174,24 +203,15 @@ pre:not(.hljs):not(.CodeMirror-line) {
       );
     }
   }
-  .grw-global-item-container.active {
-    .btn:after {
-      // fukidashi color
-      border-right-color: $bgcolor-sidebar-context;
-    }
-    i {
-      text-shadow: $text-shadow-sidebar-nav-item-active;
-    }
-  }
-  div[data-testid='GlobalNavigation'] {
-    > div {
-      background-color: $bgcolor-sidebar;
-    }
-  }
-  div[data-testid='ContextualNavigation'] {
-    > div {
-      color: $color-sidebar-context;
-      background-color: $bgcolor-sidebar-context;
+  .grw-sidebar-nav-primary-container {
+    .btn.active {
+      i {
+        text-shadow: $text-shadow-sidebar-nav-item-active;
+      }
+      // fukidashi
+      &:after {
+        border-right-color: $bgcolor-sidebar-context;
+      }
     }
   }
 

+ 8 - 5
src/client/styles/scss/theme/wood.scss

@@ -42,7 +42,6 @@ html[dark] {
 
   // Background colors
   $bgcolor-global: #ffffff;
-  $bgcolor-inline-code: #f0f0f0; //optional
   $bgcolor-card: #ece8de;
 
   // Font colors
@@ -50,13 +49,17 @@ html[dark] {
   $color-global: #433005;
   $color-reversal: #fffffc;
   $color-link: #9d7406;
-  $color-link-hover: lighten($color-link, 20%);
-  $color-link-wiki: lighten($themecolor, 5%);
-  $color-link-wiki-hover: lighten($color-link-wiki, 15%);
+  $color-link-hover: lighten($color-link, 10%);
+  $color-link-wiki: $color-link;
+  $color-link-wiki-hover: lighten($color-link-wiki, 10%);
   $color-link-nabvar: #a7a7a7;
-  $color-inline-code: #c7254e; // optional
   $color-search: white;
 
+  // Inline code
+  $bgcolor-inline-code: $themelight; //optional
+  // $color-inline-code: # !default;
+  $bordercolor-inline-code: $themecolor; //optional
+
   // List Group colors
   $color-list-hover: #eee;
 

+ 7 - 7
src/server/routes/page.js

@@ -987,11 +987,11 @@ module.exports = function(crowi, app) {
    *        description: Get page existence
    *        parameters:
    *          - in: query
-   *            name: pages
+   *            name: pagePaths
    *            schema:
    *              type: string
-   *              description: Page paths specified by hash key in JSON format
-   *              example: '{"/": "unused value", "/user/unknown": "unused value"}'
+   *              description: Page path list in JSON Array format
+   *              example: '["/", "/user/unknown"]'
    *        responses:
    *          200:
    *            description: Succeeded to get page existence.
@@ -1018,17 +1018,17 @@ module.exports = function(crowi, app) {
    * @apiParam {String} pages (stringified JSON)
    */
   api.exist = async function(req, res) {
-    const pagesAsObj = JSON.parse(req.query.pages || '{}');
-    const pagePaths = Object.keys(pagesAsObj);
+    const pagePaths = JSON.parse(req.query.pagePaths || '[]');
 
+    const pages = {};
     await Promise.all(pagePaths.map(async(path) => {
       // check page existence
       const isExist = await Page.count({ path }) > 0;
-      pagesAsObj[path] = isExist;
+      pages[path] = isExist;
       return;
     }));
 
-    const result = { pages: pagesAsObj };
+    const result = { pages };
 
     return res.json(ApiResponse.success(result));
   };

+ 7 - 6
src/server/views/widget/page_alerts.html

@@ -28,21 +28,22 @@
       {% endif %}
     {% endif %}
 
-    {% if redirectFrom or req.query.renamed or req.query.redirectFrom %}
+    {% if redirectFrom or req.query.renamedFrom or req.query.redirectFrom %}
     <div class="alert alert-pink d-edit-none py-3 px-4 d-flex align-items-center justify-content-between">
       <span>
-        {% set fromPath = req.query.renamed or req.query.redirectFrom %}
+        {% set fromPath = req.query.renamedFrom or req.query.redirectFrom %}
         {% if redirectFrom or req.query.redirectFrom %}
           <strong>{{ t('Redirected') }}:</strong> {{ t('page_page.notice.redirected', fromPath | preventXss) }}
         {% endif %}
-        {% if req.query.renamed %}
+        {% if req.query.renamedFrom %}
           <strong>{{ t('Moved') }}:</strong> {{ t('page_page.notice.moved', fromPath | preventXss) }}
         {% endif %}
       </span>
-      {% if user and not page.isDeleted() %}
-        <button type="button" id="unlink-page-button" class="btn btn-secondary btn-sm float-right">
+      {% set hasRedirectLink = redirectFrom or req.query.redirectFrom or req.query.withRedirect %}
+      {% if user and not page.isDeleted() and hasRedirectLink %}
+        <button type="button" id="unlink-page-button" class="btn btn-outline-dark btn-sm float-right">
           <i class="ti-unlink" aria-hidden="true"></i>
-          Unlink
+          Unlink redirection
         </button>
       {% endif %}
     </div>

+ 79 - 178
yarn.lock

@@ -29,34 +29,34 @@
     tslib "^1.9.3"
     use-memo-one "^1.1.1"
 
-"@atlaskit/avatar@^17.1.9":
-  version "17.1.9"
-  resolved "https://registry.yarnpkg.com/@atlaskit/avatar/-/avatar-17.1.9.tgz#cf452ce32040977a355246b2eb39f58572feac03"
-  integrity sha512-DZM9OVNn1XHPqx+1JaOr99dBPPMkXwlB1eyC6NkJnFuW3jdN7JoqWu9xT/eiyjcm9GV1hGUUmQ15nGfy7uhkSQ==
+"@atlaskit/avatar@^17.1.11":
+  version "17.1.11"
+  resolved "https://registry.yarnpkg.com/@atlaskit/avatar/-/avatar-17.1.11.tgz#3b34a216250dc65026a994b657bd8ea9cbb867ec"
+  integrity sha512-ETb66o66A5F8eph0U0H3mNuUd9m3OVKOdI388KAqKzhJSXa2VpdfaLA2V2mRT5tWyBpaUFF1scGE/LKDvb0/cg==
   dependencies:
     "@atlaskit/analytics-next" "^6.3.5"
     "@atlaskit/theme" "^9.5.1"
-    "@atlaskit/tooltip" "^15.2.5"
+    "@atlaskit/tooltip" "^15.2.7"
     tslib "^1.9.3"
 
-"@atlaskit/blanket@^10.0.17":
-  version "10.0.17"
-  resolved "https://registry.yarnpkg.com/@atlaskit/blanket/-/blanket-10.0.17.tgz#86cb68496b91ffa493e4dbb1f9c591986f52dcb0"
-  integrity sha512-bwpt15VxUnrCjb6ytFXENSw88RnPvdeOgbMhdp0qmQCCAhjXzo2YqIr5/PGRrPnZB/rK9s7Ce+RzN1pSlMqaFg==
+"@atlaskit/blanket@^10.0.18":
+  version "10.0.18"
+  resolved "https://registry.yarnpkg.com/@atlaskit/blanket/-/blanket-10.0.18.tgz#e7a008c8a5cc93a564083aab8cce3b4c2cec85e5"
+  integrity sha512-vwflq+p7cT0gLFABJNdV6y8Ln448qyh4VWflhP3opzyAUtnVa/LCJ5EkC1ZD0CA9uhBOeKsAQL0dwis5oIvChw==
   dependencies:
     "@atlaskit/analytics-next" "^6.3.5"
     "@atlaskit/theme" "^9.5.1"
     tslib "^1.9.3"
 
-"@atlaskit/drawer@^5.3.5":
-  version "5.3.5"
-  resolved "https://registry.yarnpkg.com/@atlaskit/drawer/-/drawer-5.3.5.tgz#eaf8d88337b62223b0c575efc3ed8e20520c7132"
-  integrity sha512-HQv7oPjXMU3XtcySOxwEiANaLZehZI2Mz8dtUu6MqshWK5c8J3yAw7BfzItTwA4sCSotLJpcDGztt1MQp9RGqw==
+"@atlaskit/drawer@^5.3.7":
+  version "5.3.7"
+  resolved "https://registry.yarnpkg.com/@atlaskit/drawer/-/drawer-5.3.7.tgz#cebf416145fd33e26d661a3dee5ecda010871c3b"
+  integrity sha512-QMdFr8yI3VvvWfeawrig+pu6S+ZQ3N0hJnoRcUzIU+9/T6w/v1a1fPR3toAMCWRmN+h3rn1sDzAdMEpt+ALqug==
   dependencies:
     "@atlaskit/analytics-next" "^6.3.5"
-    "@atlaskit/avatar" "^17.1.9"
-    "@atlaskit/blanket" "^10.0.17"
-    "@atlaskit/icon" "^20.1.0"
+    "@atlaskit/avatar" "^17.1.11"
+    "@atlaskit/blanket" "^10.0.18"
+    "@atlaskit/icon" "^20.1.1"
     "@atlaskit/item" "^11.0.2"
     "@atlaskit/portal" "^3.1.6"
     "@atlaskit/theme" "^9.5.1"
@@ -70,10 +70,10 @@
     tiny-invariant "^0.0.3"
     tslib "^1.9.3"
 
-"@atlaskit/icon@^20.1.0":
-  version "20.1.0"
-  resolved "https://registry.yarnpkg.com/@atlaskit/icon/-/icon-20.1.0.tgz#d89652672d298dd16276da237fcaf134e213f859"
-  integrity sha512-kIVSyrAo/0dZaU2BenQ7TACtafRQ7cjvQlUPD9xxuJBQuL8rx7NCB0NnK8JYfVGOxpujJ6SaeBGB1osMovukTw==
+"@atlaskit/icon@^20.1.1":
+  version "20.1.2"
+  resolved "https://registry.yarnpkg.com/@atlaskit/icon/-/icon-20.1.2.tgz#1054196d5442cb818faefe17a47c3e528bc15ae7"
+  integrity sha512-cDpE6kfiCxv4VNY4LKtRUPAdXTcx4t2eEU1K5Htm/5i6/rmJMHMITIvpZaRqF2R7XdBH5kE2MLxSfexBHC0DjQ==
   dependencies:
     "@atlaskit/theme" "^9.5.1"
     tslib "^1.9.3"
@@ -90,19 +90,19 @@
     react-addons-text-content "^0.0.4"
     uuid "^3.1.0"
 
-"@atlaskit/navigation-next@^8.0.2":
-  version "8.0.2"
-  resolved "https://registry.yarnpkg.com/@atlaskit/navigation-next/-/navigation-next-8.0.2.tgz#045839d292d21ab01655325f6942e587c00a311d"
-  integrity sha512-+YRctHYFmi9E/U/O2ehYrXTiE4IySFC3pyjHL8H5La0Lh3KWjDa8BGD+xBIhtBYIQgRW/E2hV4lgoeItq3a/UQ==
+"@atlaskit/navigation-next@^8.0.5":
+  version "8.0.5"
+  resolved "https://registry.yarnpkg.com/@atlaskit/navigation-next/-/navigation-next-8.0.5.tgz#0258dc7d7d41c7d7179e0d3c3705d64b6236641c"
+  integrity sha512-Eu8ybgNig6Yzwf4ElRfDJTqvNGr8fUVxtoIMMGJKrhiOZYQLrLTvccLcxNwrscOTQo5GfAVAkG5GGZpCZsuZ8g==
   dependencies:
     "@atlaskit/analytics-namespaced-context" "^4.1.11"
     "@atlaskit/analytics-next" "^6.3.5"
-    "@atlaskit/avatar" "^17.1.9"
-    "@atlaskit/icon" "^20.1.0"
-    "@atlaskit/select" "^11.0.9"
-    "@atlaskit/spinner" "^12.1.6"
+    "@atlaskit/avatar" "^17.1.11"
+    "@atlaskit/icon" "^20.1.1"
+    "@atlaskit/select" "^11.0.11"
+    "@atlaskit/spinner" "^12.1.7"
     "@atlaskit/theme" "^9.5.1"
-    "@atlaskit/tooltip" "^15.2.5"
+    "@atlaskit/tooltip" "^15.2.7"
     "@babel/runtime" "^7.0.0"
     "@emotion/core" "^10.0.9"
     chromatism "^2.6.0"
@@ -117,10 +117,10 @@
     shallow-equal "^1.0.0"
     unstated "^1.2.0"
 
-"@atlaskit/popper@^3.1.11":
-  version "3.1.11"
-  resolved "https://registry.yarnpkg.com/@atlaskit/popper/-/popper-3.1.11.tgz#e6dabbf6440ffaf0d8a42d9ddc7dd3bd16e88ab8"
-  integrity sha512-c6Kms/6I5bS4FWbvkarLE6jooNwfSpom0DyaDGqG8rLuFZld1YOVVDXwlLHCLq4oY0nhPi7Jl2aGz0VpGuJpLA==
+"@atlaskit/popper@^3.1.12":
+  version "3.1.12"
+  resolved "https://registry.yarnpkg.com/@atlaskit/popper/-/popper-3.1.12.tgz#9c8722d7787c847229c9cbd8232ba646eefa8353"
+  integrity sha512-KATLHu/SAAGMqjDoWX9li6x0IKYfx0Q7HvoCbGq+A5m4e8qYhZ51g6M9wWNW/eidhAlM3WAsUgEspNbLMer6AA==
   dependencies:
     memoize-one "^5.1.0"
     react-popper "1.3.6"
@@ -136,14 +136,14 @@
     tiny-invariant "^0.0.3"
     tslib "^1.9.3"
 
-"@atlaskit/select@^11.0.9":
-  version "11.0.9"
-  resolved "https://registry.yarnpkg.com/@atlaskit/select/-/select-11.0.9.tgz#9c512c2a74c97bf03a23d217d5df47e2a7f6a1f5"
-  integrity sha512-wSeVDdueOkgLYSG5CKtjTZ+6Z/9KAcXPb/VdM2HnWN8eHuZ04jUEacsKKX3z1oS63bUuPwlJdvAJhtB1GYJiww==
+"@atlaskit/select@^11.0.11":
+  version "11.0.11"
+  resolved "https://registry.yarnpkg.com/@atlaskit/select/-/select-11.0.11.tgz#56bac433d0574e446dbf2801c28aa6956d7f4896"
+  integrity sha512-97c4gocTLyxPghqkOqlwiwMf6ANaCyX/g3IMfbaS0kTPmwrfuCkQpnKMsSsuOwNU5HErOynpj0os30ED3vimlA==
   dependencies:
     "@atlaskit/analytics-next" "^6.3.5"
-    "@atlaskit/icon" "^20.1.0"
-    "@atlaskit/spinner" "^12.1.6"
+    "@atlaskit/icon" "^20.1.1"
+    "@atlaskit/spinner" "^12.1.7"
     "@atlaskit/theme" "^9.5.1"
     "@emotion/core" "^10.0.9"
     "@types/react-select" "^3.0.8"
@@ -156,10 +156,10 @@
     shallow-equal "^1.0.0"
     tslib "^1.9.3"
 
-"@atlaskit/spinner@^12.1.6":
-  version "12.1.6"
-  resolved "https://registry.yarnpkg.com/@atlaskit/spinner/-/spinner-12.1.6.tgz#bf8cfef92ebbdb87c492d087032e4096c4840078"
-  integrity sha512-pqwgpc8Gx0GMlWNqHmeRvJo1K0J7kiL59lEdtfv4drjWd2ny/wuW12AzdSrEs1gcGPjrdcwGx01bB7MCsEtRYA==
+"@atlaskit/spinner@^12.1.7":
+  version "12.1.7"
+  resolved "https://registry.yarnpkg.com/@atlaskit/spinner/-/spinner-12.1.7.tgz#ced8c614d48f2bebfea959891806ffa7466bf73f"
+  integrity sha512-fGnD6fcBW13RiS1DzGTvrm+M5Ld9Jhlw+Tx3PMs9naFpZvpTqoI5oVyTz+VDoyXhdQGKJAcfk0SntyONFZmDBg==
   dependencies:
     "@atlaskit/theme" "^9.5.1"
     react-transition-group "^2.2.1"
@@ -174,13 +174,13 @@
     prop-types "^15.5.10"
     tslib "^1.9.3"
 
-"@atlaskit/tooltip@^15.2.5":
-  version "15.2.5"
-  resolved "https://registry.yarnpkg.com/@atlaskit/tooltip/-/tooltip-15.2.5.tgz#a0bdc2e00f28cba11253c6f69df870d927dacd2e"
-  integrity sha512-qtLDqcZ58QxXkbVY9IdsiJBOdJbI3roqeehnLF7IHQ7JZkH8UcqHns+7IriKlFzN/54PKsa25o6QaWB2yKBvpQ==
+"@atlaskit/tooltip@^15.2.7":
+  version "15.2.7"
+  resolved "https://registry.yarnpkg.com/@atlaskit/tooltip/-/tooltip-15.2.7.tgz#f94dac24e98287dd49e38fce7619c6063b8ac455"
+  integrity sha512-1wS05MBcX2+1BZUt3RpqRQG/9OyHN3jFPAIgVkzpE2A3GUf68b3y482Eotk/4kW0rhDxN/NQd+U2QX21/ppQDg==
   dependencies:
     "@atlaskit/analytics-next" "^6.3.5"
-    "@atlaskit/popper" "^3.1.11"
+    "@atlaskit/popper" "^3.1.12"
     "@atlaskit/portal" "^3.1.6"
     "@atlaskit/theme" "^9.5.1"
     flushable "^1.0.0"
@@ -2974,6 +2974,13 @@ binary@~0.3.0:
     buffers "~0.1.1"
     chainsaw "~0.1.0"
 
+bindings@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
+  integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
+  dependencies:
+    file-uri-to-path "1.0.0"
+
 bl@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88"
@@ -3627,10 +3634,6 @@ chokidar@^2.0.4:
   optionalDependencies:
     fsevents "^1.2.7"
 
-chownr@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
-
 chownr@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
@@ -4603,7 +4606,7 @@ debounce@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408"
 
-debug@2, debug@2.6.9, debug@^2.0.0, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6, debug@~2.6.9:
+debug@2, debug@2.6.9, debug@^2.0.0, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6, debug@~2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   dependencies:
@@ -4652,10 +4655,6 @@ decompress-response@^3.3.0:
   dependencies:
     mimic-response "^1.0.0"
 
-deep-extend@~0.4.0:
-  version "0.4.2"
-  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
-
 deep-is@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -4737,10 +4736,6 @@ detect-indent@^5.0.0:
   resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
   integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
 
-detect-libc@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
-
 detect-newline@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@@ -5967,6 +5962,11 @@ file-selector@^0.1.11:
   dependencies:
     tslib "^1.9.0"
 
+file-uri-to-path@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
+  integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+
 filename-regex@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
@@ -6255,12 +6255,6 @@ fs-extra@8.1.0:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-minipass@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
-  dependencies:
-    minipass "^2.2.1"
-
 fs-write-stream-atomic@^1.0.8:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
@@ -6274,19 +6268,13 @@ fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
 
-fsevents@^1.2.2:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
+fsevents@^1.2.2, fsevents@^1.2.7:
+  version "1.2.13"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
+  integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==
   dependencies:
-    nan "^2.9.2"
-    node-pre-gyp "^0.10.0"
-
-fsevents@^1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4"
-  dependencies:
-    nan "^2.9.2"
-    node-pre-gyp "^0.10.0"
+    bindings "^1.5.0"
+    nan "^2.12.1"
 
 fsevents@^2.1.2:
   version "2.1.2"
@@ -7090,7 +7078,7 @@ iconv-lite@0.4.19, iconv-lite@~0.4.13:
   version "0.4.19"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
 
-iconv-lite@0.4.23, iconv-lite@^0.4.4:
+iconv-lite@0.4.23:
   version "0.4.23"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
   dependencies:
@@ -7121,12 +7109,6 @@ iferr@^0.1.5:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
 
-ignore-walk@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
-  dependencies:
-    minimatch "^3.0.4"
-
 ignore@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
@@ -7245,7 +7227,7 @@ inherits@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
 
-ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
+ini@^1.3.4, ini@^1.3.5:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
 
@@ -9309,19 +9291,6 @@ minimist@~0.0.1:
   version "0.0.10"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
 
-minipass@^2.2.1, minipass@^2.3.3:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233"
-  dependencies:
-    safe-buffer "^5.1.2"
-    yallist "^3.0.0"
-
-minizlib@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
-  dependencies:
-    minipass "^2.2.1"
-
 mississippi@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
@@ -9569,6 +9538,11 @@ mv@~2:
     ncp "~2.0.0"
     rimraf "~2.4.0"
 
+nan@^2.12.1:
+  version "2.14.1"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
+  integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
+
 nan@^2.13.2:
   version "2.14.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
@@ -9578,10 +9552,6 @@ nan@^2.3.3:
   version "2.8.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
 
-nan@^2.9.2:
-  version "2.10.0"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
-
 nanomatch@^1.2.9:
   version "1.2.9"
   resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2"
@@ -9607,14 +9577,6 @@ ncp@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
 
-needle@^2.2.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d"
-  dependencies:
-    debug "^2.1.2"
-    iconv-lite "^0.4.4"
-    sax "^1.2.4"
-
 negotiator@0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
@@ -9777,21 +9739,6 @@ node-object-hash@^1.2.0:
   resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
   integrity sha512-UdS4swXs85fCGWWf6t6DMGgpN/vnlKeSGEQ7hJcrs7PBFoxoKLmibc3QRb7fwiYsjdL7PX8iI/TMSlZ90dgHhQ==
 
-node-pre-gyp@^0.10.0:
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46"
-  dependencies:
-    detect-libc "^1.0.2"
-    mkdirp "^0.5.1"
-    needle "^2.2.0"
-    nopt "^4.0.1"
-    npm-packlist "^1.1.6"
-    npmlog "^4.0.2"
-    rc "^1.1.7"
-    rimraf "^2.6.1"
-    semver "^5.3.0"
-    tar "^4"
-
 node-readfiles@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/node-readfiles/-/node-readfiles-0.2.0.tgz#dbbd4af12134e2e635c245ef93ffcf6f60673a5d"
@@ -9864,13 +9811,6 @@ nopt@1.0.10:
   dependencies:
     abbrev "1"
 
-nopt@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
-  dependencies:
-    abbrev "1"
-    osenv "^0.1.4"
-
 normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
@@ -9932,17 +9872,6 @@ normalize-url@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.2.0.tgz#98d0948afc82829f374320f405fe9ca55a5f8567"
 
-npm-bundled@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308"
-
-npm-packlist@^1.1.6:
-  version "1.1.10"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a"
-  dependencies:
-    ignore-walk "^3.0.1"
-    npm-bundled "^1.0.1"
-
 npm-run-all@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.2.tgz#90d62d078792d20669139e718621186656cea056"
@@ -9970,7 +9899,7 @@ npm-run-path@^4.0.0:
   dependencies:
     path-key "^3.0.0"
 
-"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
   dependencies:
@@ -10354,13 +10283,6 @@ osenv@0:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
-osenv@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
-  dependencies:
-    os-homedir "^1.0.0"
-    os-tmpdir "^1.0.0"
-
 p-any@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/p-any/-/p-any-1.1.0.tgz#1d03835c7eed1e34b8e539c47b7b60d0d015d4e1"
@@ -11634,15 +11556,6 @@ raw-body@^2.3.2:
     iconv-lite "0.4.23"
     unpipe "1.0.0"
 
-rc@^1.1.7:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.3.tgz#51575a900f8dd68381c710b4712c2154c3e2035b"
-  dependencies:
-    deep-extend "~0.4.0"
-    ini "~1.3.0"
-    minimist "^1.2.0"
-    strip-json-comments "~2.0.1"
-
 react-addons-text-content@^0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/react-addons-text-content/-/react-addons-text-content-0.0.4.tgz#d2e259fdc951d1d8906c08902002108dce8792e5"
@@ -12541,7 +12454,7 @@ rimraf@2, rimraf@2.6.3, rimraf@^2.6.3:
   dependencies:
     glob "^7.1.3"
 
-rimraf@^2.5.4, rimraf@^2.6.1:
+rimraf@^2.5.4:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
   dependencies:
@@ -12692,7 +12605,7 @@ sax@1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
 
-sax@>=0.6.0, sax@^1.2.1, sax@^1.2.4, sax@~1.2.4:
+sax@>=0.6.0, sax@^1.2.1, sax@~1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -13651,7 +13564,7 @@ strip-indent@^3.0.0:
   dependencies:
     min-indent "^1.0.0"
 
-strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
+strip-json-comments@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 
@@ -13986,18 +13899,6 @@ tar@^2.0.0:
     fstream "^1.0.2"
     inherits "2"
 
-tar@^4:
-  version "4.4.4"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd"
-  dependencies:
-    chownr "^1.0.1"
-    fs-minipass "^1.2.5"
-    minipass "^2.3.3"
-    minizlib "^1.1.0"
-    mkdirp "^0.5.0"
-    safe-buffer "^5.1.2"
-    yallist "^3.0.2"
-
 teeny-request@^5.2.1:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-5.2.1.tgz#a6394db8359b87e64e47eeb2fbf34a65c9a751ff"
@@ -15309,7 +15210,7 @@ yallist@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
 
-yallist@^3.0.0, yallist@^3.0.2:
+yallist@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"