Просмотр исходного кода

Merge pull request #483 from weseek/master

release v3.1.7
Yuki Takei 7 лет назад
Родитель
Сommit
297e271af2

+ 1 - 0
.eslintrc.js

@@ -17,6 +17,7 @@ module.exports = {
     "window": true
   },
   "parserOptions": {
+    "ecmaVersion": 8,
     "ecmaFeatures": {
       "experimentalObjectRestSpread": true,
       "jsx": true

+ 12 - 1
CHANGES.md

@@ -1,7 +1,18 @@
 CHANGES
 ========
 
-## 3.1.6-RC
+## 3.1.7-RC
+
+* Fix: Update hidden input 'pageForm[grant]' when save with Ctrl-S
+* Fix: Show alert message when conflict
+* Fix: `BLOCKDIAG_URI` environment variable doesn't work
+* Fix: Paste in markdown list doesn't work correctly
+* Support: Ensure to inject logger configuration from environment variables
+* Support: Upgrade libs
+    * sinon
+    * sinon-chai
+
+## 3.1.6
 
 * Feature: Support [blockdiag](http://blockdiag.com)
 * Feature: Add `BLOCKDIAG_URI` environment variable

+ 2 - 3
README.md

@@ -105,11 +105,10 @@ MONGO_URI=mongodb://MONGO_HOST:MONGO_PORT/growi npm start
 
 **DO NOT USE `npm install`**, use `yarn` instead.
 
-If you launch growi with Redis and ElasticSearch, add environment variables before `npm start` like following:
+If you launch growi with ElasticSearch, add environment variables before `npm start` like following:
 
 ```
 export MONGO_URI=mongodb://MONGO_HOST:MONGO_PORT/growi
-export REDIS_URL=redis://REDIS_HOST:REDIS_PORT/growi
 export ELASTICSEARCH_URI=http://ELASTICSEARCH_HOST:ELASTICSEARCH_PORT/growi
 npm start
 ```
@@ -158,8 +157,8 @@ Environment Variables
 * **Option**
     * NODE_ENV: `production` OR `development`.
     * PORT: Server port. default: `3000`
-    * REDIS_URL: URI to connect to Redis (to session store).
     * ELASTICSEARCH_URI: URI to connect to Elasticearch.
+    * REDIS_URI: URI to connect to Redis (to session store).
     * PLANTUML_URI: URI to connect to [PlantUML](http://plantuml.com/) server.
     * BLOCKDIAG_URI: URI to connect to [blockdiag](http://http://blockdiag.com/) server.
     * PASSWORD_SEED: A password seed used by password hash generator.

+ 1 - 1
lib/crowi/index.js

@@ -147,7 +147,7 @@ Crowi.prototype.setupSessionConfig = function() {
     , session  = require('express-session')
     , sessionConfig
     , sessionAge = (1000*3600*24*30)
-    , redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URL || null
+    , redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URI || this.env.REDIS_URL || null
     , mongoUrl = getMongoUrl(this.env)
     ;
 

+ 10 - 19
lib/models/page.js

@@ -1053,32 +1053,23 @@ module.exports = function(crowi) {
       });
   };
 
-  pageSchema.statics.updatePage = function(pageData, body, user, options) {
+  pageSchema.statics.updatePage = async function(pageData, body, user, options) {
     var Page = this
       , Revision = crowi.model('Revision')
       , grant = options.grant || null
       , grantUserGroupId = options.grantUserGroupId || null
       ;
     // update existing page
-    var newRevision = Revision.prepareRevision(pageData, body, user);
+    var newRevision = await Revision.prepareRevision(pageData, body, user);
 
-    let savedPage = undefined;
-    return Page.pushRevision(pageData, newRevision, user)
-      .then((revision) => {
-        // fetch Page
-        return Page.findPageByPath(revision.path).populate('revision');
-      })
-      .then((page) => {
-        savedPage = page;
-      })
-      .then(() => {
-        return Page.updateGrant(savedPage, grant, user, grantUserGroupId);
-      })
-      .then((data) => {
-        debug('Page grant update:', data);
-        pageEvent.emit('update', savedPage, user);
-        return savedPage;
-      });
+    const revision = await Page.pushRevision(pageData, newRevision, user);
+    const savedPage = await Page.findPageByPath(revision.path).populate('revision');
+    if (grant != null) {
+      const grantData = await Page.updateGrant(savedPage, grant, user, grantUserGroupId);
+      debug('Page grant update:', grantData);
+    }
+    pageEvent.emit('update', savedPage, user);
+    return savedPage;
   };
 
   pageSchema.statics.deletePage = function(pageData, user, options) {

+ 9 - 9
lib/routes/attachment.js

@@ -1,11 +1,11 @@
 module.exports = function(crowi, app) {
   'use strict';
 
-  var debug = require('debug')('growi:routs:attachment')
+  var debug = require('debug')('growi:routss:attachment')
+    , logger = require('@alias/logger')('growi:routes:attachment')
     , Attachment = crowi.model('Attachment')
     , User = crowi.model('User')
     , Page = crowi.model('Page')
-    , config = crowi.getConfig()
     , path = require('path')
     , fs = require('fs')
     , fileUploader = require('../util/fileUploader')(crowi, app)
@@ -42,7 +42,7 @@ module.exports = function(crowi, app) {
       })
       // not found
       .catch((err) => {
-        debug('download err', err);
+        logger.error('download err', err);
         return res.status(404).sendFile(crowi.publicDir + '/images/file-not-found.png');
       });
   };
@@ -140,7 +140,6 @@ module.exports = function(crowi, app) {
           return Attachment.create(id, req.user, filePath, originalName, fileName, fileType, fileSize);
         }).then(function(data) {
           var fileUrl = data.fileUrl;
-          var config = crowi.getConfig();
 
           var result = {
             page: page.toObject(),
@@ -157,18 +156,18 @@ module.exports = function(crowi, app) {
 
           return res.json(ApiResponse.success(result));
         }).catch(function(err) {
-          debug('Error on saving attachment data', err);
+          logger.error('Error on saving attachment data', err);
           // @TODO
           // Remove from S3
 
           // delete anyway
-          fs.unlink(tmpPath, function(err) { if (err) { debug('Error while deleting tmp file.') } });
+          fs.unlink(tmpPath, function(err) { if (err) { logger.error('Error while deleting tmp file.') } });
 
           return res.json(ApiResponse.error('Error while uploading.'));
         });
-      
+
     }).catch(function(err) {
-      debug('Attachement upload error', err);
+      logger.error('Attachement upload error', err);
       return res.json(ApiResponse.error('Error.'));
     });
   };
@@ -192,10 +191,11 @@ module.exports = function(crowi, app) {
         debug('removeAttachment', data);
         return res.json(ApiResponse.success({}));
       }).catch(err => {
+        logger.error('Error', err);
         return res.status(500).json(ApiResponse.error('Error while deleting file'));
       });
     }).catch(err => {
-      debug('Error', err);
+      logger.error('Error', err);
       return res.status(404);
     });
   };

