فهرست منبع

Merge pull request #267 from weseek/rc/2.3.9

release v2.3.9
Yuki Takei 8 سال پیش
والد
کامیت
dfcb1b8756

+ 8 - 0
CHANGES.md

@@ -1,6 +1,14 @@
 CHANGES
 CHANGES
 ========
 ========
 
 
+## 2.3.9-RC
+
+* Fix: `Ctrl-/` doesn't work on Chrome
+* Fix: Close Shortcuts help with `Ctrl-/`, ESC key
+* Fix: Jump to last line wrongly when `.revision-head-edit-button` clicked
+* Support: Upgrade libs
+    * googleapis
+
 ## 2.3.8
 ## 2.3.8
 
 
 * Feature: Suggest page path when creating pages
 * Feature: Suggest page path when creating pages

+ 12 - 22
lib/models/user.js

@@ -436,34 +436,24 @@ module.exports = function(crowi) {
 
 
 
 
   userSchema.statics.findUserByUsername = function(username) {
   userSchema.statics.findUserByUsername = function(username) {
-    var User = this;
-    return new Promise(function(resolve, reject) {
-      User.findOne({username: username}, function (err, userData) {
-        if (err) {
-          return reject(err);
-        }
-
-        return resolve(userData);
-      });
-    });
+    if (username == null) {
+      return Promise.resolve(null);
+    }
+    return this.findOne({username});
   };
   };
 
 
   userSchema.statics.findUserByApiToken = function(apiToken) {
   userSchema.statics.findUserByApiToken = function(apiToken) {
-    var self = this;
-
-    return new Promise(function(resolve, reject) {
-      self.findOne({apiToken: apiToken}, function (err, userData) {
-        if (err) {
-          return reject(err);
-        } else {
-          return resolve(userData);
-        }
-      });
-    });
+    if (apiToken == null) {
+      return Promise.resolve(null);
+    }
+    return this.findOne({apiToken});
   };
   };
 
 
   userSchema.statics.findUserByGoogleId = function(googleId, callback) {
   userSchema.statics.findUserByGoogleId = function(googleId, callback) {
-    this.findOne({googleId: googleId}, function (err, userData) {
+    if (googleId == null) {
+      callback(null, null);
+    }
+    this.findOne({googleId}, function (err, userData) {
       callback(err, userData);
       callback(err, userData);
     });
     });
   };
   };

+ 1 - 0
lib/routes/login.js

@@ -130,6 +130,7 @@ module.exports = function(crowi, app) {
         User.findUserByGoogleId(googleId, function(err, userData) {
         User.findUserByGoogleId(googleId, function(err, userData) {
           debug('findUserByGoogleId', err, userData);
           debug('findUserByGoogleId', err, userData);
           if (!userData) {
           if (!userData) {
+            clearGoogleSession(req);
             return loginFailure(req, res);
             return loginFailure(req, res);
           }
           }
           return loginSuccess(req, res, userData);
           return loginSuccess(req, res, userData);

+ 5 - 3
lib/util/googleAuth.js

@@ -5,7 +5,8 @@
 module.exports = function(config) {
 module.exports = function(config) {
   'use strict';
   'use strict';
 
 
-  var google = require('googleapis')
+  const { GoogleApis } = require('googleapis');
+  var google = new GoogleApis()
     , debug = require('debug')('crowi:lib:googleAuth')
     , debug = require('debug')('crowi:lib:googleAuth')
     , lib = {}
     , lib = {}
     ;
     ;
@@ -58,8 +59,9 @@ module.exports = function(config) {
           return callback(new Error('[googleAuth.handleCallback] Error while proceccing userinfo.get.'), null);
           return callback(new Error('[googleAuth.handleCallback] Error while proceccing userinfo.get.'), null);
         }
         }
 
 
-        response.user_id = response.id; // This is for B.C. (tokeninfo をつかっている前提のコードに対してのもの)
-        return callback(null, response);
+        const data = response.data;
+        data.user_id = data.id;           // This is for B.C. (tokeninfo をつかっている前提のコードに対してのもの)
+        return callback(null, data);
       });
       });
     });
     });
   };
   };

