Преглед изворни кода

Merge remote-tracking branch 'origin/master' into feat/use-passport

Yuki Takei пре 8 година
родитељ
комит
7fd7227db7

+ 5 - 0
CHANGES.md

@@ -1,6 +1,11 @@
 CHANGES
 ========
 
+## 2.0.10
+
+* Feat: Selective batch deletion in search result page
+* Support: Upgrade outdated libs
+
 ## 2.0.9
 
 * Fix: Server is down when a guest user accesses to someone's private pages

+ 1 - 1
README.md

@@ -77,7 +77,7 @@ On-premise
 ### Dependencies
 
 - node 6.x (DON'T USE 7.x)
-- npm 4.x
+- npm 5.x
 - yarn
 - MongoDB 3.x
 

+ 1 - 0
config/env.dev.js

@@ -1,5 +1,6 @@
 module.exports = {
   NODE_ENV: 'development',
+  // FILE_UPLOAD: 'local',
   // MATHJAX: 1,
   // REDIS_URL: 'redis://localhost:6379/crowi',
   // ELASTICSEARCH_URI: 'http://localhost:9200/crowi',

+ 2 - 0
lib/views/layout/layout.html

@@ -70,6 +70,8 @@
   data-me="{{ user._id.toString() }}"
   data-plugin-enabled="{{ isEnabledPlugins() }}"
  {% block html_base_attr %}{% endblock %}
+  data-csrftoken="{{ csrf() }}"
+  data-current-username="{% if user %}{{ user.username }}{% endif %}"
  >
 
 {% block layout_head_nav %}

+ 0 - 2
lib/views/not_found.html

@@ -38,12 +38,10 @@
   data-path-shortname="{{ path|path2name }}"
   data-page-id="{% if page %}{{ page._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
-  data-current-username="{% if user %}{{ user.username }}{% endif %}"
   data-page-revision-id="{% if revision %}{{ revision._id.toString() }}{% endif %}"
   data-page-revision-created="{% if revision %}{{ revision.createdAt|datetz('U') }}{% endif %}"
   data-page-is-seen="{% if page and page.isSeenUser(user) %}1{% else %}0{% endif %}"
   data-linebreaks-enabled="{{ isEnabledLinebreaks() }}"
-  data-csrftoken="{{ csrf() }}"
   >
 
   <ul class="nav nav-tabs hidden-print">

+ 0 - 2
lib/views/page.html

@@ -54,12 +54,10 @@
   data-path-shortname="{{ path|path2name }}"
   data-page-id="{% if page %}{{ page._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
-  data-current-username="{% if user %}{{ user.username }}{% endif %}"
   data-page-revision-id="{% if revision %}{{ revision._id.toString() }}{% endif %}"
   data-page-revision-created="{% if revision %}{{ revision.createdAt|datetz('U') }}{% endif %}"
   data-page-is-seen="{% if page and page.isSeenUser(user) %}1{% else %}0{% endif %}"
   data-linebreaks-enabled="{{ isEnabledLinebreaks() }}"
-  data-csrftoken="{{ csrf() }}"
   >
 
   {% if not page %}

+ 0 - 2
lib/views/page_list.html

@@ -77,12 +77,10 @@
   data-page-portal="{% if page and page.isPortal() %}1{% else %}0{% endif %}"
   data-page-id="{% if page %}{{ page._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
-  data-current-username="{% if user %}{{ user.username }}{% endif %}"
   data-page-revision-id="{% if revision %}{{ revision._id.toString() }}{% endif %}"
   data-page-revision-created="{% if revision %}{{ revision.createdAt|datetz('U') }}{% endif %}"
   data-page-is-seen="{% if page and page.isSeenUser(user) %}1{% else %}0{% endif %}"
   data-linebreaks-enabled="{{ isEnabledLinebreaks() }}"
-  data-csrftoken="{{ csrf() }}"
   >
 
 <div class="portal {% if not page or req.query.offset > 0 %}hide{% endif %}">

+ 3 - 3
package.json

@@ -68,7 +68,7 @@
     "diff2html": "^2.3.0",
     "elasticsearch": "^13.2.0",
     "emojify.js": "^1.1.0",
-    "env-cmd": "^5.0.0",
+    "env-cmd": "^6.0.0",
     "escape-string-regexp": "^1.0.5",
     "express": "~4.15.2",
     "express-form": "~0.12.0",
@@ -76,7 +76,7 @@
     "express-session": "~1.15.0",
     "express-webpack-assets": "^0.1.0",
     "file-loader": "^0.11.1",
-    "googleapis": "^21.3.0",
+    "googleapis": "^22.0.0",
     "graceful-fs": "^4.1.11",
     "highlight.js": "^9.10.0",
     "i18next": "^9.0.0",
@@ -135,7 +135,7 @@
   },
   "engines": {
     "node": "6.x",
-    "npm": "4.x"
+    "npm": "5.x"
   },
   "config": {
     "blanket": {

+ 12 - 7
resource/css/_search.scss

@@ -80,26 +80,31 @@
         overflow-y: scroll;
       }
       .nav {
-
         > li {
-          padding: 0px 11px 0 8px;
+          padding: 0px 10px 0 0;
           &.active {
-            padding: 0px 8px;
+            padding-right: 7px;
             border-right: solid 3px #666;
             background: #f0f0f0;
           }
         }
       }
     }
-  }
-
-  .search-result-content {
-    padding-bottom: 32px;
 
     .search-result-meta {
       margin-bottom: 16px;
       font-weight: bold;
     }
+
+    .search-result-list-delete-checkbox {
+      margin: 0 10px 0 0;
+      vertical-align: middle;
+    }
+  }
+
+  .search-result-content {
+    padding-bottom: 32px;
+
     .search-result-page {
       > h2 {
         font-size: 20px;

+ 2 - 2
resource/js/app.js

@@ -39,8 +39,8 @@ if (mainContent !== null) {
 
 // FIXME
 const crowi = new Crowi({
-  me: $('#content-main').data('current-username'),
-  csrfToken: $('#content-main').data('csrftoken'),
+  me: $('body').data('current-username'),
+  csrfToken: $('body').data('csrftoken'),
 }, window);
 window.crowi = crowi;
 crowi.setConfig(JSON.parse(document.getElementById('crowi-context-hydrate').textContent || '{}'));

+ 2 - 0
resource/js/components/PageListSearch.js

@@ -151,6 +151,7 @@ export default class PageListSearch extends React.Component {
           searchingKeyword={this.state.searchingKeyword}
           searchResultMeta={this.state.searchResultMeta}
           searchError={this.state.searchError}
+          crowi={this.props.crowi}
           />
       </div>
     );
@@ -159,6 +160,7 @@ export default class PageListSearch extends React.Component {
 
 PageListSearch.propTypes = {
   query: PropTypes.object,
+  crowi: PropTypes.object.isRequired,
 };
 PageListSearch.defaultProps = {
   //pollInterval: 1000,

+ 2 - 0
resource/js/components/SearchPage.js

@@ -104,6 +104,7 @@ export default class SearchPage extends React.Component {
           pages={this.state.searchedPages}
           searchingKeyword={this.state.searchingKeyword}
           searchResultMeta={this.state.searchResultMeta}
+          crowi={this.props.crowi}
           />
       </div>
     );
@@ -112,6 +113,7 @@ export default class SearchPage extends React.Component {
 
 SearchPage.propTypes = {
   query: PropTypes.object,
+  crowi: PropTypes.object.isRequired,
 };
 SearchPage.defaultProps = {
   //pollInterval: 1000,

+ 61 - 0
resource/js/components/SearchPage/DeletePageListModal.js

@@ -0,0 +1,61 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Button, Modal } from 'react-bootstrap';
+import moment from 'moment';
+
+import ReactUtils from '../ReactUtils';
+
+export default class DeletePageListModal extends React.Component {
+
+  /*
+   * the threshold for omitting body
+   */
+  static get OMIT_BODY_THRES() { return 400 };
+
+  constructor(props) {
+    super(props);
+  }
+
+  componentWillMount() {
+  }
+
+  render() {
+    if (this.props.pages === undefined || this.props.pages.length == 0) {
+      return <div></div>
+    }
+
+    const listView = this.props.pages.map((page) => {
+      return (
+        <li key={page._id}>{page.path}</li>
+      );
+    });
+
+    return (
+      <Modal show={this.props.isShown} onHide={this.props.cancel} className="page-list-delete-modal">
+        <Modal.Header closeButton>
+          <Modal.Title>Deleting pages:</Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+          <ul>
+            {listView}
+          </ul>
+        </Modal.Body>
+        <Modal.Footer>
+          <span className="text-danger">{this.props.errorMessage}</span>&nbsp;
+          <Button onClick={this.props.cancel}>Cancel</Button>
+          <Button onClick={this.props.confirmedToDelete} className="btn-danger">Delete</Button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+
+}
+
+DeletePageListModal.propTypes = {
+  isShown: PropTypes.bool.isRequired,
+  pages: PropTypes.array,
+  errorMessage: PropTypes.string,
+  cancel: PropTypes.func.isRequired,            // for cancel evnet handling
+  confirmedToDelete: PropTypes.func.isRequired, // for confirmed event handling
+};

+ 130 - 3
resource/js/components/SearchPage/SearchResult.js

@@ -3,9 +3,21 @@ import PropTypes from 'prop-types';
 
 import Page from '../PageList/Page';
 import SearchResultList from './SearchResultList';
+import DeletePageListModal from './DeletePageListModal';
 
 // Search.SearchResult
 export default class SearchResult extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      deletionMode : false,
+      selectedPages : new Set(),
+      isDeleteConfirmModalShown: false,
+      errorMessageForDeleting: undefined,
+    }
+    this.deleteSelectedPages = this.deleteSelectedPages.bind(this);
+    this.closeDeleteConfirmModal = this.closeDeleteConfirmModal.bind(this);
+  }
 
   isNotSearchedYet() {
     return !this.props.searchResultMeta.took;
@@ -22,6 +34,84 @@ export default class SearchResult extends React.Component {
     return false;
   }
 
+  /**
+   * toggle checkbox and add (or delete from) selected pages list
+   *
+   * @param {any} page
+   * @memberof SearchResult
+   */
+  toggleCheckbox(page) {
+    if (this.state.selectedPages.has(page)) {
+      this.state.selectedPages.delete(page);
+    } else {
+      this.state.selectedPages.add(page);
+    }
+    this.setState({isDeleteConfirmModalShown: false});
+    this.setState({selectedPages: this.state.selectedPages});
+  }
+
+  /**
+   * change deletion mode
+   *
+   * @memberof SearchResult
+   */
+  handleDeletionModeChange() {
+    this.state.selectedPages.clear();
+    this.setState({deletionMode: !this.state.deletionMode});
+  }
+
+  /**
+   * delete selected pages
+   *
+   * @memberof SearchResult
+   */
+  deleteSelectedPages() {
+    let isDeleteComplete = true;
+    Array.from(this.state.selectedPages).map((page) => {
+      const pageId = page._id;
+      const revisionId = page.revision._id;
+      this.props.crowi.apiPost('/pages.remove',
+        {page_id: pageId, revision_id: revisionId})
+      .then(res => {
+        if (res.ok) {
+          this.state.selectedPages.delete(page);
+        }
+        else {
+          isDeleteComplete = false;
+        }
+      }).catch(err => {
+        console.log(err.message);
+        isDeleteComplete = false;
+        this.setState({errorMessageForDeleting: err.message});
+      });
+    });
+
+    if ( isDeleteComplete ) {
+      window.location.reload();
+    }
+  }
+
+  /**
+   * open confirm modal for page selection delete
+   *
+   * @memberof SearchResult
+   */
+  showDeleteConfirmModal() {
+    this.setState({isDeleteConfirmModalShown: true});
+  }
+
+  /**
+   * close confirm modal for page selection delete
+   *
+   * @memberof SearchResult
+   */
+  closeDeleteConfirmModal() {
+    this.setState({
+      isDeleteConfirmModalShown: false,
+      errorMessageForDeleting: undefined,
+    });
+  }
+
   render() {
     const excludePathString = this.props.tree;
 
@@ -52,6 +142,24 @@ export default class SearchResult extends React.Component {
 
     }
 
+    let deletionModeButtons = '';
+
+    if (this.state.deletionMode) {
+      deletionModeButtons =
+      <div className="btn-group">
+        <button type="button" className="btn btn-danger btn-xs" onClick={() => this.showDeleteConfirmModal()} disabled={this.state.selectedPages.size == 0}><i className="fa fa-trash-o"/> Delete</button>
+        <button type="button" className="btn btn-default btn-xs" onClick={() => this.handleDeletionModeChange()}><i className="fa fa-undo"/> Cancel</button>
+      </div>
+    }
+    else {
+      deletionModeButtons =
+      <div className="btn-group">
+        <button type="button" className="btn btn-default btn-xs" onClick={() => this.handleDeletionModeChange()}>
+          <i className="fa fa-toggle-off"/> DeletionMode
+        </button>
+      </div>
+    }
+
     const listView = this.props.pages.map((page) => {
       const pageId = "#" + page._id;
       return (
@@ -60,8 +168,14 @@ export default class SearchResult extends React.Component {
           key={page._id}
           excludePathString={excludePathString}
           >
+          { this.state.deletionMode &&
+            <input type="checkbox" className="search-result-list-delete-checkbox"
+              value={pageId}
+              checked={this.state.selectedPages.has(page)}
+              onClick={() => this.toggleCheckbox(page)} />
+            }
           <div className="page-list-option">
-            <a href={page.path}><i className="fa fa-arrow-circle-right" /></a>
+            <a href={page.path}><i className="fa fa-sign-in" /></a>
           </div>
         </Page>
       );
@@ -81,20 +195,32 @@ export default class SearchResult extends React.Component {
         <div className="search-result row" id="search-result">
           <div className="col-md-4 hidden-xs hidden-sm page-list search-result-list" id="search-result-list">
             <nav data-spy="affix" data-offset-top="120">
+              <div className="pull-right">{deletionModeButtons}</div>
+              <div className="search-result-meta">
+                <i className="fa fa-lightbulb-o" /> Found {this.props.searchResultMeta.total} pages with "{this.props.searchingKeyword}"
+              </div>
+              <div className="clearfix"></div>
               <ul className="page-list-ul page-list-ul-flat nav">
                 {listView}
               </ul>
             </nav>
           </div>
           <div className="col-md-8 search-result-content" id="search-result-content">
-            <div className="search-result-meta"><i className="fa fa-lightbulb-o" /> Found {this.props.searchResultMeta.total} pages with "{this.props.searchingKeyword}"</div>
             <SearchResultList
               pages={this.props.pages}
               searchingKeyword={this.props.searchingKeyword}
               />
           </div>
         </div>
-      </div>
+        <DeletePageListModal
+          isShown={this.state.isDeleteConfirmModalShown}
+          pages={Array.from(this.state.selectedPages)}
+          errorMessage={this.state.errorMessageForDeleting}
+          cancel={this.closeDeleteConfirmModal}
+          confirmedToDelete={this.deleteSelectedPages}
+        />
+
+      </div>//content-main
     );
   }
 }
@@ -104,6 +230,7 @@ SearchResult.propTypes = {
   pages: PropTypes.array.isRequired,
   searchingKeyword: PropTypes.string.isRequired,
   searchResultMeta: PropTypes.object.isRequired,
+  crowi: PropTypes.object.isRequired,
 };
 SearchResult.defaultProps = {
   tree: '',

+ 133 - 17
yarn.lock

@@ -484,6 +484,10 @@ async@~0.2.6:
   version "0.2.10"
   resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
 
+async@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
+
 async@~1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0"
@@ -509,7 +513,7 @@ autoprefixer@^6.3.1:
     postcss "^5.2.16"
     postcss-value-parser "^3.2.3"
 
-aws-sdk@^2.2.36, aws-sdk@^2.80.0:
+aws-sdk@^2.2.36:
   version "2.113.0"
   resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.113.0.tgz#8fb95f4654e851c761603157c7cf7a431af6fd6d"
   dependencies:
@@ -524,6 +528,21 @@ aws-sdk@^2.2.36, aws-sdk@^2.80.0:
     xml2js "0.4.17"
     xmlbuilder "4.2.1"
 
+aws-sdk@^2.80.0:
+  version "2.121.0"
+  resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.121.0.tgz#9994b1497dfc0cf882571e78401839cb0ad416de"
+  dependencies:
+    buffer "4.9.1"
+    crypto-browserify "1.0.9"
+    events "^1.1.1"
+    jmespath "0.15.0"
+    querystring "0.2.0"
+    sax "1.2.1"
+    url "0.10.3"
+    uuid "3.0.1"
+    xml2js "0.4.17"
+    xmlbuilder "4.2.1"
+
 aws-sign2@~0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@@ -1232,8 +1251,8 @@ botkit-studio-sdk@^1.0.2:
     request "^2.67.0"
 
 botkit@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/botkit/-/botkit-0.6.0.tgz#22c69d480cfc63a40df90acb119cc98d7f8e01d6"
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/botkit/-/botkit-0.6.1.tgz#d4a4f0dbe0840d0cb7cc29ecd84709b78aef8881"
   dependencies:
     async "^2.1.5"
     back "^1.0.1"
@@ -1371,6 +1390,21 @@ buffer@4.9.1, buffer@^4.3.0:
     ieee754 "^1.1.4"
     isarray "^1.0.0"
 
+build@^0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/build/-/build-0.1.4.tgz#707fe026ffceddcacbfdcdf356eafda64f151046"
+  dependencies:
+    cssmin "0.3.x"
+    jsmin "1.x"
+    jxLoader "*"
+    moo-server "*"
+    promised-io "*"
+    timespan "2.x"
+    uglify-js "1.x"
+    walker "1.x"
+    winston "*"
+    wrench "1.3.x"
+
 builtin-modules@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@@ -1673,7 +1707,7 @@ colormin@^1.0.5:
     css-color-names "0.0.4"
     has "^1.0.1"
 
-colors@1.0.3:
+colors@1.0.3, colors@1.0.x:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
 
@@ -1964,6 +1998,10 @@ cssesc@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
 
+cssmin@0.3.x:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/cssmin/-/cssmin-0.3.2.tgz#ddce4c547b510ae0d594a8f1fbf8aaf8e2c5c00d"
+
 "cssnano@>=2.6.1 <4":
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
@@ -2014,6 +2052,10 @@ currently-unhandled@^0.4.1:
   dependencies:
     array-find-index "^1.0.1"
 
+cycle@1.0.x:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
+
 d@1:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
@@ -2313,9 +2355,9 @@ enhanced-resolve@^3.4.0:
     object-assign "^4.0.1"
     tapable "^0.2.7"
 
-env-cmd@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/env-cmd/-/env-cmd-5.1.0.tgz#0236db393c3f033005204fcd0a92ee40723a9c9e"
+env-cmd@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/env-cmd/-/env-cmd-6.0.0.tgz#a8664ab1f0f18d5a9faa7cb00a8ea0985ed21f8a"
   dependencies:
     cross-spawn "^5.0.1"
 
@@ -2603,6 +2645,10 @@ extsprintf@1.3.0, extsprintf@^1.2.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
 
+eyes@0.1.x:
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
+
 fast-deep-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
@@ -2955,9 +3001,9 @@ google-p12-pem@^0.1.0:
   dependencies:
     node-forge "^0.7.1"
 
-googleapis@^21.3.0:
-  version "21.3.0"
-  resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-21.3.0.tgz#043d0276574eb7930a9fa731768d5571eeac11ee"
+googleapis@^22.0.0:
+  version "22.0.0"
+  resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-22.0.0.tgz#79ce0401646bf540728bf2204d79c669b38bfd5b"
   dependencies:
     async "~2.3.0"
     google-auth-library "~0.10.0"
@@ -3435,7 +3481,7 @@ isomorphic-fetch@^2.1.1:
     node-fetch "^1.0.1"
     whatwg-fetch ">=0.10.0"
 
-isstream@~0.1.2:
+isstream@0.1.x, isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
 
@@ -3473,6 +3519,10 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
 
+js-yaml@0.3.x:
+  version "0.3.7"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-0.3.7.tgz#d739d8ee86461e54b354d6a7d7d1f2ad9a167f62"
+
 js-yaml@3.5.4:
   version "3.5.4"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.5.4.tgz#f64f16dcd78beb9ce8361068e733ebe47b079179"
@@ -3499,6 +3549,10 @@ jsesc@~0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
 
+jsmin@1.x:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/jsmin/-/jsmin-1.0.1.tgz#e7bd0dcd6496c3bf4863235bf461a3d98aa3b98c"
+
 json-loader@^0.5.4:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
@@ -3604,6 +3658,15 @@ jws@^3.0.0, jws@^3.1.4:
     jwa "^1.1.4"
     safe-buffer "^5.0.1"
 
+jxLoader@*:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/jxLoader/-/jxLoader-0.1.1.tgz#0134ea5144e533b594fc1ff25ff194e235c53ecd"
+  dependencies:
+    js-yaml "0.3.x"
+    moo-server "1.3.x"
+    promised-io "*"
+    walker "1.x"
+
 kareem@1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/kareem/-/kareem-1.5.0.tgz#e3e4101d9dcfde299769daf4b4db64d895d17448"
@@ -3968,6 +4031,12 @@ make-dir@^1.0.0:
   dependencies:
     pify "^2.3.0"
 
+makeerror@1.0.x:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
+  dependencies:
+    tmpl "1.0.x"
+
 map-obj@^1.0.0, map-obj@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
@@ -4197,8 +4266,8 @@ mongoose-paginate@5.0.x:
     bluebird "3.0.5"
 
 mongoose@^4.11.1:
-  version "4.11.11"
-  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.11.11.tgz#ae9baf860241e086c90b226fdda52f1afddc6a7b"
+  version "4.11.12"
+  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.11.12.tgz#48ebd5cad051f6ddfd46648b86a19c7fd30e36db"
   dependencies:
     async "2.1.4"
     bson "~1.0.4"
@@ -4213,6 +4282,10 @@ mongoose@^4.11.1:
     regexp-clone "0.0.1"
     sliced "1.0.1"
 
+moo-server@*, moo-server@1.3.x:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/moo-server/-/moo-server-1.3.0.tgz#5dc79569565a10d6efed5439491e69d2392e58f1"
+
 morgan@^1.8.2:
   version "1.8.2"
   resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.8.2.tgz#784ac7734e4a453a9c6e6e8680a9329275c8b687"
@@ -5172,6 +5245,10 @@ promise@^8.0.0:
   dependencies:
     asap "~2.0.3"
 
+promised-io@*:
+  version "0.3.5"
+  resolved "https://registry.yarnpkg.com/promised-io/-/promised-io-0.3.5.tgz#4ad217bb3658bcaae9946b17a8668ecd851e1356"
+
 prop-types-extra@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.0.1.tgz#a57bd4810e82d27a3ff4317ecc1b4ad005f79a82"
@@ -5912,11 +5989,13 @@ sinon-chai@^2.12.0:
   resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.13.0.tgz#b9a42e801c20234bfc2f43b29e6f4f61b60990c4"
 
 sinon@^3.0.0:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/sinon/-/sinon-3.2.1.tgz#d8adabd900730fd497788a027049c64b08be91c2"
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/sinon/-/sinon-3.3.0.tgz#9132111b4bbe13c749c2848210864250165069b1"
   dependencies:
+    build "^0.1.4"
     diff "^3.1.0"
     formatio "1.2.0"
+    lodash.get "^4.4.2"
     lolex "^2.1.2"
     native-promise-only "^0.8.1"
     nise "^1.0.1"
@@ -6057,6 +6136,10 @@ sshpk@^1.7.0:
     jsbn "~0.1.0"
     tweetnacl "~0.14.0"
 
+stack-trace@0.0.x:
+  version "0.0.10"
+  resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
+
 "statuses@>= 1.3.1 < 2", statuses@~1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
@@ -6303,6 +6386,10 @@ timers-browserify@^2.0.2:
   dependencies:
     setimmediate "^1.0.4"
 
+timespan@2.x:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/timespan/-/timespan-2.3.0.tgz#4902ce040bd13d845c8f59b27e9d59bad6f39929"
+
 tiny-emitter@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
@@ -6311,6 +6398,10 @@ tinycolor@0.x:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/tinycolor/-/tinycolor-0.0.1.tgz#320b5a52d83abb5978d81a3e887d4aefb15a6164"
 
+tmpl@1.0.x:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
+
 to-array@0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
@@ -6408,6 +6499,10 @@ ua-parser-js@^0.7.9:
   version "0.7.14"
   resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.14.tgz#110d53fa4c3f326c121292bbeac904d2e03387ca"
 
+uglify-js@1.x:
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-1.3.5.tgz#4b5bfff9186effbaa888e4c9e94bd9fc4c94929d"
+
 uglify-js@2.6.0:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.6.0.tgz#25eaa1cc3550e39410ceefafd1cfbb6b6d15f001"
@@ -6583,6 +6678,12 @@ vm-browserify@0.0.4:
   dependencies:
     indexof "0.0.1"
 
+walker@1.x:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
+  dependencies:
+    makeerror "1.0.x"
+
 ware@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/ware/-/ware-1.3.0.tgz#d1b14f39d2e2cb4ab8c4098f756fe4b164e473d4"
@@ -6631,8 +6732,8 @@ webpack-sources@^1.0.1:
     source-map "~0.5.3"
 
 webpack@^3.1.0:
-  version "3.5.6"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.5.6.tgz#a492fb6c1ed7f573816f90e00c8fbb5a20cc5c36"
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.6.0.tgz#a89a929fbee205d35a4fa2cc487be9cbec8898bc"
   dependencies:
     acorn "^5.0.0"
     acorn-dynamic-import "^2.0.0"
@@ -6714,6 +6815,17 @@ window-size@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
 
+winston@*:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/winston/-/winston-2.3.1.tgz#0b48420d978c01804cf0230b648861598225a119"
+  dependencies:
+    async "~1.0.0"
+    colors "1.0.x"
+    cycle "1.0.x"
+    eyes "0.1.x"
+    isstream "0.1.x"
+    stack-trace "0.0.x"
+
 wordwrap@0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
@@ -6739,6 +6851,10 @@ wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
+wrench@1.3.x:
+  version "1.3.9"
+  resolved "https://registry.yarnpkg.com/wrench/-/wrench-1.3.9.tgz#6f13ec35145317eb292ca5f6531391b244111411"
+
 ws@0.4.20:
   version "0.4.20"
   resolved "https://registry.yarnpkg.com/ws/-/ws-0.4.20.tgz#f44b63f46b9edfc457309c720bcc0f83f2fc5874"