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

Merge commit '167f53f3c472964a9ef68b1f6dbcf22aee809902' into feat/limit-amount-of-gridfs-use

yusueketk 7 лет назад
Родитель
Сommit
2b3a2d65f0

+ 5 - 0
CHANGES.md

@@ -4,6 +4,11 @@ CHANGES
 ## 3.2.9-RC
 ## 3.2.9-RC
 
 
 * Feature: Attachment Storing to MongoDB GridFS
 * Feature: Attachment Storing to MongoDB GridFS
+* Fix: row/col moving of Spreadsheet like GUI (Handsontable) doesn't work
+* Fix: Emoji AutoComplete dialog pops up at wrong position
+* Support: Upgrade libs
+    * codemirror
+    * react-codemirror2
 
 
 ## 3.2.8
 ## 3.2.8
 
 

+ 6 - 1
bin/wercker/trigger-growi-docker.sh

@@ -9,17 +9,22 @@
 #   - $WERCKER_TOKEN
 #   - $WERCKER_TOKEN
 #   - $GROWI_DOCKER_PIPELINE_ID
 #   - $GROWI_DOCKER_PIPELINE_ID
 #   - $RELEASE_VERSION
 #   - $RELEASE_VERSION
+#   - $WERCKER_GIT_COMMIT
 #
 #
 RESPONSE=`curl -X POST \
 RESPONSE=`curl -X POST \
   -H "Content-Type: application/json" \
   -H "Content-Type: application/json" \
   -H "Authorization: Bearer $WERCKER_TOKEN" \
   -H "Authorization: Bearer $WERCKER_TOKEN" \
   https://app.wercker.com/api/v3/runs -d '{ \
   https://app.wercker.com/api/v3/runs -d '{ \
     "pipelineId": "'$GROWI_DOCKER_PIPELINE_ID'", \
     "pipelineId": "'$GROWI_DOCKER_PIPELINE_ID'", \
-    "branch": "release", \
+    "branch": "master", \
     "envVars": [ \
     "envVars": [ \
       { \
       { \
         "key": "RELEASE_VERSION", \
         "key": "RELEASE_VERSION", \
         "value": "'$RELEASE_VERSION'" \
         "value": "'$RELEASE_VERSION'" \
+      }, \
+      { \
+        "key": "GROWI_REPOS_GIT_COMMIT", \
+        "value": "'$WERCKER_GIT_COMMIT'" \
       } \
       } \
     ] \
     ] \
   }' \
   }' \

+ 1 - 0
config/webpack.common.js