+ 21 - 3
lib/util/search.js

@@ -210,15 +210,33 @@ SearchClient.prototype.addAllPages = function()
   var Page = this.crowi.model('Page');
   var Page = this.crowi.model('Page');
   var cursor = Page.getStreamOfFindAll();
   var cursor = Page.getStreamOfFindAll();
   var body = [];
   var body = [];
+  var sent = 0;
+  var skipped = 0;
 
 
   return new Promise(function(resolve, reject) {
   return new Promise(function(resolve, reject) {
     cursor.on('data', function (doc) {
     cursor.on('data', function (doc) {
       if (!doc.creator || !doc.revision || !self.shouldIndexed(doc)) {
       if (!doc.creator || !doc.revision || !self.shouldIndexed(doc)) {
-        debug('Skipped', doc.path);
+        //debug('Skipped', doc.path);
+        skipped++;
         return ;
         return ;
       }
       }
 
 
       self.prepareBodyForCreate(body, doc);
       self.prepareBodyForCreate(body, doc);
+      //debug(body.length);
+      if (body.length > 2000) {
+        sent++;
+        debug('Sending request (seq, skipped)', sent, skipped);
+        self.client.bulk({
+          body: body,
+          requestTimeout: Infinity,
+        }).then(res => {
+          debug('addAllPages add anyway (items, errors, took): ', (res.items || []).length, res.errors, res.took)
+        }).catch(err => {
+          debug('addAllPages error on add anyway: ', err)
+        });
+
+        body = [];
+      }
     }).on('error', function (err) {
     }).on('error', function (err) {
       // TODO: handle err
       // TODO: handle err
       debug('Error cursor:', err);
       debug('Error cursor:', err);
@@ -231,13 +249,13 @@ SearchClient.prototype.addAllPages = function()
         return resolve();
         return resolve();
       }
       }
 
 
-      // 最後に送信
+      // 最後にすべてを送信
       self.client.bulk({
       self.client.bulk({
         body: body,
         body: body,
         requestTimeout: Infinity,
         requestTimeout: Infinity,
       })
       })
       .then(function(res) {
       .then(function(res) {
-        debug('Reponse from es:', res);
+        debug('Reponse from es (item length, errros, took):', (res.items || []).length, res.errors, res.took);
         return resolve(res);
         return resolve(res);
       }).catch(function(err) {
       }).catch(function(err) {
         debug('Err from es:', err);
         debug('Err from es:', err);

+ 1 - 1
lib/views/modal/shortcuts.html

@@ -1,4 +1,4 @@
-<div class="modal" id="shortcuts-modal">
+<div class="modal" id="shortcuts-modal" tabindex="-1">
   <div class="modal-dialog">
   <div class="modal-dialog">
     <div class="modal-content">
     <div class="modal-content">
 
 

+ 2 - 2
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "crowi-plus",
   "name": "crowi-plus",
-  "version": "2.3.8-RC",
+  "version": "2.3.9-RC",
   "description": "Enhanced Crowi",
   "description": "Enhanced Crowi",
   "tags": [
   "tags": [
     "wiki",
     "wiki",
@@ -83,7 +83,7 @@
     "express-webpack-assets": "^0.1.0",
     "express-webpack-assets": "^0.1.0",
     "file-loader": "^1.1.0",
     "file-loader": "^1.1.0",
     "get-line-from-pos": "^1.0.0",
     "get-line-from-pos": "^1.0.0",
-    "googleapis": "^25.0.0",
+    "googleapis": "^26.0.0",
     "graceful-fs": "^4.1.11",
     "graceful-fs": "^4.1.11",
     "highlight.js": "^9.10.0",
     "highlight.js": "^9.10.0",
     "i18next": "^10.0.1",
     "i18next": "^10.0.1",

+ 3 - 1
resource/js/components/NewPageNameInputter.js

@@ -1,5 +1,7 @@
 import React from 'react';
 import React from 'react';
-import { FormGroup, Button, InputGroup } from 'react-bootstrap';
+import { FormGroup } from 'react-bootstrap/es/FormGroup';
+import { Button } from 'react-bootstrap/es/Button';
+import { InputGroup } from 'react-bootstrap/es/InputGroup';
 
 
 import UserPicture from './User/UserPicture';
 import UserPicture from './User/UserPicture';
 import PageListMeta from './PageList/PageListMeta';
 import PageListMeta from './PageList/PageListMeta';

+ 4 - 3
resource/js/components/PageEditor/Editor.js

@@ -34,6 +34,7 @@ require('codemirror/theme/twilight.css');
 import Dropzone from 'react-dropzone';
 import Dropzone from 'react-dropzone';
 
 
 import pasteHelper from './PasteHelper';
 import pasteHelper from './PasteHelper';
+import markdownListHelper from './MarkdownListHelper';
 import emojiAutoCompleteHelper from './EmojiAutoCompleteHelper';
 import emojiAutoCompleteHelper from './EmojiAutoCompleteHelper';
 
 
 
 
@@ -97,8 +98,8 @@ export default class Editor extends React.Component {
     const editor = this.getCodeMirror();
     const editor = this.getCodeMirror();
 
 
     // scroll to the bottom for a moment
     // scroll to the bottom for a moment
-    const eol = editor.getDoc().lineCount() - 1;
-    editor.scrollIntoView(eol);
+    const lastLine = editor.getDoc().lastLine();
+    editor.scrollIntoView(lastLine);
 
 
     const linePosition = Math.max(0, line - 1);
     const linePosition = Math.max(0, line - 1);
     editor.scrollIntoView(linePosition);
     editor.scrollIntoView(linePosition);
@@ -318,7 +319,7 @@ export default class Editor extends React.Component {
               highlightFormatting: true,
               highlightFormatting: true,
               // continuelist, indentlist
               // continuelist, indentlist
               extraKeys: {
               extraKeys: {
-                "Enter": "newlineAndIndentContinueMarkdownList",
+                "Enter": markdownListHelper.newlineAndIndentContinueMarkdownList,
                 "Tab": "indentMore",
                 "Tab": "indentMore",
                 "Shift-Tab": "indentLess",
                 "Shift-Tab": "indentLess",
                 "Ctrl-Q": (cm) => { cm.foldCode(cm.getCursor()) },
                 "Ctrl-Q": (cm) => { cm.foldCode(cm.getCursor()) },

+ 167 - 0
resource/js/components/PageEditor/MarkdownListHelper.js

@@ -0,0 +1,167 @@
+import * as codemirror from 'codemirror';
+
+class MarkdownListHelper {
+
+  constructor() {
+    // https://github.com/codemirror/CodeMirror/blob/c7853a989c77bb9f520c9c530cbe1497856e96fc/addon/edit/continuelist.js#L14
+    // https://regex101.com/r/7BN2fR/5
+    this.indentAndMarkRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/;
+    this.indentAndMarkOnlyRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/;
+
+    this.newlineAndIndentContinueMarkdownList = this.newlineAndIndentContinueMarkdownList.bind(this);
+    this.pasteText = this.pasteText.bind(this);
+
+    this.getBol = this.getBol.bind(this);
+    this.getEol = this.getEol.bind(this);
+    this.getStrFromBol = this.getStrFromBol.bind(this);
+    this.getStrToEol = this.getStrToEol.bind(this);
+  }
+
+  /**
+   * wrap codemirror.commands.newlineAndIndentContinueMarkdownList
+   * @param {any} editor An editor instance of CodeMirror
+   */
+  newlineAndIndentContinueMarkdownList(editor) {
+    // get strings from current position to EOL(end of line) before break the line
+    const strToEol = this.getStrToEol(editor);
+
+    if (this.indentAndMarkRE.test(strToEol)) {
+      codemirror.commands.newlineAndIndent(editor);
+      // replace the line with strToEol (abort auto indent)
+      editor.getDoc().replaceRange(strToEol, this.getBol(editor), this.getEol(editor));
+    }
+    else {
+      codemirror.commands.newlineAndIndentContinueMarkdownList(editor);
+    }
+  }
+
+  /**
+   * paste text
+   * @param {any} editor An editor instance of CodeMirror
+   * @param {any} event
+   * @param {string} text
+   */
+  pasteText(editor, event, text) {
+    // get strings from BOL(beginning of line) to current position
+    const strFromBol = this.getStrFromBol(editor);
+
+    const matched = strFromBol.match(this.indentAndMarkRE);
+    // when match indentAndMarkOnlyRE
+    // (this means the current position is the beginning of the list item)
+    if (this.indentAndMarkOnlyRE.test(strFromBol)) {
+      const adjusted = this.adjustPastedData(strFromBol, text);
+
+      // replace
+      if (adjusted != null) {
+        event.preventDefault();
+        editor.getDoc().replaceRange(adjusted, this.getBol(editor), editor.getCursor());
+      }
+    }
+  }
+
+  /**
+   * return adjusted pasted data by indentAndMark
+   *
+   * @param {string} indentAndMark
+   * @param {string} text
+   * @returns adjusted pasted data
+   *      returns null when adjustment is not necessary
+   */
+  adjustPastedData(indentAndMark, text) {
+    let adjusted = null;
+
+    // list data (starts with indent and mark)
+    if (text.match(this.indentAndMarkRE)) {
+      const indent = indentAndMark.match(this.indentAndMarkRE)[1];
+
+      // splice to an array of line
+      const lines = text.match(/[^\r\n]+/g);
+      // indent
+      const replacedLines = lines.map((line) => {
+        return indent + line;
+      })
+
+      adjusted = replacedLines.join('\n');
+    }
+    // listful data
+    else if (this.isListfulData(text)) {
+      // do nothing (return null)
+    }
+    // not listful data
+    else {
+      // append `indentAndMark` at the beginning of all lines (except the first line)
+      const replacedText = text.replace(/(\r\n|\r|\n)/g, "$1" + indentAndMark);
+      // append `indentAndMark` to the first line
+      adjusted = indentAndMark + replacedText;
+    }
+
+    return adjusted;
+  }
+
+  /**
+   * evaluate whether `text` is list like data or not
+   * @param {string} text
+   */
+  isListfulData(text) {
+    // return false if includes at least one blank line
+    // see https://stackoverflow.com/a/16369725
+    if (text.match(/^\s*[\r\n]/m) != null) {
+      return false;
+    }
+
+    const lines = text.match(/[^\r\n]+/g);
+    // count lines that starts with indent and mark
+    let isListful = false;
+    let count = 0;
+    lines.forEach((line) => {
+      if (line.match(this.indentAndMarkRE)) {
+        count++;
+      }
+      // ensure to be true if it is 50% or more
+      if (count >= lines.length / 2) {
+        isListful = true;
+        return;
+      }
+    });
+
+    return isListful;
+  }
+
+  /**
+   * return the postion of the BOL(beginning of line)
+   */
+  getBol(editor) {
+    const curPos = editor.getCursor();
+    return { line: curPos.line, ch: 0 };
+  }
+
+  /**
+   * return the postion of the EOL(end of line)
+   */
+  getEol(editor) {
+    const curPos = editor.getCursor();
+    const lineLength = editor.getDoc().getLine(curPos.line).length;
+    return { line: curPos.line, ch: lineLength };
+  }
+
+  /**
+   * return strings from BOL(beginning of line) to current position
+   */
+  getStrFromBol(editor) {
+    const curPos = editor.getCursor();
+    return editor.getDoc().getRange(this.getBol(editor), curPos);
+  }
+
+  /**
+   * return strings from current position to EOL(end of line)
+   */
+  getStrToEol(editor) {
+    const curPos = editor.getCursor();
+    return editor.getDoc().getRange(curPos, this.getEol(editor));
+  }
+}
+
+// singleton pattern
+const instance = new MarkdownListHelper();
+Object.freeze(instance);
+export default instance;

+ 5 - 91
resource/js/components/PageEditor/PasteHelper.js

@@ -1,13 +1,11 @@
 import accepts from 'attr-accept'
 import accepts from 'attr-accept'
 
 
+import markdownListHelper from './MarkdownListHelper';
+
 class PasteHelper {
 class PasteHelper {
 
 
   constructor() {
   constructor() {
-    // https://regex101.com/r/7BN2fR/3
-    this.indentAndMarkPattern = /^([ \t]*)(?:>|\-|\+|\*|\d+\.) /;
-
     this.pasteText = this.pasteText.bind(this);
     this.pasteText = this.pasteText.bind(this);
-    this.adjustPastedData = this.adjustPastedData.bind(this);
   }
   }
 
 
   /**
   /**
@@ -19,97 +17,13 @@ class PasteHelper {
     // get data in clipboard
     // get data in clipboard
     const text = event.clipboardData.getData('text/plain');
     const text = event.clipboardData.getData('text/plain');
 
 
-    if (text.length == 0) { return; }
-
-    const curPos = editor.getCursor();
-    const bol = { line: curPos.line, ch: 0 }; // beginning of line
-
-    // get strings from BOL(beginning of line) to current position
-    const strFromBol = editor.getDoc().getRange(bol, curPos);
-
-    const matched = strFromBol.match(this.indentAndMarkPattern);
-    // when match completely to pattern
-    // (this means the current position is the beginning of the list item)
-    if (matched && matched[0] == strFromBol) {
-      const adjusted = this.adjustPastedData(strFromBol, text);
-
-      // replace
-      if (adjusted != null) {
-        event.preventDefault();
-        editor.getDoc().replaceRange(adjusted, bol, curPos);
-      }
+    if (text.length == 0) {
+      return;
     }
     }
-  }
-
-  /**
-   * return adjusted pasted data by indentAndMark
-   *
-   * @param {string} indentAndMark
-   * @param {string} text
-   * @returns adjusted pasted data
-   *      returns null when adjustment is not necessary
-   */
-  adjustPastedData(indentAndMark, text) {
-    let adjusted = null;
-
-    // list data (starts with indent and mark)
-    if (text.match(this.indentAndMarkPattern)) {
-      const indent = indentAndMark.match(this.indentAndMarkPattern)[1];
-
-      // splice to an array of line
-      const lines = text.match(/[^\r\n]+/g);
-      // indent
-      const replacedLines = lines.map((line) => {
-        return indent + line;
-      })
 
 
-      adjusted = replacedLines.join('\n');
-    }
-    // listful data
-    else if (this.isListfulData(text)) {
-      // do nothing (return null)
-    }
-    // not listful data
-    else {
-      // append `indentAndMark` at the beginning of all lines (except the first line)
-      const replacedText = text.replace(/(\r\n|\r|\n)/g, "$1" + indentAndMark);
-      // append `indentAndMark` to the first line
-      adjusted = indentAndMark + replacedText;
-    }
-
-    return adjusted;
-  }
-
-  /**
-   * evaluate whether `text` is list like data or not
-   * @param {string} text
-   */
-  isListfulData(text) {
-    // return false if includes at least one blank line
-    // see https://stackoverflow.com/a/16369725
-    if (text.match(/^\s*[\r\n]/m) != null) {
-      return false;
-    }
-
-    const lines = text.match(/[^\r\n]+/g);
-    // count lines that starts with indent and mark
-    let isListful = false;
-    let count = 0;
-    lines.forEach((line) => {
-      if (line.match(this.indentAndMarkPattern)) {
-        count++;
-      }
-      // ensure to be true if it is 50% or more
-      if (count >= lines.length / 2) {
-        isListful = true;
-        return;
-      }
-    });
-
-    return isListful;
+    markdownListHelper.pasteText(editor, event, text);
   }
   }
 
 
-
   // Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
   // Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
   /**
   /**
    * transplanted from react-dropzone
    * transplanted from react-dropzone

+ 3 - 1
resource/js/components/PageList/PagePath.js

@@ -1,6 +1,8 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
+import escapeStringRegexp from 'escape-string-regexp';
+
 export default class PagePath extends React.Component {
 export default class PagePath extends React.Component {
 
 
   getShortPath(path) {
   getShortPath(path) {
@@ -29,7 +31,7 @@ export default class PagePath extends React.Component {
     const page = this.props.page;
     const page = this.props.page;
     const pagePath = page.path.replace(this.props.excludePathString.replace(/^\//, ''), '');
     const pagePath = page.path.replace(this.props.excludePathString.replace(/^\//, ''), '');
     const shortPath = this.getShortPath(pagePath);
     const shortPath = this.getShortPath(pagePath);
-    const shortPathEscaped = shortPath.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    const shortPathEscaped = escapeStringRegexp(shortPath);
     const pathPrefix = pagePath.replace(new RegExp(shortPathEscaped + '(/)?$'), '');
     const pathPrefix = pagePath.replace(new RegExp(shortPathEscaped + '(/)?$'), '');
 
 
     return (
     return (

+ 19 - 14
resource/js/legacy/crowi.js

@@ -2,9 +2,10 @@
 /* Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
 /* Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
 */
 */
 
 
-var io = require('socket.io-client');
-var entities = require("entities");
-var getLineFromPos = require('get-line-from-pos');
+const io = require('socket.io-client');
+const entities = require("entities");
+const escapeStringRegexp = require('escape-string-regexp');
+const getLineFromPos = require('get-line-from-pos');
 require('bootstrap-sass');
 require('bootstrap-sass');
 require('jquery.cookie');
 require('jquery.cookie');
 
 
@@ -39,10 +40,11 @@ Crowi.appendEditSectionButtons = function(contentId, markdown) {
   $('h1,h2,h3,h4,h5,h6', $content).each(function(idx, elm) {
   $('h1,h2,h3,h4,h5,h6', $content).each(function(idx, elm) {
     // get header text string
     // get header text string
     const text = $(this).text();
     const text = $(this).text();
+    const escapedText = escapeStringRegexp(text);
 
 
     // search pos for '# ...'
     // search pos for '# ...'
     // https://regex101.com/r/y5rpO5/1
     // https://regex101.com/r/y5rpO5/1
-    const regexp = new RegExp(`[^\r\n]*#+[^\r\n]*${text}[^\r\n]*`);
+    const regexp = new RegExp(`[^\r\n]*#+[^\r\n]*${escapedText}[^\r\n]*`);
     let position = markdown.search(regexp);
     let position = markdown.search(regexp);
     if (position < 0) { // if not found, search with header text only
     if (position < 0) { // if not found, search with header text only
       position = markdown.search(text);
       position = markdown.search(text);
@@ -189,18 +191,29 @@ Crowi.updateCurrentRevision = function(revisionId) {
 }
 }
 
 
 Crowi.handleKeyEHandler = (event) => {
 Crowi.handleKeyEHandler = (event) => {
+  // ignore when dom that has 'modal in' classes exists
+  if (document.getElementsByClassName('modal in').length > 0) {
+    return;
+  }
   // show editor
   // show editor
   $('a[data-toggle="tab"][href="#edit-form"]').tab('show');
   $('a[data-toggle="tab"][href="#edit-form"]').tab('show');
+  event.preventDefault();
 }
 }
 
 
 Crowi.handleKeyCHandler = (event) => {
 Crowi.handleKeyCHandler = (event) => {
+  // ignore when dom that has 'modal in' classes exists
+  if (document.getElementsByClassName('modal in').length > 0) {
+    return;
+  }
   // show modal to create a page
   // show modal to create a page
   $('#create-page').modal();
   $('#create-page').modal();
+  event.preventDefault();
 }
 }
 
 
 Crowi.handleKeyCtrlSlashHandler = (event) => {
 Crowi.handleKeyCtrlSlashHandler = (event) => {
   // show modal to create a page
   // show modal to create a page
   $('#shortcuts-modal').modal('toggle');
   $('#shortcuts-modal').modal('toggle');
+  event.preventDefault();
 }
 }
 
 
 $(function() {
 $(function() {
@@ -466,11 +479,8 @@ $(function() {
       return;
       return;
     }
     }
 
 
-    var escape = function(s) {
-      return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
-    };
     path = entities.encodeHTML(path);
     path = entities.encodeHTML(path);
-    var pattern = escape(entities.encodeHTML(shortPath)) + '(/)?$';
+    var pattern = escapeStringRegexp(entities.encodeHTML(shortPath)) + '(/)?$';
 
 
     $link.html(path.replace(new RegExp(pattern), '<strong>' + shortPath + '$1</strong>'));
     $link.html(path.replace(new RegExp(pattern), '<strong>' + shortPath + '$1</strong>'));
   });
   });
@@ -966,7 +976,7 @@ window.addEventListener('hashchange', function(e) {
   }
   }
 });
 });
 
 
-window.addEventListener('keypress', (event) => {
+window.addEventListener('keydown', (event) => {
   const target = event.target;
   const target = event.target;
 
 
   // ignore when target dom is input
   // ignore when target dom is input
@@ -975,11 +985,6 @@ window.addEventListener('keypress', (event) => {
     return;
     return;
   }
   }
 
 
-  // ignore when dom that has 'modal in' classes exists
-  if (document.getElementsByClassName('modal in').length > 0) {
-    return;
-  }
-
   switch (event.key) {
   switch (event.key) {
     case 'e':
     case 'e':
       if (!event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
       if (!event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {

+ 15 - 16
yarn.lock

@@ -300,12 +300,6 @@ async@2.1.4:
   dependencies:
   dependencies:
     lodash "^4.14.0"
     lodash "^4.14.0"
 
 
-async@2.6.0, async@^2.1.2, async@^2.1.5, async@^2.3.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
-  dependencies:
-    lodash "^4.14.0"
-
 async@^0.9.0:
 async@^0.9.0:
   version "0.9.2"
   version "0.9.2"
   resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
   resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
@@ -314,6 +308,12 @@ async@^1.5.0:
   version "1.5.2"
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
 
+async@^2.1.2, async@^2.1.5, async@^2.3.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
+  dependencies:
+    lodash "^4.14.0"
+
 async@~0.2.6:
 async@~0.2.6:
   version "0.2.10"
   version "0.2.10"
   resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
   resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
@@ -2645,12 +2645,12 @@ good-listener@^1.2.2:
   dependencies:
   dependencies:
     delegate "^3.1.2"
     delegate "^3.1.2"
 
 
-google-auth-library@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-1.1.0.tgz#f3e17e8d9f93a0cdd8c78503427cb656be3aa435"
+google-auth-library@^1.1.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-1.2.1.tgz#20eb9d585b1837a703712abdb787da4984982b64"
   dependencies:
   dependencies:
     axios "^0.17.1"
     axios "^0.17.1"
-    gtoken "^2.0.2"
+    gtoken "^2.1.0"
     jws "^3.1.4"
     jws "^3.1.4"
     lodash.isstring "^4.0.1"
     lodash.isstring "^4.0.1"
     lru-cache "^4.1.1"
     lru-cache "^4.1.1"
@@ -2662,12 +2662,11 @@ google-p12-pem@^1.0.0:
     node-forge "^0.7.1"
     node-forge "^0.7.1"
     pify "^3.0.0"
     pify "^3.0.0"
 
 
-googleapis@^25.0.0:
-  version "25.0.0"
-  resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-25.0.0.tgz#0f6f48109584e035e266022eb7fdc1a86823da3a"
+googleapis@^26.0.0:
+  version "26.0.1"
+  resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-26.0.1.tgz#e1efb43b00546b1ad8c055a83cf210d5422b7f42"
   dependencies:
   dependencies:
-    async "2.6.0"
-    google-auth-library "^1.0.0"
+    google-auth-library "^1.1.0"
     qs "^6.5.1"
     qs "^6.5.1"
     string-template "1.0.0"
     string-template "1.0.0"
     uuid "^3.1.0"
     uuid "^3.1.0"
@@ -2684,7 +2683,7 @@ growly@^1.2.0:
   version "1.3.0"
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
   resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
 
 
-gtoken@^2.0.2:
+gtoken@^2.1.0:
   version "2.1.0"
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-2.1.0.tgz#e65028d32d1d52eeb17b00f85ef0f7484f0fd36f"
   resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-2.1.0.tgz#e65028d32d1d52eeb17b00f85ef0f7484f0fd36f"
   dependencies:
   dependencies: