فهرست منبع

Merge branch 'master' into feat/use-MongoDB-GridFS-for-file-storage

# Conflicts:
#	yarn.lock
yusueketk 7 سال پیش
والد
کامیت
c07ec23258

+ 1 - 1
package.json

@@ -96,7 +96,7 @@
     "markdown-it-blockdiag": "^1.0.2",
     "md5": "^2.2.1",
     "method-override": "^3.0.0",
-    "migrate-mongo": "^3.0.5",
+    "migrate-mongo": "^4.0.0",
     "mkdirp": "~0.5.1",
     "module-alias": "^2.0.6",
     "mongoose": "^5.3.1",

+ 1 - 0
public/images/icons/editor/table.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M0 19.7v216.6h256V19.7H0zm78.8 196.9H19.7v-39.4h59.1v39.4zm0-59.1H19.7v-39.3h59.1v39.3zm0-59H19.7V59.1h59.1v39.4zm78.7 118.1h-59v-39.4h59v39.4zm0-59.1h-59v-39.3h59v39.3zm0-59h-59V59.1h59v39.4zm78.8 118.1h-59.1v-39.4h59.1v39.4zm0-59.1h-59.1v-39.3h59.1v39.3zm0-59h-59.1V59.1h59.1v39.4z"/></svg>

+ 0 - 18
src/client/js/components/Page.js

@@ -17,7 +17,6 @@ export default class Page extends React.Component {
       currentTargetTableArea: null
     };
 
-    this.appendEditSectionButtons = this.appendEditSectionButtons.bind(this);
     this.renderHtml = this.renderHtml.bind(this);
     this.getHighlightedBody = this.getHighlightedBody.bind(this);
     this.saveHandlerForHandsontableModal = this.saveHandlerForHandsontableModal.bind(this);
@@ -27,27 +26,10 @@ export default class Page extends React.Component {
     this.renderHtml(this.props.markdown, this.props.highlightKeywords);
   }
 
-  componentDidUpdate() {
-    this.appendEditSectionButtons();
-  }
-
   setMarkdown(markdown) {
     this.renderHtml(markdown, this.props.highlightKeywords);
   }
 
-  /**
-   * Add edit section buttons to headers
-   * This invoke `appendEditSectionButtons` method of `legacy/crowi.js`
-   *
-   * TODO: transplant `appendEditSectionButtons` to this class in the future
-   */
-  appendEditSectionButtons(parentElement) {
-    if (this.props.showHeadEditButton) {
-      const crowiForJquery = this.props.crowi.getCrowiForJquery();
-      crowiForJquery.appendEditSectionButtons(this.revisionBodyElement);
-    }
-  }
-
   /**
    * transplanted from legacy code -- Yuki Takei
    * @param {string} body html strings

+ 1 - 1
src/client/js/components/PageEditor/CodeMirrorEditor.js

@@ -650,7 +650,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
   }
 
   getNavbarItems() {
-    return <Button bsSize="small" onClick={ this.showHandsonTableHandler }><i className="icon-grid"></i></Button>;
+    return <Button bsSize="small" onClick={ this.showHandsonTableHandler }><img src="/images/icons/editor/table.svg" width="14" /></Button>;
   }
 
   render() {

+ 71 - 20
src/client/js/components/PageEditor/HandsontableModal.jsx

@@ -4,9 +4,11 @@ import PropTypes from 'prop-types';
 import Modal from 'react-bootstrap/es/Modal';
 import Button from 'react-bootstrap/es/Button';
 
+import Handsontable from 'handsontable';
 import { HotTable } from '@handsontable/react';
 
 import MarkdownTable from '../../models/MarkdownTable';
+import HandsontableUtil from './HandsontableUtil';
 
 export default class HandsontableModal extends React.Component {
   constructor(props) {
@@ -15,17 +17,8 @@ export default class HandsontableModal extends React.Component {
     this.state = {
       show: false,
       markdownTableOnInit: HandsontableModal.getDefaultMarkdownTable(),
-      markdownTable: HandsontableModal.getDefaultMarkdownTable()
-    };
-
-    this.settings = {
-      height: 300,
-      rowHeaders: true,
-      colHeaders: true,
-      fixedRowsTop: [0, 1],
-      contextMenu: ['row_above', 'row_below', 'col_left', 'col_right', '---------', 'remove_row', 'remove_col', '---------', 'alignment'],
-      stretchH: 'all',
-      selectionMode: 'multiple',
+      markdownTable: HandsontableModal.getDefaultMarkdownTable(),
+      handsontableSetting: HandsontableModal.getDefaultHandsotableSetting()
     };
 
     this.init = this.init.bind(this);
@@ -36,8 +29,16 @@ export default class HandsontableModal extends React.Component {
 
   init(markdownTable) {
     const initMarkdownTable = markdownTable || HandsontableModal.getDefaultMarkdownTable();
-    this.setState({ markdownTableOnInit: initMarkdownTable });
-    this.setState({ markdownTable: initMarkdownTable.clone() });
+    this.setState(
+      {
+        markdownTableOnInit: initMarkdownTable,
+        markdownTable: initMarkdownTable.clone(),
+        handsontableSetting: Object.assign({}, this.state.handsontableSetting, {
+          afterUpdateSettings: HandsontableUtil.createHandlerToSynchronizeHandontableAlignWith(initMarkdownTable.options.align),
+          loadData: HandsontableUtil.createHandlerToSynchronizeHandontableAlignWith(initMarkdownTable.options.align)
+        })
+      }
+    );
   }
 
   show(markdownTable) {
@@ -54,9 +55,13 @@ export default class HandsontableModal extends React.Component {
   }
 
   save() {
+    let newMarkdownTable = this.state.markdownTable.clone();
+    newMarkdownTable.options.align = HandsontableUtil.getMarkdownTableAlignmentFrom(this.refs.hotTable.hotInstance);
+
     if (this.props.onSave != null) {
-      this.props.onSave(this.state.markdownTable);
+      this.props.onSave(newMarkdownTable);
     }
+
     this.setState({ show: false });
   }
 
@@ -68,7 +73,7 @@ export default class HandsontableModal extends React.Component {
         </Modal.Header>
         <Modal.Body className="p-0">
           <div className="p-4">
-            <HotTable data={this.state.markdownTable.table} settings={this.settings} />
+            <HotTable ref='hotTable' data={this.state.markdownTable.table} settings={this.state.handsontableSetting} />
           </div>
         </Modal.Body>
         <Modal.Footer>
@@ -85,11 +90,57 @@ export default class HandsontableModal extends React.Component {
   }
 
   static getDefaultMarkdownTable() {
-    return new MarkdownTable([
-      ['col1', 'col2', 'col3'],
-      ['', '', ''],
-      ['', '', ''],
-    ]);
+    return new MarkdownTable(
+      [
+        ['col1', 'col2', 'col3'],
+        ['', '', ''],
+        ['', '', ''],
+      ],
+      {
+        align: ['', '', '']
+      }
+    );
+  }
+
+  static getDefaultHandsotableSetting() {
+    return {
+      height: 300,
+      rowHeaders: true,
+      colHeaders: true,
+      contextMenu: {
+        items: {
+          'row_above': {}, 'row_below': {}, 'col_left': {}, 'col_right': {},
+          'separator1': Handsontable.plugins.ContextMenu.SEPARATOR,
+          'remove_row': {}, 'remove_col': {},
+          'separator2': Handsontable.plugins.ContextMenu.SEPARATOR,
+          'custom_alignment': {
+            name: 'Align columns',
+            key: 'align_columns',
+            submenu: {
+              items: [{
+                name: 'Left',
+                key: 'align_columns:1',
+                callback: function(key, selection) {
+                  HandsontableUtil.setClassNameToColumns(this, selection[0].start.col, selection[0].end.col, 'htLeft');
+                }}, {
+                name: 'Center',
+                key: 'align_columns:2',
+                callback: function(key, selection) {
+                  HandsontableUtil.setClassNameToColumns(this, selection[0].start.col, selection[0].end.col, 'htCenter');
+                }}, {
+                name: 'Right',
+                key: 'align_columns:3',
+                callback: function(key, selection) {
+                  HandsontableUtil.setClassNameToColumns(this, selection[0].start.col, selection[0].end.col, 'htRight');
+                }}
+              ]
+            }
+          }
+        }
+      },
+      stretchH: 'all',
+      selectionMode: 'multiple'
+    };
   }
 }
 

+ 54 - 0
src/client/js/components/PageEditor/HandsontableUtil.js

@@ -0,0 +1,54 @@
+/**
+ * Utility for Handsontable (and cooperation with MarkdownTable)
+ */
+export default class HandsontableUtil {
+
+  static setClassNameToColumns(core, startCol, endCol, className) {
+    for (let i = startCol; i <= endCol; i++) {
+      for (let j = 0; j < core.countRows(); j++) {
+        core.setCellMeta(j, i, 'className', className);
+      }
+    }
+    core.render();
+  }
+
+  /**
+   * return a function(handsontable event handler) to adjust the handsontable alignment to the markdown table
+   */
+  static createHandlerToSynchronizeHandontableAlignWith(markdownTableAlign) {
+    const mapping = {
+      'r': 'htRight',
+      'c': 'htCenter',
+      'l': 'htLeft',
+      '': ''
+    };
+
+    return function() {
+      const align = markdownTableAlign;
+      for (let i = 0; i < align.length; i++) {
+        HandsontableUtil.setClassNameToColumns(this, i, i, mapping[align[i]]);
+      }
+    };
+  }
+
+  /**
+   * return MarkdownTable alignment retrieved from Handsontable instance
+   */
+  static getMarkdownTableAlignmentFrom(handsontable) {
+    const cellMetasAtFirstRow = handsontable.getCellMetaAtRow(0);
+    const mapping = {
+      'htRight': 'r',
+      'htCenter': 'c',
+      'htLeft': 'l',
+      '': ''
+    };
+
+    let align = [];
+    for (let i = 0; i < cellMetasAtFirstRow.length; i++) {
+      align.push(mapping[cellMetasAtFirstRow[i].className]);
+    }
+
+    return align;
+  }
+}
+