@@ -77,6 +77,7 @@ module.exports = (options) => {
           exclude: {
           exclude: {
             test:    helpers.root('node_modules'),
             test:    helpers.root('node_modules'),
             exclude: [  // include as a result
             exclude: [  // include as a result
+              helpers.root('node_modules/codemirror/src'),
               helpers.root('node_modules/string-width'),
               helpers.root('node_modules/string-width'),
               helpers.root('node_modules/is-fullwidth-code-point'), // depends from string-width
               helpers.root('node_modules/is-fullwidth-code-point'), // depends from string-width
             ]
             ]

+ 2 - 2
package.json

@@ -140,7 +140,7 @@
     "bunyan-debug": "^2.0.0",
     "bunyan-debug": "^2.0.0",
     "chai": "^4.1.0",
     "chai": "^4.1.0",
     "cli": "~1.0.1",
     "cli": "~1.0.1",
-    "codemirror": "^5.37.0",
+    "codemirror": "^5.42.0",
     "colors": "^1.2.5",
     "colors": "^1.2.5",
     "commander": "^2.11.0",
     "commander": "^2.11.0",
     "connect-browser-sync": "^2.1.0",
     "connect-browser-sync": "^2.1.0",
@@ -188,7 +188,7 @@
     "react-bootstrap": "^0.32.1",
     "react-bootstrap": "^0.32.1",
     "react-bootstrap-typeahead": "^3.1.5",
     "react-bootstrap-typeahead": "^3.1.5",
     "react-clipboard.js": "^2.0.0",
     "react-clipboard.js": "^2.0.0",
-    "react-codemirror2": "^5.0.4",
+    "react-codemirror2": "^5.1.0",
     "react-dom": "^16.4.1",
     "react-dom": "^16.4.1",
     "react-dropzone": "^7.0.1",
     "react-dropzone": "^7.0.1",
     "react-frame-component": "^4.0.0",
     "react-frame-component": "^4.0.0",

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

@@ -96,7 +96,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
   }
   }
 
 
   init() {
   init() {
-    this.cmCdnRoot = 'https://cdn.jsdelivr.net/npm/codemirror@5.37.0';
+    this.cmCdnRoot = 'https://cdn.jsdelivr.net/npm/codemirror@5.42.0';
 
 
     this.interceptorManager = new InterceptorManager();
     this.interceptorManager = new InterceptorManager();
     this.interceptorManager.addInterceptors([
     this.interceptorManager.addInterceptors([

+ 14 - 0
src/client/js/components/PageEditor/EmojiAutoCompleteHelper.js

@@ -1,3 +1,5 @@
+import UpdateDisplayUtil from '../../util/codemirror/update-display-util.ext';
+
 class EmojiAutoCompleteHelper {
 class EmojiAutoCompleteHelper {
 
 
   constructor(emojiStrategy) {
   constructor(emojiStrategy) {
@@ -42,6 +44,18 @@ class EmojiAutoCompleteHelper {
       return;
       return;
     }
     }
 
 
+    /*
+     * https://github.com/weseek/growi/issues/703 is caused
+     * because 'editor.display.viewOffset' is zero
+     *
+     * call stack:
+     *   1. https://github.com/codemirror/CodeMirror/blob/5.42.0/addon/hint/show-hint.js#L220
+     *   2. https://github.com/codemirror/CodeMirror/blob/5.42.0/src/edit/methods.js#L189
+     *   3. https://github.com/codemirror/CodeMirror/blob/5.42.0/src/measurement/position_measurement.js#L372
+     *   4. https://github.com/codemirror/CodeMirror/blob/5.42.0/src/measurement/position_measurement.js#L315
+     */
+    UpdateDisplayUtil.forceUpdateViewOffset(editor);
+
     // see https://codemirror.net/doc/manual.html#addon_show-hint
     // see https://codemirror.net/doc/manual.html#addon_show-hint
     editor.showHint({
     editor.showHint({
       completeSingle: false,
       completeSingle: false,

+ 127 - 10
src/client/js/components/PageEditor/HandsontableModal.jsx

@@ -21,10 +21,21 @@ const MARKDOWNTABLE_TO_HANDSONTABLE_ALIGNMENT_SYMBOL_MAPPING = {
 
 
 export default class HandsontableModal extends React.PureComponent {
 export default class HandsontableModal extends React.PureComponent {
 
 
-
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
+    /*
+     * ## Note ##
+     * Currently, this component try to synchronize the cells data and alignment data of state.markdownTable with these of the HotTable.
+     * However, changes made by the following operations are not synchronized.
+     *
+     * 1. move columns: Alignment changes are synchronized but data changes are not.
+     * 2. move rows: Data changes are not synchronized.
+     * 3. insert columns or rows: Data changes are synchronized but alignment changes are not.
+     * 4. delete columns or rows: Data changes are synchronized but alignment changes are not.
+     *
+     * However, all operations are reflected in the data to be saved because the HotTable data is used when the save method is called.
+     */
     this.state = {
     this.state = {
       show: false,
       show: false,
       isDataImportAreaExpanded: false,
       isDataImportAreaExpanded: false,
@@ -39,10 +50,11 @@ export default class HandsontableModal extends React.PureComponent {
     this.cancel = this.cancel.bind(this);
     this.cancel = this.cancel.bind(this);
     this.save = this.save.bind(this);
     this.save = this.save.bind(this);
     this.afterLoadDataHandler = this.afterLoadDataHandler.bind(this);
     this.afterLoadDataHandler = this.afterLoadDataHandler.bind(this);
-    this.beforeColumnMoveHandler = this.beforeColumnMoveHandler.bind(this);
     this.beforeColumnResizeHandler = this.beforeColumnResizeHandler.bind(this);
     this.beforeColumnResizeHandler = this.beforeColumnResizeHandler.bind(this);
     this.afterColumnResizeHandler = this.afterColumnResizeHandler.bind(this);
     this.afterColumnResizeHandler = this.afterColumnResizeHandler.bind(this);
     this.modifyColWidthHandler = this.modifyColWidthHandler.bind(this);
     this.modifyColWidthHandler = this.modifyColWidthHandler.bind(this);
+    this.beforeColumnMoveHandler = this.beforeColumnMoveHandler.bind(this);
+    this.afterColumnMoveHandler = this.afterColumnMoveHandler.bind(this);
     this.synchronizeAlignment = this.synchronizeAlignment.bind(this);
     this.synchronizeAlignment = this.synchronizeAlignment.bind(this);
     this.alignButtonHandler = this.alignButtonHandler.bind(this);
     this.alignButtonHandler = this.alignButtonHandler.bind(this);
     this.toggleDataImportArea = this.toggleDataImportArea.bind(this);
     this.toggleDataImportArea = this.toggleDataImportArea.bind(this);
@@ -120,6 +132,13 @@ export default class HandsontableModal extends React.PureComponent {
     });
     });
   }
   }
 
 
+  /**
+   * Reset table data to initial value
+   *
+   * ## Note ##
+   * It may not return completely to the initial state because of the manualColumnMove operations.
+   * https://github.com/handsontable/handsontable/issues/5591
+   */
   reset() {
   reset() {
     this.setState({ markdownTable: this.state.markdownTableOnInit.clone() });
     this.setState({ markdownTable: this.state.markdownTableOnInit.clone() });
   }
   }
@@ -129,15 +148,39 @@ export default class HandsontableModal extends React.PureComponent {
   }
   }
 
 
   save() {
   save() {
+    const markdownTable = new MarkdownTable(
+      this.refs.hotTable.hotInstance.getData(),
+      {align: [].concat(this.state.markdownTable.options.align)}
+    ).normalizeCells();
+
     if (this.props.onSave != null) {
     if (this.props.onSave != null) {
-      this.props.onSave(this.state.markdownTable.clone().normalizeCells());
+      this.props.onSave(markdownTable);
     }
     }
 
 
     this.hide();
     this.hide();
   }
   }
 
 
+  /**
+   * An afterLoadData hook
+   *
+   * This performs the following operations.
+   * - clear 'manuallyResizedColumnIndicesSet' for the first loading
+   * - synchronize the handsontable alignment to the markdowntable alignment
+   *
+   * ## Note ##
+   * The afterLoadData hook is called when one of the following states of this component are passed into the setState.
+   *
+   * - markdownTable
+   * - handsontableHeight
+   *
+   * In detail, when the setState method is called with those state passed,
+   * React will start re-render process for the HotTable of this component because the HotTable receives those state values by props.
+   * HotTable#shouldComponentUpdate is called in this re-render process and calls the updateSettings method for the Handsontable instance.
+   * In updateSettings method, the loadData method is called in some case. (refs: https://github.com/handsontable/handsontable/blob/6.2.0/src/core.js#L1652-L1657)
+   * The updateSettings method calls in the HotTable always lead to call the loadData method because the HotTable passes data source by settings.data.
+   * After the loadData method is executed, afterLoadData hooks are called.
+   */
   afterLoadDataHandler(initialLoad) {
   afterLoadDataHandler(initialLoad) {
-    // clear 'manuallyResizedColumnIndicesSet' for the first loading
     if (initialLoad) {
     if (initialLoad) {
       this.manuallyResizedColumnIndicesSet.clear();
       this.manuallyResizedColumnIndicesSet.clear();
     }
     }
@@ -145,11 +188,6 @@ export default class HandsontableModal extends React.PureComponent {
     this.synchronizeAlignment();
     this.synchronizeAlignment();
   }
   }
 
 
-  beforeColumnMoveHandler(columns, target) {
-    // clear 'manuallyResizedColumnIndicesSet'
-    this.manuallyResizedColumnIndicesSet.clear();
-  }
-
   beforeColumnResizeHandler(currentColumn) {
   beforeColumnResizeHandler(currentColumn) {
     /*
     /*
      * The following bug disturbs to use 'beforeColumnResizeHandler' to store column index -- 2018.10.23 Yuki Takei
      * The following bug disturbs to use 'beforeColumnResizeHandler' to store column index -- 2018.10.23 Yuki Takei
@@ -186,6 +224,77 @@ export default class HandsontableModal extends React.PureComponent {
     return Math.max(80, Math.min(400, width));
     return Math.max(80, Math.min(400, width));
   }
   }
 
 
+  beforeColumnMoveHandler(columns, target) {
+    // clear 'manuallyResizedColumnIndicesSet'
+    this.manuallyResizedColumnIndicesSet.clear();
+  }
+
+  /**
+   * An afterColumnMove hook.
+   *
+   * This synchronizes alignment when columns are moved by manualColumnMove
+   */
+  afterColumnMoveHandler(columns, target) {
+    const align = [].concat(this.state.markdownTable.options.align);
+    const removed = align.splice(columns[0], columns.length);
+
+    /*
+     * The following is a description of the algorithm for the alignment synchronization.
+     *
+     * Consider the case where the target is X and the columns are [2,3] and data is as follows.
+     *
+     * 0 1 2 3 4 5 (insert position number)
+     * +-+-+-+-+-+
+     * | | | | | |
+     * +-+-+-+-+-+
+     *  0 1 2 3 4  (column index number)
+     *
+     * At first, remove columns by the splice.
+     *
+     * 0 1 2   4 5
+     * +-+-+   +-+
+     * | | |   | |
+     * +-+-+   +-+
+     *  0 1     4
+     *
+     * Next, insert those columns into a new position.
+     * However the target number is a insert position number before deletion, it may be changed.
+     * These are changed as follows.
+     *
+     * Before:
+     * 0 1 2   4 5
+     * +-+-+   +-+
+     * | | |   | |
+     * +-+-+   +-+
+     *
+     * After:
+     * 0 1 2   2 3
+     * +-+-+   +-+
+     * | | |   | |
+     * +-+-+   +-+
+     *
+     * If X is 0, 1 or 2, that is, lower than columns[0], the target number is not changed.
+     * If X is 4 or 5, that is, higher than columns[columns.length - 1], the target number is modified to the original value minus columns.length.
+     *
+     */
+    let insertPosition = 0;
+    if (target <= columns[0]) {
+      insertPosition = target;
+    }
+    else if (columns[columns.length - 1] < target) {
+      insertPosition = target - columns.length;
+    }
+    align.splice.apply(align, [insertPosition, 0].concat(removed));
+
+    this.setState((prevState) => {
+      // change only align info, so share table data to avoid redundant copy
+      const newMarkdownTable = new MarkdownTable(prevState.markdownTable.table, {align: align});
+      return { markdownTable: newMarkdownTable };
+    }, () => {
+      this.synchronizeAlignment();
+    });
+  }
+
   /**
   /**
    * change the markdownTable alignment and synchronize the handsontable alignment to it
    * change the markdownTable alignment and synchronize the handsontable alignment to it
    */
    */
@@ -244,6 +353,13 @@ export default class HandsontableModal extends React.PureComponent {
     this.setState({ isDataImportAreaExpanded: !this.state.isDataImportAreaExpanded });
     this.setState({ isDataImportAreaExpanded: !this.state.isDataImportAreaExpanded });
   }
   }
 
 
+  /**
+   * Import a markdowntable
+   *
+   * ## Note ##
+   * The manualColumnMove operation affects the column order of imported data.
+   * https://github.com/handsontable/handsontable/issues/5591
+   */
   importData(markdownTable) {
   importData(markdownTable) {
     this.init(markdownTable);
     this.init(markdownTable);
     this.toggleDataImportArea();
     this.toggleDataImportArea();
@@ -320,6 +436,7 @@ export default class HandsontableModal extends React.PureComponent {
                 beforeColumnMove={this.beforeColumnMoveHandler}
                 beforeColumnMove={this.beforeColumnMoveHandler}
                 beforeColumnResize={this.beforeColumnResizeHandler}
                 beforeColumnResize={this.beforeColumnResizeHandler}
                 afterColumnResize={this.afterColumnResizeHandler}
                 afterColumnResize={this.afterColumnResizeHandler}
+                afterColumnMove={this.afterColumnMoveHandler}
               />
               />
           </div>
           </div>
         </Modal.Body>
         </Modal.Body>
@@ -358,7 +475,7 @@ export default class HandsontableModal extends React.PureComponent {
       manualColumnMove: true,
       manualColumnMove: true,
       manualColumnResize: true,
       manualColumnResize: true,
       selectionMode: 'multiple',
       selectionMode: 'multiple',
-      outsideClickDeselects: false,
+      outsideClickDeselects: false
     };
     };
   }
   }
 }
 }

+ 7 - 2
src/client/js/models/MarkdownTable.js

@@ -41,12 +41,17 @@ export default class MarkdownTable {
   }
   }
 
 
   /**
   /**
-   * normalize all cell data(trim & convert the newline character to space)
+   * normalize all cell data(trim & convert the newline character to space or pad '' if cell data is null)
    */
    */
   normalizeCells() {
   normalizeCells() {
     for (let i = 0; i < this.table.length; i++) {
     for (let i = 0; i < this.table.length; i++) {
       for (let j = 0; j < this.table[i].length; j++) {
       for (let j = 0; j < this.table[i].length; j++) {
-        this.table[i][j] = this.table[i][j].trim().replace(/\r?\n/g, ' ');
+        if (this.table[i][j] != null) {
+          this.table[i][j] = this.table[i][j].trim().replace(/\r?\n/g, ' ');
+        }
+        else {
+          this.table[i][j] = '';
+        }
       }
       }
     }
     }
 
 

+ 41 - 0
src/client/js/util/codemirror/update-display-util.ext.js

@@ -0,0 +1,41 @@
+import { sawCollapsedSpans } from 'codemirror/src/line/saw_special_spans';
+import { getLine } from 'codemirror/src/line/utils_line';
+import { heightAtLine, visualLineEndNo, visualLineNo } from 'codemirror/src/line/spans';
+import { DisplayUpdate } from 'codemirror/src/display/update_display';
+import { adjustView } from 'codemirror/src/display/view_tracking';
+
+class UpdateDisplayUtil {
+
+  /**
+   * Transplant 'updateDisplayIfNeeded' method to fix weseek/growi#703
+   *
+   * @see https://github.com/weseek/growi/issues/703
+   * @see https://github.com/codemirror/CodeMirror/blob/5.42.0/src/display/update_display.js#L125
+   *
+   * @param {CodeMirror} cm
+   */
+  static forceUpdateViewOffset(cm) {
+    const doc = cm.doc;
+    const display = cm.display;
+
+    const update = new DisplayUpdate(cm, cm.getViewport());
+
+    // Compute a suitable new viewport (from & to)
+    let end = doc.first + doc.size;
+    let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
+    let to = Math.min(end, update.visible.to + cm.options.viewportMargin);
+    if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
+    if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
+    if (sawCollapsedSpans) {
+      from = visualLineNo(cm.doc, from);
+      to = visualLineEndNo(cm.doc, to);
+    }
+    adjustView(cm, from, to);
+
+    display.viewOffset = heightAtLine(getLine(doc, display.viewFrom));
+  }
+
+}
+
+
+export default UpdateDisplayUtil;

+ 2 - 2
src/server/models/config.js

@@ -200,7 +200,7 @@ module.exports = function(crowi) {
     });
     });
   };
   };
 
 
-  configSchema.statics.setupCofigFormData = function(ns, config) {
+  configSchema.statics.setupConfigFormData = function(ns, config) {
     var defaultConfig = {};
     var defaultConfig = {};
 
 
     // set Default Settings
     // set Default Settings
@@ -349,7 +349,7 @@ module.exports = function(crowi) {
     return method != 'none';
     return method != 'none';
   };
   };
 
 
-  configSchema.statics.isGuesstAllowedToRead = function(config) {
+  configSchema.statics.isGuestAllowedToRead = function(config) {
     // return true if puclic wiki mode
     // return true if puclic wiki mode
     if (Config.isPublicWikiOnly(config)) {
     if (Config.isPublicWikiOnly(config)) {
       return true;
       return true;

+ 6 - 6
src/server/routes/admin.js

@@ -90,7 +90,7 @@ module.exports = function(crowi, app) {
   actions.app = {};
   actions.app = {};
   actions.app.index = function(req, res) {
   actions.app.index = function(req, res) {
     var settingForm;
     var settingForm;
-    settingForm = Config.setupCofigFormData('crowi', req.config);
+    settingForm = Config.setupConfigFormData('crowi', req.config);
 
 
     return res.render('admin/app', {
     return res.render('admin/app', {
       settingForm: settingForm,
       settingForm: settingForm,
@@ -103,7 +103,7 @@ module.exports = function(crowi, app) {
   // app.get('/admin/security'                  , admin.security.index);
   // app.get('/admin/security'                  , admin.security.index);
   actions.security = {};
   actions.security = {};
   actions.security.index = function(req, res) {
   actions.security.index = function(req, res) {
-    const settingForm = Config.setupCofigFormData('crowi', req.config);
+    const settingForm = Config.setupConfigFormData('crowi', req.config);
     const isAclEnabled = !Config.isPublicWikiOnly(req.config);
     const isAclEnabled = !Config.isPublicWikiOnly(req.config);
     return res.render('admin/security', { settingForm, isAclEnabled });
     return res.render('admin/security', { settingForm, isAclEnabled });
   };
   };
@@ -112,7 +112,7 @@ module.exports = function(crowi, app) {
   actions.markdown = {};
   actions.markdown = {};
   actions.markdown.index = function(req, res) {
   actions.markdown.index = function(req, res) {
     const config = crowi.getConfig();
     const config = crowi.getConfig();
-    const markdownSetting = Config.setupCofigFormData('markdown', config);
+    const markdownSetting = Config.setupConfigFormData('markdown', config);
 
 
     return res.render('admin/markdown', {
     return res.render('admin/markdown', {
       markdownSetting: markdownSetting,
       markdownSetting: markdownSetting,
@@ -189,7 +189,7 @@ module.exports = function(crowi, app) {
   actions.customize = {};
   actions.customize = {};
   actions.customize.index = function(req, res) {
   actions.customize.index = function(req, res) {
     var settingForm;
     var settingForm;
-    settingForm = Config.setupCofigFormData('crowi', req.config);
+    settingForm = Config.setupConfigFormData('crowi', req.config);
 
 
     const highlightJsCssSelectorOptions = {
     const highlightJsCssSelectorOptions = {
       'github':           { name: '[Light] GitHub',         border: false },
       'github':           { name: '[Light] GitHub',         border: false },
@@ -215,7 +215,7 @@ module.exports = function(crowi, app) {
   actions.notification.index = async(req, res) => {
   actions.notification.index = async(req, res) => {
     const config = crowi.getConfig();
     const config = crowi.getConfig();
     const UpdatePost = crowi.model('UpdatePost');
     const UpdatePost = crowi.model('UpdatePost');
-    let slackSetting = Config.setupCofigFormData('notification', config);
+    let slackSetting = Config.setupConfigFormData('notification', config);
     const hasSlackIwhUrl = Config.hasSlackIwhUrl(config);
     const hasSlackIwhUrl = Config.hasSlackIwhUrl(config);
     const hasSlackToken = Config.hasSlackToken(config);
     const hasSlackToken = Config.hasSlackToken(config);
 
 
@@ -1010,7 +1010,7 @@ module.exports = function(crowi, app) {
   actions.importer.index = function(req, res) {
   actions.importer.index = function(req, res) {
 
 
     var settingForm;
     var settingForm;
-    settingForm = Config.setupCofigFormData('crowi', req.config);
+    settingForm = Config.setupConfigFormData('crowi', req.config);
 
 
     return res.render('admin/importer', {
     return res.render('admin/importer', {
       settingForm: settingForm,
       settingForm: settingForm,

+ 1 - 1
src/server/util/middlewares.js

@@ -223,7 +223,7 @@ exports.loginRequired = function(crowi, app, isStrictly = true) {
       var Config = crowi.model('Config');
       var Config = crowi.model('Config');
 
 
       // when allowed to read
       // when allowed to read
-      if (Config.isGuesstAllowedToRead(config)) {
+      if (Config.isGuestAllowedToRead(config)) {
         return next();
         return next();
       }
       }
     }
     }

+ 27 - 1
wercker.yml

@@ -134,7 +134,10 @@ release: # would be run on release branch
         git reset --hard
         git reset --hard
         # npm version to bump version
         # npm version to bump version
         npm version patch
         npm version patch
-        # get version
+
+    - script:
+      name: get RELEASE_VERSION
+      code: |
         export RELEASE_VERSION=`npm run version --silent`
         export RELEASE_VERSION=`npm run version --silent`
         echo "export RELEASE_VERSION=$RELEASE_VERSION"
         echo "export RELEASE_VERSION=$RELEASE_VERSION"
 
 
@@ -167,3 +170,26 @@ release: # would be run on release branch
       username: wercker
       username: wercker
       notify_on: "failed"
       notify_on: "failed"
 
 
+
+release-rc: # would be run on rc/* branches
+  steps:
+    - install-packages:
+      packages: jq
+
+    - script:
+      name: get RELEASE_VERSION
+      code: |
+        export RELEASE_VERSION=`npm run version --silent`
+        echo "export RELEASE_VERSION=$RELEASE_VERSION"
+
+    - script:
+      name: trigger growi-docker release-rc pipeline
+      code: sh ./bin/wercker/trigger-growi-docker.sh
+
+  after-steps:
+    - slack-notifier:
+      url: $SLACK_WEBHOOK_URL
+      channel: ci
+      username: wercker
+      notify_on: "failed"
+

+ 6 - 6
yarn.lock

@@ -2010,9 +2010,9 @@ code-point-at@^1.0.0:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
 
-codemirror@^5.37.0:
-  version "5.39.0"
-  resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.39.0.tgz#4654f7d2f7e525e04a62e72d9482348ccb37dce5"
+codemirror@^5.42.0:
+  version "5.42.0"
+  resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.42.0.tgz#2d5b640ed009e89dee9ed8a2a778e2a25b65f9eb"
 
 
 collection-visit@^1.0.0:
 collection-visit@^1.0.0:
   version "1.0.0"
   version "1.0.0"
@@ -7467,9 +7467,9 @@ react-clipboard.js@^2.0.0:
     clipboard "^2.0.0"
     clipboard "^2.0.0"
     prop-types "^15.5.0"
     prop-types "^15.5.0"
 
 
-react-codemirror2@^5.0.4:
-  version "5.0.4"
-  resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-5.0.4.tgz#d44a2d7a63a96509ba65db9b771bd61a781b8a0d"
+react-codemirror2@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-5.1.0.tgz#62de4460178adea40eb52eabf7491669bf3794b8"
 
 
 react-dom@^16.4.1:
 react-dom@^16.4.1:
   version "16.4.1"
   version "16.4.1"