Przeglądaj źródła

Merge remote-tracking branch 'origin/master' into feat/integrate-with-hackmd

Yuki Takei 7 lat temu
rodzic
commit
cd4ed6092f

+ 1 - 0
.eslintrc.js

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

+ 5 - 0
CHANGES.md

@@ -3,6 +3,11 @@ CHANGES
 
 ## 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

+ 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);
         }
 

+ 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) {