+ 26 - 32
src/client/js/components/RecentCreated/RecentCreated.js → src/client/js/components/RecentCreated/RecentCreated.jsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import UserPicture from '../User/UserPicture';
+import Page from '../PageList/Page';
 
 import PropTypes from 'prop-types';
 import Pagination from 'react-bootstrap/lib/Pagination';
@@ -11,8 +11,7 @@ export default class RecentCreated extends React.Component {
     this.state = {
       pages: [],
       activePage: 1,
-      PaginationNumbers: {},
-
+      paginationNumbers: {},
     };
     this.calculatePagination = this.calculatePagination.bind(this);
   }
@@ -21,32 +20,33 @@ export default class RecentCreated extends React.Component {
   componentWillMount() {
     this.getRecentCreatedList(1);
   }
-  getRecentCreatedList(selectPageNumber) {
 
+  getRecentCreatedList(selectPageNumber) {
     const pageId = this.props.pageId;
     const userId = this.props.crowi.me;
     const limit = this.props.limit;
     const offset = (selectPageNumber - 1) * limit;
 
     // pagesList get and pagination calculate
-    this.props.crowi.apiGet('/pages.recentCreated', {page_id: pageId, user: userId, limit: limit, offset: offset, })
+    this.props.crowi.apiGet('/pages.recentCreated', { page_id: pageId, user: userId, limit, offset })
       .then(res => {
         const totalCount = res.pages[0].totalCount;
         const activePage = selectPageNumber;
         const pages = res.pages[1];
         // pagiNation calculate function call
-        const PaginationNumbers = this.calculatePagination(limit, totalCount, activePage);
+        const paginationNumbers = this.calculatePagination(limit, totalCount, activePage);
         this.setState({
           pages,
           activePage,
-          PaginationNumbers,
+          paginationNumbers,
         });
       });
   }
+
   calculatePagination(limit, totalCount, activePage) {
-    let PaginationNumbers = {};
-    // pagiNation totalPageNumber calculate
-    let totalPage = Math.floor(totalCount / limit) + (totalCount % limit === 0 ? 0  : 1);
+    // calc totalPageNumber
+    const totalPage = Math.floor(totalCount / limit) + (totalCount % limit === 0 ? 0  : 1);
+
     let paginationStart = activePage - 2;
     let maxViewPageNum =  activePage + 2;
     // pagiNation Number area size = 5 , pageNuber calculate in here
@@ -61,11 +61,12 @@ export default class RecentCreated extends React.Component {
       maxViewPageNum -= diff;
       paginationStart = Math.max(1, paginationStart - diff);
     }
-    PaginationNumbers.totalPage = totalPage;
-    PaginationNumbers.paginationStart = paginationStart;
-    PaginationNumbers.maxViewPageNum = maxViewPageNum;
 
-    return PaginationNumbers;
+    return {
+      totalPage,
+      paginationStart,
+      maxViewPageNum,
+    };
   }
   /**
    * generate Elements of Page
@@ -74,14 +75,8 @@ export default class RecentCreated extends React.Component {
    *
    */
   generatePageList(pages) {
-    return pages.map((page, i) => {
-      const pageuser = page.lastUpdateUser;
-      return (
-        <li key={i}>
-          <UserPicture user={pageuser} />
-          <a href={page.path} className="page-list-link" data-path={page.path} data-short-path={page.revision.body}>{decodeURI(page.path)}</a>
-        </li>
-      );
+    return pages.map(page => {
+      return <Page page={page} key={'recent-created:list-view:' + page._id} />;
     });
 
   }
@@ -160,25 +155,24 @@ export default class RecentCreated extends React.Component {
     const pageList = this.generatePageList(this.state.pages);
 
     let paginationItems = [];
-    let activePage = this.state.activePage;
-    let totalPage = this.state.PaginationNumbers.totalPage;
-    let paginationStart = this.state.PaginationNumbers.paginationStart;
-    let maxViewPageNum =  this.state.PaginationNumbers.maxViewPageNum;
-    let firstPrevItems = this.generateFirstPrev(activePage);
+
+    const activePage = this.state.activePage;
+    const totalPage = this.state.paginationNumbers.totalPage;
+    const paginationStart = this.state.paginationNumbers.paginationStart;
+    const maxViewPageNum =  this.state.paginationNumbers.maxViewPageNum;
+    const firstPrevItems = this.generateFirstPrev(activePage);
     paginationItems.push(firstPrevItems);
-    let paginations = this.generatePaginations(activePage, paginationStart, maxViewPageNum);
+    const paginations = this.generatePaginations(activePage, paginationStart, maxViewPageNum);
     paginationItems.push(paginations);
-    let nextLastItems = this.generateNextLast(activePage, totalPage);
+    const nextLastItems = this.generateNextLast(activePage, totalPage);
     paginationItems.push(nextLastItems);
 
     return (
       <div className="page-list-container-create">
         <ul className="page-list-ul page-list-ul-flat">
-            {pageList}
+          {pageList}
         </ul>
-        {
         <Pagination bsSize="small">{paginationItems}</Pagination>
-        }
       </div>
     );
   }

+ 0 - 19
src/client/js/legacy/crowi.js

@@ -36,25 +36,6 @@ Crowi.renderTocContent = (tocHtml) => {
   $('#revision-toc-content').html(tocHtml);
 };
 
-/**
- * append buttons to section headers
- */
-Crowi.appendEditSectionButtons = function(parentElement) {
-  $('h1,h2,h3,h4,h5,h6', parentElement).each(function(idx, elm) {
-    const line = +elm.getAttribute('data-line');
-
-    // add button
-    $(this).append(`
-      <span class="revision-head-edit-button">
-        <a href="#edit" onClick="Crowi.setCaretLineData(${line})">
-          <i class="icon-note"></i>
-        </a>
-      </span>
-      `
-    );
-  });
-};
-
 /**
  * set 'data-caret-line' attribute that will be processed when 'shown.bs.tab' event fired
  * @param {number} line

+ 2 - 0
src/client/js/util/GrowiRenderer.js

@@ -17,6 +17,7 @@ import TaskListsConfigurer from './markdown-it/task-lists';
 import TocAndAnchorConfigurer from './markdown-it/toc-and-anchor';
 import BlockdiagConfigurer from './markdown-it/blockdiag';
 import TableWithHandsontableButtonConfigurer from './markdown-it/table-with-handsontable-button';
+import HeaderWithEditLinkConfigurer from './markdown-it/header-with-edit-link';
 
 export default class GrowiRenderer {
 
@@ -87,6 +88,7 @@ export default class GrowiRenderer {
           new FooternoteConfigurer(crowi),
           new TocAndAnchorConfigurer(crowi, options.renderToc),
           new HeaderLineNumberConfigurer(crowi),
+          new HeaderWithEditLinkConfigurer(crowi),
           new TableWithHandsontableButtonConfigurer(crowi)
         ]);
         break;

+ 16 - 0
src/client/js/util/markdown-it/header-with-edit-link.js

@@ -0,0 +1,16 @@
+export default class HeaderWithEditLinkConfigurer {
+
+  constructor(crowi) {
+    this.crowi = crowi;
+  }
+
+  configure(md) {
+    md.renderer.rules.heading_close = (tokens, idx) => {
+      return `<span class="revision-head-edit-button">
+                <a href="#edit" onClick="Crowi.setCaretLineData(parseInt(this.parentNode.parentNode.dataset.line, 10))">
+                  <i class="icon-note"></i>
+                </a>
+              </span></${tokens[idx].tag}>`;
+    };
+  }
+}

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

@@ -49,6 +49,18 @@
           min-height: calc(100vh - #{$header-plus-footer});   // for IE11
           height: calc(100vh - #{$header-plus-footer});
         }
+
+        .navbar-editor {
+          button {
+            padding: 7px 8px;
+            line-height: 1;
+          }
+
+          img {
+            vertical-align: bottom;
+          }
+        }
+
         // left(editor)
         .page-editor-editor-container {
           min-height: calc(100vh - #{$header-plus-footer});   // for IE11

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

@@ -169,8 +169,15 @@ body.on-edit {
       // add icon on cursor
       .autoformat-markdown-table-activated .CodeMirror-cursor {
         &:after {
-          font-family: 'FontAwesome';
-          content: '\f0ce';
+          display: block;
+          content: ' ';
+          background-image: url(/images/icons/editor/table.svg);
+
+          position: relative;
+          width: 14px;
+          height: 14px;
+          top: -16px;
+          left: 5px;
         }
       }
 

+ 0 - 5
src/migrations/20180927102719-init-serverurl.js

@@ -6,11 +6,6 @@ const logger = require('@alias/logger')('growi:migrate:init-serverurl');
 const mongoose = require('mongoose');
 const config = require('@root/config/migrate');
 
-const queryToFindSiteUrl = {
-  ns: 'crowi',
-  key: 'app:siteUrl',
-};
-
 /**
  * check all values of the array are equal
  * @see https://stackoverflow.com/a/35568895

+ 15 - 52
src/server/models/config.js

@@ -138,6 +138,15 @@ module.exports = function(crowi) {
     return config.crowi[key];
   }
 
+  function getValueForMarkdownNS(config, key) {
+    // return the default value if undefined
+    if (undefined === config.markdown || undefined === config.markdown[key]) {
+      return getDefaultMarkdownConfigs()[key];
+    }
+
+    return config.markdown[key];
+  }
+
   configSchema.statics.getRestrictGuestModeLabels = function() {
     var labels = {};
     labels[SECURITY_RESTRICT_GUEST_MODE_DENY]     = 'security_setting.guest_mode.deny';
@@ -344,78 +353,37 @@ module.exports = function(crowi) {
 
   configSchema.statics.isEnabledLinebreaks = function(config) {
     const key = 'markdown:isEnabledLinebreaks';
-
-    // return default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs()[key];
-    }
-
-    return config.markdown[key];
+    return getValueForMarkdownNS(config, key);
   };
 
   configSchema.statics.isEnabledLinebreaksInComments = function(config) {
     const key = 'markdown:isEnabledLinebreaksInComments';
-
-    // return default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs()[key];
-    }
-
-    return config.markdown[key];
+    return getValueForMarkdownNS(config, key);
   };
 
   configSchema.statics.pageBreakSeparator = function(config) {
     const key = 'markdown:presentation:pageBreakSeparator';
-
-    // return default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs[key];
-    }
-
-    return config.markdown[key];
+    return getValueForMarkdownNS(config, key);
   };
 
   configSchema.statics.pageBreakCustomSeparator = function(config) {
     const key = 'markdown:presentation:pageBreakCustomSeparator';
-
-    // return default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs[key];
-    }
-
-    return config.markdown[key];
+    return getValueForMarkdownNS(config, key);
   };
 
   configSchema.statics.isEnabledXssPrevention = function(config) {
     const key = 'markdown:xss:isEnabledPrevention';
-
-    // return default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs[key];
-    }
-
-    return config.markdown[key];
+    return getValueForMarkdownNS(config, key);
   };
 
   configSchema.statics.xssOption = function(config) {
     const key = 'markdown:xss:option';
-
-    // return default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs[key];
-    }
-
-    return config.markdown[key];
+    return getValueForMarkdownNS(config, key);
   };
 
   configSchema.statics.tagWhiteList = function(config) {
     const key = 'markdown:xss:tagWhiteList';
 
-    // return default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs[key];
-    }
-
     if (this.isEnabledXssPrevention(config)) {
       switch (this.xssOption(config)) {
         case 1: // ignore all: use default option
@@ -440,11 +408,6 @@ module.exports = function(crowi) {
   configSchema.statics.attrWhiteList = function(config) {
     const key = 'markdown:xss:attrWhiteList';
 
-    // return default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs[key];
-    }
-
     if (this.isEnabledXssPrevention(config)) {
       switch (this.xssOption(config)) {
         case 1: // ignore all: use default option

+ 1 - 1
src/server/models/page.js

@@ -540,7 +540,7 @@ module.exports = function(crowi) {
     const Page = this;
     const templatePath = cutOffLastSlash(path);
     const pathList = generatePathsOnTree(templatePath, []);
-    const regexpList = pathList.map(path => new RegExp(`^${path}/_{1,2}template$`));
+    const regexpList = pathList.map(path => new RegExp(`^${escapeStringRegexp(path)}/_{1,2}template$`));
 
     return Page
       .find({path: {$in: regexpList}})

+ 21 - 21
yarn.lock

@@ -393,10 +393,6 @@ ansistyles@~0.1.1:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539"
 
-any-promise@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
-
 anymatch@^1.3.0:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
@@ -2541,7 +2537,7 @@ dasherize@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308"
 
-date-fns@^1.29.0:
+date-fns@1.29.0, date-fns@^1.29.0:
   version "1.29.0"
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
 
@@ -3615,9 +3611,9 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.1"
     readable-stream "^2.0.4"
 
-flushwritable@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/flushwritable/-/flushwritable-1.0.0.tgz#3e328d8fde412ad47e738e3be750b4d290043498"
+fn-args@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/fn-args/-/fn-args-3.0.0.tgz#df5c3805ed41ec3b38a72aabe390cf9493ec084c"
 
 follow-redirects@^1.3.0:
   version "1.4.1"
@@ -5635,18 +5631,18 @@ micromatch@^3.1.4, micromatch@^3.1.8:
     snapdragon "^0.8.1"
     to-regex "^3.0.2"
 
-migrate-mongo@^3.0.5:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/migrate-mongo/-/migrate-mongo-3.0.5.tgz#c4d83ca285361a1005f147f22e3ba2d344800e16"
+migrate-mongo@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/migrate-mongo/-/migrate-mongo-4.0.0.tgz#7bb83c7b8e96253a15b67a298478a57f6e3b08d8"
   dependencies:
-    async "2.6.1"
     cli-table "0.3.1"
     commander "2.18.0"
+    date-fns "1.29.0"
+    fn-args "3.0.0"
     fs-extra "7.0.0"
     lodash "4.17.11"
-    moment "2.22.2"
     mongodb "3.1.6"
-    shifting "1.2.5"
+    p-each-series "1.0.0"
 
 miller-rabin@^4.0.0:
   version "4.0.1"
@@ -5816,7 +5812,7 @@ moment@2.20.1, moment@^2.10.6:
   version "2.20.1"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"
 
-moment@2.22.2, moment@2.x:
+moment@2.x:
   version "2.22.2"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
 
@@ -6508,6 +6504,12 @@ osenv@0, osenv@^0.1.4:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
+p-each-series@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
+  dependencies:
+    p-reduce "^1.0.0"
+
 p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@@ -6536,6 +6538,10 @@ p-locate@^3.0.0:
   dependencies:
     p-limit "^2.0.0"
 
+p-reduce@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
+
 p-try@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@@ -8310,12 +8316,6 @@ shellwords@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
 
-shifting@1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/shifting/-/shifting-1.2.5.tgz#24b8b370586bb661a353c10a3e18e4a85abf5053"
-  dependencies:
-    any-promise "^1.3.0"
-
 signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"