+ 4 - 9
lib/routes/page.js

@@ -643,15 +643,10 @@ module.exports = function(crowi, app) {
     .then(function(data) {
       pageData = data;
 
-      if (!req.form.isValid) {
-        debug('Form data not valid');
-        throw new Error('Form data not valid.');
-      }
-
       if (data && !data.isUpdatable(currentRevision)) {
         debug('Conflict occured');
-        req.form.errors.push('page_edit.notice.conflict');
-        throw new Error('Conflict.');
+        req.flash('dangerMessage', 'Conflict occured');
+        return res.redirect(req.headers.referer);
       }
 
       if (data) {
@@ -839,8 +834,8 @@ module.exports = function(crowi, app) {
         throw new Error('Revision error.');
       }
 
-      var grantOption = {grant: pageData.grant};
-      if (grant !== null) {
+      var grantOption = {};
+      if (grant != null) {
         grantOption.grant = grant;
       }
       if (grantUserGroupId != null) {

+ 21 - 2
lib/service/logger/index.js

@@ -4,13 +4,32 @@ const minimatch = require('minimatch');
 const isBrowser = typeof window !== 'undefined';
 const isProd = process.env.NODE_ENV === 'production';
 
-const config = require('@root/config').logger;
-
+let config = require('@root/config').logger;
 let stream = isProd ? require('./stream.prod') : require('./stream.dev');
 
 // logger store
 let loggers = {};
 
+
+// merge configuration from environment variables
+const envLevelMap = {
+  INFO:   'info',
+  DEBUG:  'debug',
+  WARN:   'warn',
+  TRACE:  'trace',
+  ERROR:  'error',
+};
+Object.keys(envLevelMap).forEach(envName => {   // ['INFO', 'DEBUG', ...].forEach
+  const envVars = process.env[envName];         // process.env.DEBUG should have a value like 'growi:routes:page,growi:models.page,...'
+  if (envVars != null) {
+    const level = envLevelMap[envName];
+    envVars.split(',').forEach(ns => {          // ['growi:routes:page', 'growi:models.page', ...].forEach
+      config[ns.trim()] = level;
+    });
+  }
+});
+
+
 /**
  * determine logger level
  * @param {string} name Logger name

+ 0 - 3
local_modules/crowi-fileupload-local/index.js

@@ -7,8 +7,6 @@ module.exports = function(crowi) {
     , fs = require('fs')
     , path = require('path')
     , mkdir = require('mkdirp')
-    , Config = crowi.model('Config')
-    , config = crowi.getConfig()
     , lib = {}
     , basePath = path.posix.join(crowi.publicDir, 'uploads'); // TODO: to configurable
 
@@ -17,7 +15,6 @@ module.exports = function(crowi) {
     return new Promise(function(resolve, reject) {
       fs.unlink(path.posix.join(basePath, filePath), function(err) {
         if (err) {
-          debug(err);
           return reject(err);
         }
 

+ 3 - 3
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "3.1.6-RC",
+  "version": "3.1.7-RC",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -171,8 +171,8 @@
     "reveal.js": "^3.5.0",
     "sass-loader": "^7.0.1",
     "simple-load-script": "^1.0.2",
-    "sinon": "^5.0.2",
-    "sinon-chai": "^3.0.0",
+    "sinon": "^6.0.0",
+    "sinon-chai": "^3.2.0",
     "socket.io-client": "^2.0.3",
     "style-loader": "^0.21.0",
     "throttle-debounce": "^2.0.0",

+ 1 - 1
resource/js/app.js

@@ -160,7 +160,7 @@ if (pageEditorElem) {
   // create onSave event handler
   const onSaveSuccess = function(page) {
     // modify the revision id value to pass checking id when updating
-    crowi.getCrowiForJquery().updateCurrentRevision(page.revision._id);
+    crowi.getCrowiForJquery().updatePageForm(page);
     // re-render Page component if exists
     if (componentInstances.page != null) {
       componentInstances.page.setMarkdown(page.revision.body);

+ 2 - 1
resource/js/components/Page/RevisionBody.js

@@ -43,6 +43,7 @@ export default class RevisionBody extends React.Component {
   }
 
   render() {
+    const additionalClassName = this.props.additionalClassName || '';
     return (
       <div
         ref={(elm) => {
@@ -51,7 +52,7 @@ export default class RevisionBody extends React.Component {
             this.props.inputRef(elm);
           }
         }}
-        className={'wiki ' + this.props.additionalClassName} dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}>
+        className={`wiki ${additionalClassName}`} dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}>
       </div>
     );
   }

+ 0 - 1
resource/js/components/PageEditor.js

@@ -311,7 +311,6 @@ export default class PageEditor extends React.Component {
   }
 
   apiErrorHandler(error) {
-    console.error(error);
     toastr.error(error.message, 'Error occured', {
       closeButton: true,
       progressBar: true,

+ 7 - 0
resource/js/components/PageEditor/AbstractEditor.js

@@ -49,6 +49,13 @@ export default class AbstractEditor extends React.Component {
     throw new Error('this method should be impelemented in subclass');
   }
 
+  /**
+   * return strings from BOL(beginning of line) to current position
+   */
+  getStrFromBolToSelectedUpperPos() {
+    throw new Error('this method should be impelemented in subclass');
+  }
+
   /**
    * replace Beggining Of Line to current position with param 'text'
    * @param {string} text

+ 37 - 1
resource/js/components/PageEditor/CodeMirrorEditor.js

@@ -176,12 +176,22 @@ export default class CodeMirrorEditor extends AbstractEditor {
     return editor.getDoc().getRange(curPos, this.getEol());
   }
 
+  /**
+   * @inheritDoc
+   */
+  getStrFromBolToSelectedUpperPos() {
+    const editor = this.getCodeMirror();
+    const pos = this.selectUpperPos(editor.getCursor('from'), editor.getCursor('to'));
+    return editor.getDoc().getRange(this.getBol(), pos);
+  }
+
   /**
    * @inheritDoc
    */
   replaceBolToCurrentPos(text) {
     const editor = this.getCodeMirror();
-    editor.getDoc().replaceRange(text, this.getBol(), editor.getCursor());
+    const pos = this.selectLowerPos(editor.getCursor('from'), editor.getCursor('to'));
+    editor.getDoc().replaceRange(text, this.getBol(), pos);
   }
 
   /**
@@ -211,6 +221,32 @@ export default class CodeMirrorEditor extends AbstractEditor {
     return { line: curPos.line, ch: lineLength };
   }
 
+  /**
+   * select the upper position of pos1 and pos2
+   * @param {{line: number, ch: number}} pos1
+   * @param {{line: number, ch: number}} pos2
+   */
+  selectUpperPos(pos1, pos2) {
+    // if both is in same line
+    if (pos1.line === pos2.line) {
+      return (pos1.ch < pos2.ch) ? pos1 : pos2;
+    }
+    return (pos1.line < pos2.line) ? pos1 : pos2;
+  }
+
+  /**
+   * select the lower position of pos1 and pos2
+   * @param {{line: number, ch: number}} pos1
+   * @param {{line: number, ch: number}} pos2
+   */
+  selectLowerPos(pos1, pos2) {
+    // if both is in same line
+    if (pos1.line === pos2.line) {
+      return (pos1.ch < pos2.ch) ? pos2 : pos1;
+    }
+    return (pos1.line < pos2.line) ? pos2 : pos1;
+  }
+
   loadCss(source) {
     return new Promise((resolve) => {
       loadCssSync(source);

+ 1 - 1
resource/js/components/PageEditor/MarkdownListUtil.js

@@ -42,7 +42,7 @@ class MarkdownListUtil {
    */
   pasteText(editor, event, text) {
     // get strings from BOL(beginning of line) to current position
-    const strFromBol = editor.getStrFromBol();
+    const strFromBol = editor.getStrFromBolToSelectedUpperPos();
 
     // when match indentAndMarkOnlyRE
     // (this means the current position is the beginning of the list item)

+ 14 - 2
resource/js/components/PageEditor/TextAreaEditor.js

@@ -109,12 +109,24 @@ export default class TextAreaEditor extends AbstractEditor {
     return this.textarea.value.substring(currentPos, this.getEolPos());
   }
 
+  /**
+   * @inheritDoc
+   */
+  getStrFromBolToSelectedUpperPos() {
+    const startPos = this.textarea.selectionStart;
+    const endPos = this.textarea.selectionEnd;
+    const upperPos = (startPos < endPos) ? startPos : endPos;
+    return this.textarea.value.substring(this.getBolPos(), upperPos);
+  }
+
   /**
    * @inheritDoc
    */
   replaceBolToCurrentPos(text) {
-    const currentPos = this.textarea.selectionStart;
-    this.replaceValue(text, this.getBolPos(), currentPos);
+    const startPos = this.textarea.selectionStart;
+    const endPos = this.textarea.selectionEnd;
+    const lowerPos = (startPos < endPos) ? endPos : startPos;
+    this.replaceValue(text, this.getBolPos(), lowerPos);
   }
 
   getBolPos() {

+ 3 - 2
resource/js/legacy/crowi.js

@@ -137,8 +137,9 @@ Crowi.modifyScrollTop = function() {
   }, timeout);
 };
 
-Crowi.updateCurrentRevision = function(revisionId) {
-  $('#page-form [name="pageForm[currentRevision]"]').val(revisionId);
+Crowi.updatePageForm = function(page) {
+  $('#page-form [name="pageForm[currentRevision]"]').val(page.revision._id);
+  $('#page-form [name="pageForm[grant]"]').val(page.grant);
 };
 
 Crowi.handleKeyEHandler = (event) => {

+ 1 - 1
resource/js/util/markdown-it/blockdiag.js

@@ -4,7 +4,7 @@ export default class BlockdiagConfigurer {
     this.crowi = crowi;
     const config = crowi.getConfig();
 
-    this.generateSourceUrl = config.env.BLOCKDIAG_URL || 'https://blockdiag-api.com/';
+    this.generateSourceUrl = config.env.BLOCKDIAG_URI || 'https://blockdiag-api.com/';
   }
 
   configure(md) {

+ 34 - 34
yarn.lock

@@ -2238,7 +2238,7 @@ diff@3.3.1:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
 
-diff@^3.1.0, diff@^3.3.1:
+diff@^3.3.1:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
 
@@ -3022,12 +3022,6 @@ form-data@~2.3.1:
     combined-stream "^1.0.5"
     mime-types "^2.1.12"
 
-formatio@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
-  dependencies:
-    samsam "1.x"
-
 forwarded@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -3990,7 +3984,7 @@ jsx-ast-utils@^2.0.1:
   dependencies:
     array-includes "^3.0.3"
 
-just-extend@^1.1.26:
+just-extend@^1.1.27:
   version "1.1.27"
   resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
 
@@ -4303,13 +4297,9 @@ lodash@^4.17.10:
   version "4.17.10"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
 
-lolex@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
-
-lolex@^2.2.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.1.tgz#3d2319894471ea0950ef64692ead2a5318cff362"
+lolex@^2.3.2, lolex@^2.4.2:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.0.tgz#9c087a69ec440e39d3f796767cf1b2cdc43d5ea5"
 
 longest@^1.0.1:
   version "1.0.1"
@@ -4809,13 +4799,13 @@ nice-try@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
 
-nise@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53"
+nise@^1.3.3:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.1.tgz#78bc2b343d5ff1031ea9d1bb2c87a94c26db7250"
   dependencies:
-    formatio "^1.2.0"
-    just-extend "^1.1.26"
-    lolex "^1.6.0"
+    "@sinonjs/formatio" "^2.0.0"
+    just-extend "^1.1.27"
+    lolex "^2.3.2"
     path-to-regexp "^1.7.0"
     text-encoding "^0.6.4"
 
@@ -6480,7 +6470,7 @@ safe-json-stringify@~1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911"
 
-samsam@1.3.0, samsam@1.x:
+samsam@1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
 
@@ -6691,21 +6681,21 @@ simple-load-script@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/simple-load-script/-/simple-load-script-1.0.2.tgz#d92951fe7b601ad90af8c9429bd4b2ee127ab8a3"
 
-sinon-chai@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.0.0.tgz#d5cbd70fa71031edd96b528e0eed4038fcc99f29"
+sinon-chai@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.2.0.tgz#ed995e13a8a3cfccec18f218d9b767edc47e0715"
 
-sinon@^5.0.2:
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.0.2.tgz#1d54bd6fa3d736053333f02e6e11def4fe7a2a9c"
+sinon@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.0.0.tgz#f26627e4830dc34279661474da2c9e784f166215"
   dependencies:
     "@sinonjs/formatio" "^2.0.0"
-    diff "^3.1.0"
+    diff "^3.5.0"
     lodash.get "^4.4.2"
-    lolex "^2.2.0"
-    nise "^1.2.0"
-    supports-color "^5.1.0"
-    type-detect "^4.0.5"
+    lolex "^2.4.2"
+    nise "^1.3.3"
+    supports-color "^5.4.0"
+    type-detect "^4.0.8"
 
 slack-node@^0.1.8:
   version "0.1.8"
@@ -7030,6 +7020,12 @@ supports-color@^5.3.0:
   dependencies:
     has-flag "^3.0.0"
 
+supports-color@^5.4.0:
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
+  dependencies:
+    has-flag "^3.0.0"
+
 svgo@^0.7.0:
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
@@ -7194,10 +7190,14 @@ type-check@~0.3.2:
   dependencies:
     prelude-ls "~1.1.2"
 
-type-detect@^4.0.0, type-detect@^4.0.5:
+type-detect@^4.0.0:
   version "4.0.5"
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2"
 
+type-detect@^4.0.8:
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+
 type-is@^1.6.4, type-is@~1.6.15:
   version "1.6.15"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"