Bladeren bron

Merge commit 'fb4364f3284045069aa9c682b9a99ab20368631f' into feat/control-tag-model

# Conflicts:
#	src/server/models/tag.js
yusuketk 7 jaren geleden
bovenliggende
commit
f5105796ca
100 gewijzigde bestanden met toevoegingen van 1107 en 1052 verwijderingen
  1. 1 0
      .eslintignore
  2. 99 46
      .eslintrc.js
  3. 3 3
      bin/shrink-emojione-strategy.js
  4. 1 1
      config/webpack.prod.js
  5. 2 1
      package.json
  6. 6 9
      src/client/js/components/Admin/CustomCssEditor.js
  7. 6 9
      src/client/js/components/Admin/CustomHeaderEditor.js
  8. 6 9
      src/client/js/components/Admin/CustomScriptEditor.js
  9. 1 1
      src/client/js/components/Common/UserDate.js
  10. 15 7
      src/client/js/components/CopyButton.js
  11. 4 3
      src/client/js/components/Page/PagePath.js
  12. 6 4
      src/client/js/components/Page/RevisionBody.js
  13. 23 16
      src/client/js/components/Page/RevisionPath.js
  14. 12 6
      src/client/js/components/Page/RevisionUrl.js
  15. 16 11
      src/client/js/components/PageAttachment.js
  16. 10 5
      src/client/js/components/PageAttachment/Attachment.js
  17. 11 5
      src/client/js/components/PageAttachment/DeleteAttachmentModal.js
  18. 7 6
      src/client/js/components/PageAttachment/PageAttachmentList.js
  19. 22 20
      src/client/js/components/PageComment/Comment.js
  20. 7 8
      src/client/js/components/PageComment/CommentPreview.js
  21. 2 6
      src/client/js/components/PageComment/DeleteCommentModal.js
  22. 34 26
      src/client/js/components/PageComments.js
  23. 30 23
      src/client/js/components/PageEditor.js
  24. 2 2
      src/client/js/components/PageEditor/AbstractEditor.js
  25. 20 16
      src/client/js/components/PageEditor/Cheatsheet.js
  26. 62 57
      src/client/js/components/PageHistory.js
  27. 6 6
      src/client/js/components/ReactUtils.js
  28. 5 4
      src/client/js/components/SearchForm.js
  29. 31 32
      src/client/js/components/SearchPage.js
  30. 25 27
      src/client/js/components/SearchTypeahead.js
  31. 19 17
      src/client/js/legacy/crowi-admin.js
  32. 4 4
      src/client/js/legacy/crowi-presentation.js
  33. 136 135
      src/client/js/legacy/crowi.js
  34. 14 13
      src/client/js/models/MarkdownTable.js
  35. 44 44
      src/client/js/util/Crowi.js
  36. 17 14
      src/client/js/util/GrowiRenderer.js
  37. 13 13
      src/client/js/util/PostProcessor/CrowiTemplate.js
  38. 2 1
      src/client/js/util/PreProcessor/CsvToTable.js
  39. 3 3
      src/client/js/util/PreProcessor/Linker.js
  40. 4 5
      src/client/js/util/PreProcessor/XssFilter.js
  41. 1 1
      src/client/js/util/codemirror/update-display-util.ext.js
  42. 14 11
      src/client/js/util/interceptor/detach-code-blocks.js
  43. 1 0
      src/client/js/util/markdown-it/blockdiag.js
  44. 4 2
      src/client/js/util/markdown-it/emoji.js
  45. 3 3
      src/client/js/util/markdown-it/header-line-number.js
  46. 1 0
      src/client/js/util/markdown-it/header-with-edit-link.js
  47. 3 3
      src/client/js/util/markdown-it/header.js
  48. 1 1
      src/client/js/util/markdown-it/mathjax.js
  49. 1 0
      src/client/js/util/markdown-it/plantuml.js
  50. 3 1
      src/client/js/util/markdown-it/table-with-handsontable-button.js
  51. 37 34
      src/client/js/util/reveal/plugins/growi-renderer.js
  52. 16 13
      src/lib/service/cdn-resources-downloader.js
  53. 12 13
      src/lib/service/cdn-resources-service.js
  54. 13 15
      src/lib/service/interceptor-manager.js
  55. 11 10
      src/lib/service/logger/index.js
  56. 1 1
      src/lib/service/logger/stream.dev.js
  57. 1 1
      src/lib/service/logger/stream.prod.js
  58. 6 8
      src/lib/service/xss/index.js
  59. 2 2
      src/lib/service/xss/recommendedXssWhiteList.js
  60. 0 2
      src/lib/service/xss/xssOption.js
  61. 9 10
      src/lib/util/path-utils.js
  62. 4 4
      src/lib/util/template-checker.js
  63. 3 5
      src/migrations/20180926134048-make-email-unique.js
  64. 5 5
      src/migrations/20180927102719-init-serverurl.js
  65. 7 5
      src/migrations/20181019114028-abolish-page-group-relation.js
  66. 4 4
      src/server/app.js
  67. 4 6
      src/server/crowi/dev.js
  68. 51 53
      src/server/crowi/express-init.js
  69. 71 79
      src/server/crowi/index.js
  70. 3 3
      src/server/events/page.js
  71. 3 5
      src/server/form/admin/app.js
  72. 3 5
      src/server/form/admin/aws.js
  73. 3 5
      src/server/form/admin/custombehavior.js
  74. 3 5
      src/server/form/admin/customcss.js
  75. 3 5
      src/server/form/admin/customfeatures.js
  76. 3 5
      src/server/form/admin/customheader.js
  77. 3 5
      src/server/form/admin/customhighlightJsStyle.js
  78. 3 5
      src/server/form/admin/customlayout.js
  79. 3 5
      src/server/form/admin/customscript.js
  80. 3 5
      src/server/form/admin/customtheme.js
  81. 3 5
      src/server/form/admin/customtitle.js
  82. 2 4
      src/server/form/admin/importerEsa.js
  83. 2 4
      src/server/form/admin/importerQiita.js
  84. 3 6
      src/server/form/admin/mail.js
  85. 3 5
      src/server/form/admin/markdown.js
  86. 3 4
      src/server/form/admin/markdownPresentation.js
  87. 3 4
      src/server/form/admin/markdownXss.js
  88. 1 3
      src/server/form/admin/notificationGlobal.js
  89. 3 5
      src/server/form/admin/plugin.js
  90. 1 2
      src/server/form/admin/securityGeneral.js
  91. 4 6
      src/server/form/admin/securityGoogle.js
  92. 3 5
      src/server/form/admin/securityMechanism.js
  93. 2 4
      src/server/form/admin/securityPassportGitHub.js
  94. 2 4
      src/server/form/admin/securityPassportGoogle.js
  95. 7 9
      src/server/form/admin/securityPassportLdap.js
  96. 1 2
      src/server/form/admin/securityPassportSaml.js
  97. 2 4
      src/server/form/admin/securityPassportTwitter.js
  98. 3 4
      src/server/form/admin/siteUrl.js
  99. 3 5
      src/server/form/admin/slackIwhSetting.js
  100. 3 4
      src/server/form/admin/slackSetting.js

+ 1 - 0
.eslintignore

@@ -3,5 +3,6 @@
 /node_modules/**
 /public/**
 /src/client/js/legacy/thirdparty-js/**
+/src/client/js/util/reveal/plugins/markdown.js
 /test/**
 /tmp/**

+ 99 - 46
.eslintrc.js

@@ -2,7 +2,7 @@ module.exports = {
   parser: 'babel-eslint',
   extends: [
     'airbnb',
-    'plugin:react/recommended'
+    'plugin:react/recommended',
   ],
   env: {
     browser: true,
@@ -18,34 +18,70 @@ module.exports = {
     hljs: true,
     window: true,
   },
-  plugins: ['react'],
+  plugins: [
+    'react',
+    'chai-friendly',
+  ],
   rules: {
-    "arrow-body-style": ["error", "always"],
-    "brace-style": [
-      "error",
-      "stroustrup", { "allowSingleLine": true }
+    'arrow-body-style': ['error', 'always'],
+    'brace-style': [
+      'error',
+      'stroustrup',
+      { allowSingleLine: true },
+    ],
+    'class-methods-use-this': 'off',
+    'consistent-return': 'off',
+    'default-case': 'off',
+    'func-names': 'off',
+    'global-require': 'off',
+    'key-spacing': [
+      'error',
+      { mode: 'minimum' },
+    ],
+    'max-len': ['error',
+      {
+        code: 160,
+        ignoreTrailingComments: true,
+      },
+    ],
+    'no-continue': 'off',
+    'no-param-reassign': [
+      'error',
+      { props: false },
+    ],
+    'no-plusplus': 'off',
+    // Allow only for-of
+    // https://qiita.com/the_red/items/0c826e97b57da6d67621
+    'no-restricted-syntax': [
+      'error',
+      {
+        selector: 'ForInStatement',
+        message: 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
+      },
+      {
+        selector: 'LabeledStatement',
+        message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
+      },
+      {
+        selector: 'WithStatement',
+        message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
+      },
+    ],
+    'no-shadow': 'off',
+    'no-underscore-dangle': 'off',
+    'no-useless-return': 'off',
+    'prefer-destructuring': 'off',
+    indent: [
+      'error',
+      2,
+      {
+        SwitchCase: 1,
+        ignoredNodes: ['JSXElement *', 'JSXElement', 'JSXAttribute', 'JSXSpreadAttribute'],
+        ArrayExpression: 'first',
+        FunctionDeclaration: { body: 1, parameters: 2 },
+        FunctionExpression: { body: 1, parameters: 2 },
+      },
     ],
-    "global-require": 'off',
-    "key-spacing": ['error', { 'mode': 'minimum' }],
-    // "comma-spacing": [
-    //   "error",
-    //   { "before": false, "after": true }
-    // ],
-    // "func-call-spacing": [
-    //   "error",
-    //   "never"
-    // ],
-    // "indent": [
-    //   "error",
-    //   2,
-    //   {
-    //     "SwitchCase": 1,
-    //     "ignoredNodes": ['JSXElement *', 'JSXElement', "JSXAttribute", "JSXSpreadAttribute"],
-    //     "FunctionDeclaration": {"body": 1, "parameters": 2},
-    //     "FunctionExpression": {"body": 1, "parameters": 2},
-    //     "MemberExpression": "off"
-    //   }
-    // ],
     // "key-spacing": [
     //   "error", {
     //     "beforeColon": false,
@@ -60,32 +96,49 @@ module.exports = {
     //   "error",
     //   "unix"
     // ],
-    // "no-unused-vars": [
-    //   "error",
-    //   { "args": "none" }
-    // ],
-    // "no-var": [ "error" ],
-    // "quotes": [
-    //   "error",
-    //   "single"
-    // ],
+    'no-unused-vars': [
+      'error',
+      { args: 'none' },
+    ],
+    'padded-blocks': [
+      'error',
+      { classes: 'always' },
+    ],
+    'radix': 'off',
+    'react/destructuring-assignment': 'off',
+    'react/forbid-prop-types': 'off',
+    'react/jsx-filename-extension': [
+      'warn',
+      { extensions: ['.jsx']},
+    ],
+    'react/no-unused-prop-types': 'off',
+    'react/jsx-one-expression-per-line': 'off',
+    'react/no-access-state-in-setstate': 'off',
+    'react/prefer-stateless-function': 'off',
+    'react/require-default-props': 'off',
+    'react/self-closing-comp': 'off',
+    'react/sort-comp': 'off',
     // "react/jsx-uses-vars": 1,
     // "react/no-string-refs": "off",
-    "semi": [
-      "error",
-      "always",
-      { "omitLastInOneLineBlock": true }
+    'jsx-a11y/img-redundant-alt': 'off',
+    semi: [
+      'error',
+      'always',
+      { omitLastInOneLineBlock: true },
     ],
     // "space-before-blocks": [
     //   "error",
     //   "always"
     // ],
-    "space-before-function-paren": [
-      "error",
-      "never"
+    'space-before-function-paren': [
+      'error',
+      'never',
     ],
-    "import/no-extraneous-dependencies": 'off',
-    "import/no-dynamic-require": 'off',
-    "import/no-unresolved": [2, { ignore: ['^@'] }],  // ignore @alias/..., @commons/..., ...
+    'import/no-extraneous-dependencies': 'off',
+    'import/no-dynamic-require': 'off',
+    'import/no-unresolved': [2, { ignore: ['^@'] }], // ignore @alias/..., @commons/..., ...
+    // eslint-plugin-chai-friendly rules
+    'no-unused-expressions': 0,
+    'chai-friendly/no-unused-expressions': 2,
   },
 };

+ 3 - 3
bin/shrink-emojione-strategy.js

@@ -15,18 +15,18 @@ const markdownItEmojiFull = require('markdown-it-emoji/lib/data/full.json');
 const OUT = helpers.root('tmp/emoji_strategy_shrinked.json');
 
 const shrinkedMap = {};
-for (const unicode in emojiStrategy) {
+Object.keys(emojiStrategy).forEach((unicode) => {
   const data = emojiStrategy[unicode];
   const shortname = data.shortname.replace(/:/g, '');
 
   // ignore if it isn't included in markdownItEmojiFull
   if (markdownItEmojiFull[shortname] == null) {
-    continue;
+    return;
   }
 
   // add
   shrinkedMap[unicode] = data;
-}
+});
 
 // write
 fs.writeFileSync(OUT, JSON.stringify(shrinkedMap));

+ 1 - 1
config/webpack.prod.js

@@ -35,7 +35,7 @@ module.exports = require('./webpack.common')({
             loader: 'postcss-loader',
             options: {
               sourceMap: false,
-              plugins: (loader) => { // eslint-disable-line no-unused-vars
+              plugins: (loader) => {
                 return [
                   require('autoprefixer')(),
                 ];

+ 2 - 1
package.json

@@ -32,7 +32,7 @@
     "clean:report": "rimraf -- report",
     "clean": "npm-run-all -p clean:*",
     "heroku-postbuild": "sh bin/heroku/install-plugins.sh && npm run build:prod",
-    "lint:js:fix": "eslint . --fix",
+    "lint:js:fix": "eslint src/migrations --fix",
     "lint:js": "eslint .",
     "lint:styles:fix": "prettier-stylelint --quiet --write src/client/styles/scss/**/*.scss",
     "lint:styles": "stylelint src/client/styles/scss/**/*.scss",
@@ -155,6 +155,7 @@
     "eazy-logger": "^3.0.2",
     "eslint": "^5.0.0",
     "eslint-config-airbnb": "^17.1.0",
+    "eslint-plugin-chai-friendly": "^0.4.1",
     "eslint-plugin-import": "^2.16.0",
     "eslint-plugin-jsx-a11y": "^6.2.1",
     "eslint-plugin-react": "^7.12.4",

+ 6 - 9
src/client/js/components/Admin/CustomCssEditor.js

@@ -2,6 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 
 import { UnControlled as CodeMirror } from 'react-codemirror2';
+
 require('codemirror/addon/lint/css-lint');
 require('codemirror/addon/hint/css-hint');
 require('codemirror/addon/hint/show-hint');
@@ -14,10 +15,6 @@ require('jquery-ui/ui/widgets/resizable');
 
 export default class CustomCssEditor extends React.Component {
 
-  constructor(props) {
-    super(props);
-  }
-
   render() {
     // get initial value from inputElem
     const value = this.props.inputElem.value;
@@ -25,24 +22,24 @@ export default class CustomCssEditor extends React.Component {
     return (
       <CodeMirror
         value={value}
-        autoFocus={true}
+        autoFocus
         options={{
           mode: 'css',
           lineNumbers: true,
           tabSize: 2,
           indentUnit: 2,
           theme: 'eclipse',
-          autoRefresh: {force: true},   // force option is enabled by autorefresh.ext.js -- Yuki Takei
+          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
           matchBrackets: true,
           autoCloseBrackets: true,
-          extraKeys: {'Ctrl-Space': 'autocomplete'},
+          extraKeys: { 'Ctrl-Space': 'autocomplete' },
         }}
         editorDidMount={(editor, next) => {
           // resizable with jquery.ui
           $(editor.getWrapperElement()).resizable({
-            resize: function() {
+            resize() {
               editor.setSize($(this).width(), $(this).height());
-            }
+            },
           });
         }}
         onChange={(editor, data, value) => {

+ 6 - 9
src/client/js/components/Admin/CustomHeaderEditor.js

@@ -2,6 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 
 import { UnControlled as CodeMirror } from 'react-codemirror2';
+
 require('codemirror/addon/hint/show-hint');
 require('codemirror/addon/edit/matchbrackets');
 require('codemirror/addon/edit/closebrackets');
@@ -12,10 +13,6 @@ require('jquery-ui/ui/widgets/resizable');
 
 export default class CustomHeaderEditor extends React.Component {
 
-  constructor(props) {
-    super(props);
-  }
-
   render() {
     // get initial value from inputElem
     const value = this.props.inputElem.value;
@@ -23,24 +20,24 @@ export default class CustomHeaderEditor extends React.Component {
     return (
       <CodeMirror
         value={value}
-        autoFocus={true}
+        autoFocus
         options={{
           mode: 'htmlmixed',
           lineNumbers: true,
           tabSize: 2,
           indentUnit: 2,
           theme: 'eclipse',
-          autoRefresh: {force: true},   // force option is enabled by autorefresh.ext.js -- Yuki Takei
+          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
           matchBrackets: true,
           autoCloseBrackets: true,
-          extraKeys: {'Ctrl-Space': 'autocomplete'},
+          extraKeys: { 'Ctrl-Space': 'autocomplete' },
         }}
         editorDidMount={(editor, next) => {
           // resizable with jquery.ui
           $(editor.getWrapperElement()).resizable({
-            resize: function() {
+            resize() {
               editor.setSize($(this).width(), $(this).height());
-            }
+            },
           });
         }}
         onChange={(editor, data, value) => {

+ 6 - 9
src/client/js/components/Admin/CustomScriptEditor.js

@@ -2,6 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 
 import { UnControlled as CodeMirror } from 'react-codemirror2';
+
 require('codemirror/addon/lint/javascript-lint');
 require('codemirror/addon/hint/javascript-hint');
 require('codemirror/addon/hint/show-hint');
@@ -14,10 +15,6 @@ require('jquery-ui/ui/widgets/resizable');
 
 export default class CustomScriptEditor extends React.Component {
 
-  constructor(props) {
-    super(props);
-  }
-
   render() {
     // get initial value from inputElem
     const value = this.props.inputElem.value;
@@ -25,24 +22,24 @@ export default class CustomScriptEditor extends React.Component {
     return (
       <CodeMirror
         value={value}
-        autoFocus={true}
+        autoFocus
         options={{
           mode: 'javascript',
           lineNumbers: true,
           tabSize: 2,
           indentUnit: 2,
           theme: 'eclipse',
-          autoRefresh: {force: true},   // force option is enabled by autorefresh.ext.js -- Yuki Takei
+          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
           matchBrackets: true,
           autoCloseBrackets: true,
-          extraKeys: {'Ctrl-Space': 'autocomplete'},
+          extraKeys: { 'Ctrl-Space': 'autocomplete' },
         }}
         editorDidMount={(editor, next) => {
           // resizable with jquery.ui
           $(editor.getWrapperElement()).resizable({
-            resize: function() {
+            resize() {
               editor.setSize($(this).width(), $(this).height());
-            }
+            },
           });
         }}
         onChange={(editor, data, value) => {

+ 1 - 1
src/client/js/components/Common/UserDate.js

@@ -19,6 +19,7 @@ export default class UserDate extends React.Component {
       </span>
     );
   }
+
 }
 
 UserDate.propTypes = {
@@ -32,4 +33,3 @@ UserDate.defaultProps = {
   format: 'YYYY/MM/DD HH:mm:ss',
   className: '',
 };
-

+ 15 - 7
src/client/js/components/CopyButton.js

@@ -23,7 +23,7 @@ export default class CopyButton extends React.Component {
 
   render() {
     const containerStyle = {
-      lineHeight: 0
+      lineHeight: 0,
     };
     const style = Object.assign({
       padding: '0 2px',
@@ -34,16 +34,25 @@ export default class CopyButton extends React.Component {
 
     return (
       <span className="btn-copy-container" style={containerStyle}>
-        <ClipboardButton className={this.props.buttonClassName}
-            button-id={this.props.buttonId} button-data-toggle="tooltip" button-data-container="body" button-title="copied!" button-data-placement="bottom" button-data-trigger="manual"
-            button-style={style}
-            data-clipboard-text={text} onSuccess={this.showToolTip}>
+        <ClipboardButton
+          className={this.props.buttonClassName}
+          button-id={this.props.buttonId}
+          button-data-toggle="tooltip"
+          button-data-container="body"
+          button-title="copied!"
+          button-data-placement="bottom"
+          button-data-trigger="manual"
+          button-style={style}
+          data-clipboard-text={text}
+          onSuccess={this.showToolTip}
+        >
 
-          <i className={this.props.iconClassName}></i>
+          <i className={this.props.iconClassName} />
         </ClipboardButton>
       </span>
     );
   }
+
 }
 
 CopyButton.propTypes = {
@@ -54,6 +63,5 @@ CopyButton.propTypes = {
   iconClassName: PropTypes.string.isRequired,
 };
 CopyButton.defaultProps = {
-  buttonId: 'btnCopy',
   buttonStyle: {},
 };

+ 4 - 3
src/client/js/components/Page/PagePath.js

@@ -10,14 +10,16 @@ export default class PagePath extends React.Component {
   render() {
     const page = this.props.page;
     const shortPath = this.getShortPath(page.path);
-    const pathPrefix = page.path.replace(new RegExp(shortPath + '(/)?$'), '');
+    const pathPrefix = page.path.replace(new RegExp(`${shortPath}(/)?$`), '');
 
     return (
       <span className="page-path">
-        {pathPrefix}<strong>{shortPath}</strong>
+        {pathPrefix}
+        <strong>{shortPath}</strong>
       </span>
     );
   }
+
 }
 
 PagePath.propTypes = {
@@ -25,5 +27,4 @@ PagePath.propTypes = {
 };
 
 PagePath.defaultProps = {
-  page: {},
 };

+ 6 - 4
src/client/js/components/Page/RevisionBody.js

@@ -39,7 +39,7 @@ export default class RevisionBody extends React.Component {
   }
 
   generateInnerHtml(html) {
-    return {__html: html};
+    return { __html: html };
   }
 
   render() {
@@ -52,15 +52,17 @@ export default class RevisionBody extends React.Component {
             this.props.inputRef(elm);
           }
         }}
-        className={`wiki ${additionalClassName}`} dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}>
-      </div>
+        className={`wiki ${additionalClassName}`}
+        dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}
+      />
     );
   }
+
 }
 
 RevisionBody.propTypes = {
   html: PropTypes.string,
-  inputRef: PropTypes.func,  // for getting div element
+  inputRef: PropTypes.func, // for getting div element
   isMathJaxEnabled: PropTypes.bool,
   renderMathJaxOnInit: PropTypes.bool,
   renderMathJaxInRealtime: PropTypes.bool,

+ 23 - 16
src/client/js/components/Page/RevisionPath.js

@@ -24,25 +24,25 @@ export default class RevisionPath extends React.Component {
     this.setState({ isListPage });
 
     // whether set link to '/'
-    const behaviorType = this.props.crowi.getConfig()['behaviorType'];
-    const isLinkToListPage = (!behaviorType || 'crowi' === behaviorType);
+    const behaviorType = this.props.crowi.getConfig().behaviorType;
+    const isLinkToListPage = (!behaviorType || behaviorType === 'crowi');
     this.setState({ isLinkToListPage });
 
     // generate pages obj
-    let splitted = this.props.pagePath.split(/\//);
-    splitted.shift();   // omit first element with shift()
-    if (splitted[splitted.length-1] === '') {
-      splitted.pop();   // omit last element with unshift()
+    const splitted = this.props.pagePath.split(/\//);
+    splitted.shift(); // omit first element with shift()
+    if (splitted[splitted.length - 1] === '') {
+      splitted.pop(); // omit last element with unshift()
     }
 
-    let pages = [];
+    const pages = [];
     let parentPath = '/';
     splitted.forEach((pageName) => {
       pages.push({
         pagePath: parentPath + encodeURIComponent(pageName),
         pageName: this.xss.process(pageName),
       });
-      parentPath += pageName + '/';
+      parentPath += `${pageName}/`;
     });
 
     this.setState({ pages });
@@ -56,8 +56,9 @@ export default class RevisionPath extends React.Component {
   }
 
   generateLinkElementToListPage(pagePath, isLinkToListPage, isLastElement) {
+    /* eslint-disable no-else-return */
     if (isLinkToListPage) {
-      return <a href={pagePath+'/'} className={(isLastElement && !this.state.isListPage) ? 'last-path' : ''}>/</a>;
+      return <a href={`${pagePath}/`} className={(isLastElement && !this.state.isListPage) ? 'last-path' : ''}>/</a>;
     }
     else if (!isLastElement) {
       return <span>/</span>;
@@ -65,6 +66,7 @@ export default class RevisionPath extends React.Component {
     else {
       return <span></span>;
     }
+    /* eslint-enable no-else-return */
   }
 
   render() {
@@ -85,20 +87,20 @@ export default class RevisionPath extends React.Component {
 
     const afterElements = [];
     this.state.pages.forEach((page, index) => {
-      const isLastElement = (index == pageLength-1);
+      const isLastElement = (index === pageLength - 1);
 
       // add elements for page
       afterElements.push(
         <span key={page.pagePath} className="path-segment">
           <a href={page.pagePath}>{page.pageName}</a>
-        </span>
+        </span>,
       );
 
       // add elements for '/'
       afterElements.push(
-        <span key={page.pagePath+'/'} className="separator" style={separatorStyle}>
+        <span key={`${page.pagePath}/`} className="separator" style={separatorStyle}>
           {this.generateLinkElementToListPage(page.pagePath, this.state.isLinkToListPage, isLastElement)}
-        </span>
+        </span>,
       );
     });
 
@@ -108,14 +110,19 @@ export default class RevisionPath extends React.Component {
           <a href="/">/</a>
         </span>
         {afterElements}
-        <CopyButton buttonId="btnCopyRevisionPath" text={this.props.pagePath}
-            buttonClassName="btn btn-default btn-copy" iconClassName="ti-clipboard" />
+        <CopyButton
+          buttonId="btnCopyRevisionPath"
+          text={this.props.pagePath}
+          buttonClassName="btn btn-default btn-copy"
+          iconClassName="ti-clipboard"
+        />
         <a href="#edit" className="btn btn-default btn-edit" style={editButtonStyle}>
-          <i className="icon-note"></i>
+          <i className="icon-note" />
         </a>
       </span>
     );
   }
+
 }
 
 RevisionPath.propTypes = {

+ 12 - 6
src/client/js/components/Page/RevisionUrl.js

@@ -14,24 +14,30 @@ export default class RevisionUrl extends React.Component {
 
   render() {
     const buttonStyle = {
-      fontSize: '1em'
+      fontSize: '1em',
     };
 
     const pagePath = this.xss.process(this.props.pagePath);
 
     const url = (this.props.pageId == null)
-      ? decodeURIComponent(location.href)
-      : `${location.origin}/${this.props.pageId}`;
-    const copiedText = pagePath + '\n' + url;
+      ? decodeURIComponent(window.location.href)
+      : `${window.location.origin}/${this.props.pageId}`;
+    const copiedText = `${pagePath}\n${url}`;
 
     return (
       <span>
         {url}
-        <CopyButton buttonId="btnCopyRevisionUrl" text={copiedText}
-            buttonClassName="btn btn-default btn-copy-link" buttonStyle={buttonStyle} iconClassName="ti-clipboard" />
+        <CopyButton
+          buttonId="btnCopyRevisionUrl"
+          text={copiedText}
+          buttonClassName="btn btn-default btn-copy-link"
+          buttonStyle={buttonStyle}
+          iconClassName="ti-clipboard"
+        />
       </span>
     );
   }
+
 }
 
 RevisionUrl.propTypes = {

+ 16 - 11
src/client/js/components/PageAttachment.js

@@ -1,3 +1,4 @@
+/* eslint-disable react/no-access-state-in-setstate */
 import React from 'react';
 import PropTypes from 'prop-types';
 
@@ -5,6 +6,7 @@ import PageAttachmentList from './PageAttachment/PageAttachmentList';
 import DeleteAttachmentModal from './PageAttachment/DeleteAttachmentModal';
 
 export default class PageAttachment extends React.Component {
+
   constructor(props) {
     super(props);
 
@@ -24,21 +26,21 @@ export default class PageAttachment extends React.Component {
     const pageId = this.props.pageId;
 
     if (!pageId) {
-      return ;
+      return;
     }
 
-    this.props.crowi.apiGet('/attachments.list', {page_id: pageId })
-      .then(res => {
+    this.props.crowi.apiGet('/attachments.list', { page_id: pageId })
+      .then((res) => {
         const attachments = res.attachments;
-        let inUse = {};
+        const inUse = {};
 
         for (const attachment of attachments) {
           inUse[attachment._id] = this.checkIfFileInUse(attachment);
         }
 
         this.setState({
-          attachments: attachments,
-          inUse: inUse,
+          attachments,
+          inUse,
         });
       });
   }
@@ -62,16 +64,18 @@ export default class PageAttachment extends React.Component {
       deleting: true,
     });
 
-    this.props.crowi.apiPost('/attachments.remove', {attachment_id: attachmentId})
-      .then(res => {
+    this.props.crowi.apiPost('/attachments.remove', { attachment_id: attachmentId })
+      .then((res) => {
         this.setState({
           attachments: this.state.attachments.filter((at) => {
+            // comparing ObjectId
+            // eslint-disable-next-line eqeqeq
             return at._id != attachmentId;
           }),
           attachmentToDelete: null,
           deleting: false,
         });
-      }).catch(err => {
+      }).catch((err) => {
         this.setState({
           deleteError: 'Something went wrong.',
           deleting: false,
@@ -87,10 +91,10 @@ export default class PageAttachment extends React.Component {
     let deleteAttachmentModal = '';
     if (this.isUserLoggedIn()) {
       const attachmentToDelete = this.state.attachmentToDelete;
-      let deleteModalClose = () => {
+      const deleteModalClose = () => {
         this.setState({ attachmentToDelete: null, deleteError: '' });
       };
-      let showModal = attachmentToDelete !== null;
+      const showModal = attachmentToDelete !== null;
 
       let deleteInUse = null;
       if (attachmentToDelete !== null) {
@@ -126,6 +130,7 @@ export default class PageAttachment extends React.Component {
       </div>
     );
   }
+
 }
 
 PageAttachment.propTypes = {

+ 10 - 5
src/client/js/components/PageAttachment/Attachment.js

@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 import User from '../User/User';
 
 export default class Attachment extends React.Component {
+
   constructor(props) {
     super(props);
 
@@ -36,19 +37,22 @@ export default class Attachment extends React.Component {
     const btnDownload = (this.props.isUserLoggedIn)
       ? (
         <a className="attachment-download" href={attachment.downloadPathProxied}>
-          <i className="icon-cloud-download"></i>
-        </a>)
+          <i className="icon-cloud-download" />
+        </a>
+      )
       : '';
 
     const btnTrash = (this.props.isUserLoggedIn)
       ? (
+        /* eslint-disable-next-line */
         <a className="text-danger attachment-delete" onClick={this._onAttachmentDeleteClicked}>
-          <i className="icon-trash"></i>
-        </a>)
+          <i className="icon-trash" />
+        </a>
+      )
       : '';
 
     return (
-      <li className='attachment'>
+      <li className="attachment">
         <User user={attachment.creator} />
 
         <a href={attachment.filePathProxied}><i className={formatIcon}></i> {attachment.originalName}</a>
@@ -62,6 +66,7 @@ export default class Attachment extends React.Component {
       </li>
     );
   }
+
 }
 
 Attachment.propTypes = {

+ 11 - 5
src/client/js/components/PageAttachment/DeleteAttachmentModal.js

@@ -1,3 +1,4 @@
+/* eslint-disable react/prop-types */
 import React from 'react';
 import Button from 'react-bootstrap/es/Button';
 import Modal from 'react-bootstrap/es/Modal';
@@ -5,6 +6,7 @@ import Modal from 'react-bootstrap/es/Modal';
 import User from '../User/User';
 
 export default class DeleteAttachmentModal extends React.Component {
+
   constructor(props) {
     super(props);
 
@@ -25,7 +27,7 @@ export default class DeleteAttachmentModal extends React.Component {
 
   renderByFileFormat(attachment) {
     const content = (attachment.fileFormat.match(/image\/.+/i))
-      ? <img src={attachment.filePathProxied} />
+      ? <img src={attachment.filePathProxied} alt="deleting image" />
       : '';
 
 
@@ -63,7 +65,7 @@ export default class DeleteAttachmentModal extends React.Component {
       deletingIndicator = <span>{this.props.deleteError}</span>;
     }
 
-    let renderAttachment = this.renderByFileFormat(attachment);
+    const renderAttachment = this.renderByFileFormat(attachment);
 
     return (
       <Modal {...props} className="attachment-delete-modal" bsSize="large" aria-labelledby="contained-modal-title-lg">
@@ -77,11 +79,15 @@ export default class DeleteAttachmentModal extends React.Component {
           <div className="mr-3 d-inline-block">
             {deletingIndicator}
           </div>
-          <Button onClick={this._onDeleteConfirm} bsStyle="danger"
-            disabled={this.props.deleting}>Delete!</Button>
+          <Button
+            onClick={this._onDeleteConfirm}
+            bsStyle="danger"
+            disabled={this.props.deleting}
+          >Delete!
+          </Button>
         </Modal.Footer>
       </Modal>
     );
   }
-}
 
+}

+ 7 - 6
src/client/js/components/PageAttachment/PageAttachmentList.js

@@ -1,3 +1,4 @@
+/* eslint-disable react/prop-types */
 import React from 'react';
 
 import Attachment from './Attachment';
@@ -12,25 +13,25 @@ export default class PageAttachmentList extends React.Component {
     const attachmentList = this.props.attachments.map((attachment, idx) => {
       return (
         <Attachment
-          key={'page:attachment:' + attachment._id}
+          key={`page:attachment:${attachment._id}`}
           attachment={attachment}
           inUse={this.props.inUse[attachment._id] || false}
           onAttachmentDeleteClicked={this.props.onAttachmentDeleteClicked}
           isUserLoggedIn={this.props.isUserLoggedIn}
-         />
+        />
       );
     });
 
     return (
       <div>
-        {(attachmentList.length != 0) &&
-          <h5><strong>Attachments</strong></h5>
+        {(attachmentList.length !== 0)
+          && <h5><strong>Attachments</strong></h5>
         }
-        <ul className='pl-2'>
+        <ul className="pl-2">
           {attachmentList}
         </ul>
       </div>
     );
   }
-}
 
+}

+ 22 - 20
src/client/js/components/PageComment/Comment.js

@@ -41,9 +41,8 @@ export default class Comment extends React.Component {
     this.renderHtml(nextProps.comment.comment);
   }
 
-  //not used
+  // not used
   setMarkdown(markdown) {
-    this.setState({ markdown });
     this.renderHtml(markdown);
   }
 
@@ -56,13 +55,13 @@ export default class Comment extends React.Component {
   }
 
   getRootClassName() {
-    return 'page-comment '
-        + (this.isCurrentUserEqualsToAuthor() ? 'page-comment-me ' : '');
+    return `page-comment ${
+      this.isCurrentUserEqualsToAuthor() ? 'page-comment-me ' : ''}`;
   }
 
   getRevisionLabelClassName() {
-    return 'page-comment-revision label '
-        + (this.isCurrentRevision() ? 'label-primary' : 'label-default');
+    return `page-comment-revision label ${
+      this.isCurrentRevision() ? 'label-primary' : 'label-default'}`;
   }
 
   deleteBtnClickedHandler() {
@@ -73,10 +72,12 @@ export default class Comment extends React.Component {
     const config = this.props.crowi.getConfig();
     const isMathJaxEnabled = !!config.env.MATHJAX;
     return (
-      <RevisionBody html={this.state.html}
-          isMathJaxEnabled={isMathJaxEnabled}
-          renderMathJaxOnInit={true}
-          additionalClassName="comment" />
+      <RevisionBody
+        html={this.state.html}
+        isMathJaxEnabled={isMathJaxEnabled}
+        renderMathJaxOnInit
+        additionalClassName="comment"
+      />
     );
   }
 
@@ -88,26 +89,26 @@ export default class Comment extends React.Component {
     const crowiRenderer = this.props.crowiRenderer;
     const interceptorManager = this.props.crowi.interceptorManager;
     interceptorManager.process('preRenderComment', context)
-      .then(() => interceptorManager.process('prePreProcess', context))
+      .then(() => { return interceptorManager.process('prePreProcess', context) })
       .then(() => {
         context.markdown = crowiRenderer.preProcess(context.markdown);
       })
-      .then(() => interceptorManager.process('postPreProcess', context))
+      .then(() => { return interceptorManager.process('postPreProcess', context) })
       .then(() => {
-        var parsedHTML = crowiRenderer.process(context.markdown);
-        context['parsedHTML'] = parsedHTML;
+        const parsedHTML = crowiRenderer.process(context.markdown);
+        context.parsedHTML = parsedHTML;
       })
-      .then(() => interceptorManager.process('prePostProcess', context))
+      .then(() => { return interceptorManager.process('prePostProcess', context) })
       .then(() => {
         context.parsedHTML = crowiRenderer.postProcess(context.parsedHTML);
       })
-      .then(() => interceptorManager.process('postPostProcess', context))
-      .then(() => interceptorManager.process('preRenderCommentHtml', context))
+      .then(() => { return interceptorManager.process('postPostProcess', context) })
+      .then(() => { return interceptorManager.process('preRenderCommentHtml', context) })
       .then(() => {
         this.setState({ html: context.parsedHTML });
       })
       // process interceptors for post rendering
-      .then(() => interceptorManager.process('postRenderCommentHtml', context));
+      .then(() => { return interceptorManager.process('postRenderCommentHtml', context) });
 
   }
 
@@ -118,7 +119,7 @@ export default class Comment extends React.Component {
 
     const rootClassName = this.getRootClassName();
     const commentDate = dateFnsFormat(comment.createdAt, 'YYYY/MM/DD HH:mm');
-    const commentBody = isMarkdown ? this.renderRevisionBody(): ReactUtils.nl2br(comment.comment);
+    const commentBody = isMarkdown ? this.renderRevisionBody() : ReactUtils.nl2br(comment.comment);
     const creatorsPage = `/user/${creator.username}`;
     const revHref = `?revision=${comment.revision}`;
     const revFirst8Letters = comment.revision.substr(-8);
@@ -139,7 +140,7 @@ export default class Comment extends React.Component {
             <a className={revisionLavelClassName} href={revHref}>{revFirst8Letters}</a>
           </div>
           <div className="page-comment-control">
-            <button className="btn btn-link" onClick={this.deleteBtnClickedHandler}>
+            <button type="button" className="btn btn-link" onClick={this.deleteBtnClickedHandler}>
               <i className="ti-close"></i>
             </button>
           </div>
@@ -147,6 +148,7 @@ export default class Comment extends React.Component {
       </div>
     );
   }
+
 }
 
 Comment.propTypes = {

+ 7 - 8
src/client/js/components/PageComment/CommentPreview.js

@@ -8,17 +8,15 @@ import RevisionBody from '../Page/RevisionBody';
  */
 export default class CommentPreview extends React.Component {
 
-  constructor(props) {
-    super(props);
-  }
-
   render() {
     return (
-      <div className="page-comment-preview-body"
-          ref={(elm) => {
+      <div
+        className="page-comment-preview-body"
+        ref={(elm) => {
             this.previewElement = elm;
             this.props.inputRef(elm);
-          }}>
+          }}
+      >
 
         <RevisionBody
           {...this.props}
@@ -27,9 +25,10 @@ export default class CommentPreview extends React.Component {
       </div>
     );
   }
+
 }
 
 CommentPreview.propTypes = {
   html: PropTypes.string,
-  inputRef: PropTypes.func.isRequired,  // for getting div element
+  inputRef: PropTypes.func.isRequired, // for getting div element
 };

+ 2 - 6
src/client/js/components/PageComment/DeleteCommentModal.js

@@ -16,10 +16,6 @@ export default class DeleteCommentModal extends React.Component {
    */
   static get OMIT_BODY_THRES() { return 400 }
 
-  constructor(props) {
-    super(props);
-  }
-
   componentWillMount() {
   }
 
@@ -34,7 +30,7 @@ export default class DeleteCommentModal extends React.Component {
     // generate body
     let commentBody = comment.comment;
     if (commentBody.length > DeleteCommentModal.OMIT_BODY_THRES) { // omit
-      commentBody = commentBody.substr(0, DeleteCommentModal.OMIT_BODY_THRES) + '...';
+      commentBody = `${commentBody.substr(0, DeleteCommentModal.OMIT_BODY_THRES)}...`;
     }
     commentBody = ReactUtils.nl2br(commentBody);
 
@@ -68,6 +64,6 @@ DeleteCommentModal.propTypes = {
   isShown: PropTypes.bool.isRequired,
   comment: PropTypes.object,
   errorMessage: PropTypes.string,
-  cancel: PropTypes.func.isRequired,            // for cancel evnet handling
+  cancel: PropTypes.func.isRequired, // for cancel evnet handling
   confirmedToDelete: PropTypes.func.isRequired, // for confirmed event handling
 };

+ 34 - 26
src/client/js/components/PageComments.js

@@ -1,3 +1,4 @@
+/* eslint-disable react/no-access-state-in-setstate */
 import React from 'react';
 import PropTypes from 'prop-types';
 
@@ -32,7 +33,7 @@ export default class PageComments extends React.Component {
       errorMessageForDeleting: undefined,
     };
 
-    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, {mode: 'comment'});
+    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, { mode: 'comment' });
 
     this.init = this.init.bind(this);
     this.confirmToDeleteComment = this.confirmToDeleteComment.bind(this);
@@ -51,8 +52,8 @@ export default class PageComments extends React.Component {
       return;
     }
 
-    const layoutType = this.props.crowi.getConfig()['layoutType'];
-    this.setState({isLayoutTypeGrowi: 'crowi-plus' === layoutType || 'growi' === layoutType});
+    const layoutType = this.props.crowi.getConfig().layoutType;
+    this.setState({ isLayoutTypeGrowi: layoutType === 'crowi-plus' || layoutType === 'growi' });
 
     this.retrieveData();
   }
@@ -62,35 +63,36 @@ export default class PageComments extends React.Component {
    */
   retrieveData() {
     // get data (desc order array)
-    this.props.crowi.apiGet('/comments.get', {page_id: this.props.pageId})
-      .then(res => {
+    this.props.crowi.apiGet('/comments.get', { page_id: this.props.pageId })
+      .then((res) => {
         if (res.ok) {
-          this.setState({comments: res.comments});
+          this.setState({ comments: res.comments });
         }
       });
   }
 
   confirmToDeleteComment(comment) {
-    this.setState({commentToDelete: comment});
+    this.setState({ commentToDelete: comment });
     this.showDeleteConfirmModal();
   }
 
   deleteComment() {
     const comment = this.state.commentToDelete;
 
-    this.props.crowi.apiPost('/comments.remove', {comment_id: comment._id})
-    .then(res => {
-      if (res.ok) {
-        this.findAndSplice(comment);
-      }
-      this.closeDeleteConfirmModal();
-    }).catch(err => {
-      this.setState({errorMessageForDeleting: err.message});
-    });
+    this.props.crowi.apiPost('/comments.remove', { comment_id: comment._id })
+      .then((res) => {
+        if (res.ok) {
+          this.findAndSplice(comment);
+        }
+        this.closeDeleteConfirmModal();
+      })
+      .catch((err) => {
+        this.setState({ errorMessageForDeleting: err.message });
+      });
   }
 
   findAndSplice(comment) {
-    let comments = this.state.comments;
+    const comments = this.state.comments;
 
     const index = comments.indexOf(comment);
     if (index < 0) {
@@ -98,11 +100,11 @@ export default class PageComments extends React.Component {
     }
     comments.splice(index, 1);
 
-    this.setState({comments});
+    this.setState({ comments });
   }
 
   showDeleteConfirmModal() {
-    this.setState({isDeleteConfirmModalShown: true});
+    this.setState({ isDeleteConfirmModalShown: true });
   }
 
   closeDeleteConfirmModal() {
@@ -123,35 +125,40 @@ export default class PageComments extends React.Component {
   generateCommentElements(comments) {
     return comments.map((comment) => {
       return (
-        <Comment key={comment._id} comment={comment}
+        <Comment
+          key={comment._id}
+          comment={comment}
           currentUserId={this.props.crowi.me}
           currentRevisionId={this.props.revisionId}
           deleteBtnClicked={this.confirmToDeleteComment}
           crowi={this.props.crowi}
-          crowiRenderer={this.growiRenderer} />
+          crowiRenderer={this.growiRenderer}
+        />
       );
     });
   }
 
   render() {
-    let currentComments = [];
-    let newerComments = [];
-    let olderComments = [];
+    const currentComments = [];
+    const newerComments = [];
+    const olderComments = [];
 
     let comments = this.state.comments;
     if (this.state.isLayoutTypeGrowi) {
       // replace with asc order array
-      comments = comments.slice().reverse();  // non-destructive reverse
+      comments = comments.slice().reverse(); // non-destructive reverse
     }
 
     // divide by revisionId and createdAt
     const revisionId = this.props.revisionId;
     const revisionCreatedAt = this.props.revisionCreatedAt;
     comments.forEach((comment) => {
+      // comparing ObjectId
+      // eslint-disable-next-line eqeqeq
       if (comment.revision == revisionId) {
         currentComments.push(comment);
       }
-      else if (Date.parse(comment.createdAt)/1000 > revisionCreatedAt) {
+      else if (Date.parse(comment.createdAt) / 1000 > revisionCreatedAt) {
         newerComments.push(comment);
       }
       else {
@@ -237,6 +244,7 @@ export default class PageComments extends React.Component {
       </div>
     );
   }
+
 }
 
 PageComments.propTypes = {

+ 30 - 23
src/client/js/components/PageEditor.js

@@ -3,13 +3,13 @@ import PropTypes from 'prop-types';
 
 import { throttle, debounce } from 'throttle-debounce';
 
+import * as toastr from 'toastr';
 import GrowiRenderer from '../util/GrowiRenderer';
 
 import { EditorOptions, PreviewOptions } from './PageEditor/OptionsSelector';
 import Editor from './PageEditor/Editor';
 import Preview from './PageEditor/Preview';
 import scrollSyncHelper from './PageEditor/ScrollSyncHelper';
-import * as toastr from 'toastr';
 
 
 export default class PageEditor extends React.Component {
@@ -33,7 +33,7 @@ export default class PageEditor extends React.Component {
       previewOptions: this.props.previewOptions,
     };
 
-    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiRenderer, {mode: 'editor'});
+    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiRenderer, { mode: 'editor' });
 
     this.setCaretLine = this.setCaretLine.bind(this);
     this.focusToEditor = this.focusToEditor.bind(this);
@@ -71,12 +71,12 @@ export default class PageEditor extends React.Component {
   setMarkdown(markdown, updateEditorValue = true) {
     this.setState({ markdown });
     if (updateEditorValue) {
-      this.refs.editor.setValue(markdown);
+      this.editor.setValue(markdown);
     }
   }
 
   focusToEditor() {
-    this.refs.editor.forceToFocus();
+    this.editor.forceToFocus();
   }
 
   /**
@@ -84,7 +84,7 @@ export default class PageEditor extends React.Component {
    * @param {number} line
    */
   setCaretLine(line) {
-    this.refs.editor.setCaretLine(line);
+    this.editor.setCaretLine(line);
     scrollSyncHelper.scrollPreview(this.previewElement, line);
   }
 
@@ -119,7 +119,7 @@ export default class PageEditor extends React.Component {
    */
   async onUpload(file) {
     try {
-      let res  = await this.props.crowi.apiGet('/attachments.limit', {_csrf: this.props.crowi.csrfToken, fileSize: file.size});
+      let res = await this.props.crowi.apiGet('/attachments.limit', { _csrf: this.props.crowi.csrfToken, fileSize: file.size });
       if (!res.isUploadable) {
         toastr.error(undefined, 'MongoDB for uploading files reaches limit', {
           closeButton: true,
@@ -149,9 +149,9 @@ export default class PageEditor extends React.Component {
       // when image
       if (attachment.fileFormat.startsWith('image/')) {
         // modify to "![fileName](url)" syntax
-        insertText = '!' + insertText;
+        insertText = `!${insertText}`;
       }
-      this.refs.editor.insertText(insertText);
+      this.editor.insertText(insertText);
 
       // when if created newly
       if (res.pageCreated) {
@@ -162,13 +162,14 @@ export default class PageEditor extends React.Component {
       this.apiErrorHandler(e);
     }
     finally {
-      this.refs.editor.terminateUploadingState();
+      this.editor.terminateUploadingState();
     }
   }
 
   /**
    * the scroll event handler from codemirror
-   * @param {any} data {left, top, width, height, clientWidth, clientHeight} object that represents the current scroll position, the size of the scrollable area, and the size of the visible area (minus scrollbars).
+   * @param {any} data {left, top, width, height, clientWidth, clientHeight} object that represents the current scroll position,
+   *                    the size of the scrollable area, and the size of the visible area (minus scrollbars).
    *                    And data.line is also available that is added by Editor component
    * @see https://codemirror.net/doc/manual.html#events
    */
@@ -253,13 +254,13 @@ export default class PageEditor extends React.Component {
 
     // prevent circular invocation
     if (this.isOriginOfScrollSyncEditor) {
-      this.isOriginOfScrollSyncEditor = false;  // turn off the flag
+      this.isOriginOfScrollSyncEditor = false; // turn off the flag
       return;
     }
 
     // turn on the flag
     this.isOriginOfScrollSyncPreview = true;
-    scrollSyncHelper.scrollEditor(this.refs.editor, this.previewElement, offset);
+    scrollSyncHelper.scrollEditor(this.editor, this.previewElement, offset);
   }
 
   saveDraft() {
@@ -268,6 +269,7 @@ export default class PageEditor extends React.Component {
       this.props.crowi.saveDraft(this.props.pagePath, this.state.markdown);
     }
   }
+
   clearDraft() {
     this.props.crowi.clearDraft(this.props.pagePath);
   }
@@ -278,32 +280,32 @@ export default class PageEditor extends React.Component {
     // render html
     const context = {
       markdown: this.state.markdown,
-      currentPagePath: decodeURIComponent(location.pathname)
+      currentPagePath: decodeURIComponent(window.location.pathname),
     };
 
     const growiRenderer = this.growiRenderer;
     const interceptorManager = this.props.crowi.interceptorManager;
     interceptorManager.process('preRenderPreview', context)
-      .then(() => interceptorManager.process('prePreProcess', context))
+      .then(() => { return interceptorManager.process('prePreProcess', context) })
       .then(() => {
         context.markdown = growiRenderer.preProcess(context.markdown);
       })
-      .then(() => interceptorManager.process('postPreProcess', context))
+      .then(() => { return interceptorManager.process('postPreProcess', context) })
       .then(() => {
         const parsedHTML = growiRenderer.process(context.markdown);
-        context['parsedHTML'] = parsedHTML;
+        context.parsedHTML = parsedHTML;
       })
-      .then(() => interceptorManager.process('prePostProcess', context))
+      .then(() => { return interceptorManager.process('prePostProcess', context) })
       .then(() => {
         context.parsedHTML = growiRenderer.postProcess(context.parsedHTML);
       })
-      .then(() => interceptorManager.process('postPostProcess', context))
-      .then(() => interceptorManager.process('preRenderPreviewHtml', context))
+      .then(() => { return interceptorManager.process('postPostProcess', context) })
+      .then(() => { return interceptorManager.process('preRenderPreviewHtml', context) })
       .then(() => {
         this.setState({ html: context.parsedHTML });
       })
       // process interceptors for post rendering
-      .then(() => interceptorManager.process('postRenderPreviewHtml', context));
+      .then(() => { return interceptorManager.process('postRenderPreviewHtml', context) });
 
   }
 
@@ -326,7 +328,9 @@ export default class PageEditor extends React.Component {
     return (
       <div className="row">
         <div className="col-md-6 col-sm-12 page-editor-editor-container">
-          <Editor ref="editor" value={this.state.markdown}
+          <Editor
+            ref={(c) => { this.editor = c }}
+            value={this.state.markdown}
             editorOptions={this.state.editorOptions}
             noCdn={noCdn}
             isMobile={this.props.crowi.isMobile}
@@ -343,8 +347,10 @@ export default class PageEditor extends React.Component {
           />
         </div>
         <div className="col-md-6 hidden-sm hidden-xs page-editor-preview-container">
-          <Preview html={this.state.html}
-            inputRef={el => this.previewElement = el}
+          <Preview
+            html={this.state.html}
+            // eslint-disable-next-line no-return-assign
+            inputRef={(el) => { return this.previewElement = el }}
             isMathJaxEnabled={this.state.isMathJaxEnabled}
             renderMathJaxOnInit={false}
             previewOptions={this.state.previewOptions}
@@ -354,6 +360,7 @@ export default class PageEditor extends React.Component {
       </div>
     );
   }
+
 }
 
 PageEditor.propTypes = {

+ 2 - 2
src/client/js/components/PageEditor/AbstractEditor.js

@@ -116,11 +116,12 @@ export default class AbstractEditor extends React.Component {
   getNavbarItems() {
     return null;
   }
+
 }
 
 AbstractEditor.propTypes = {
   value: PropTypes.string,
-  ifGfmMode: PropTypes.bool,
+  isGfmMode: PropTypes.bool,
   editorOptions: PropTypes.object,
   onChange: PropTypes.func,
   onScroll: PropTypes.func,
@@ -133,4 +134,3 @@ AbstractEditor.propTypes = {
 AbstractEditor.defaultProps = {
   isGfmMode: true,
 };
-

+ 20 - 16
src/client/js/components/PageEditor/Cheatsheet.js

@@ -1,8 +1,11 @@
+/* eslint-disable max-len */
+
 import React from 'react';
 import PropTypes from 'prop-types';
 import { translate } from 'react-i18next';
 
 class Cheatsheet extends React.Component {
+
   render() {
     const { t } = this.props;
 
@@ -11,9 +14,9 @@ class Cheatsheet extends React.Component {
         <div className="col-sm-6">
           <h4>{t('sandbox.header')}</h4>
           <ul className="hljs">
-            <li><code># </code>{t('sandbox.header_x', {index: '1'})}</li>
-            <li><code>## </code>{t('sandbox.header_x', {index: '2'})}</li>
-            <li><code>### </code>{t('sandbox.header_x', {index: '3'})}</li>
+            <li><code># </code>{t('sandbox.header_x', { index: '1' })}</li>
+            <li><code>## </code>{t('sandbox.header_x', { index: '2' })}</li>
+            <li><code>### </code>{t('sandbox.header_x', { index: '3' })}</li>
           </ul>
           <h4>{t('sandbox.block')}</h4>
           <p className="mb-1"><code>[{t('sandbox.empty_line')}]</code>{t('sandbox.block_detail')}</p>
@@ -33,7 +36,7 @@ class Cheatsheet extends React.Component {
             <li><i>*{t('sandbox.italics')}*</i></li>
             <li><b>**{t('sandbox.bold')}**</b></li>
             <li><i><b>***{t('sandbox.italic_bold')}***</b></i></li>
-            <li>~~{t('sandbox.strikethrough')}~~ => <s>{t('sandbox.strikethrough')}</s></li>
+            <li>~~{t('sandbox.strikethrough')}~~ =&lt; <s>{t('sandbox.strikethrough')}</s></li>
           </ul>
           <h4>{t('sandbox.link')}</h4>
           <ul className="hljs">
@@ -50,13 +53,13 @@ class Cheatsheet extends React.Component {
         <div className="col-sm-6">
           <h4>{t('sandbox.list')}</h4>
           <ul className="hljs">
-            <li>- {t('sandbox.unordered_list_x', {index: '1'})}</li>
-            <li>&nbsp;&nbsp;- {t('sandbox.unordered_list_x', {index: '1.1'})}</li>
-            <li>- {t('sandbox.unordered_list_x', {index: '2'})}</li>
+            <li>- {t('sandbox.unordered_list_x', { index: '1' })}</li>
+            <li>&nbsp;&nbsp;- {t('sandbox.unordered_list_x', { index: '1.1' })}</li>
+            <li>- {t('sandbox.unordered_list_x', { index: '2' })}</li>
           </ul>
           <ul className="hljs">
-            <li>1. {t('sandbox.ordered_list_x', {index: '1'})}</li>
-            <li>1. {t('sandbox.ordered_list_x', {index: '2'})}</li>
+            <li>1. {t('sandbox.ordered_list_x', { index: '1' })}</li>
+            <li>1. {t('sandbox.ordered_list_x', { index: '2' })}</li>
           </ul>
           <ul className="hljs">
             <li>- [ ] {t('sandbox.task')}({t('sandbox.task_unchecked')})</li>
@@ -64,13 +67,13 @@ class Cheatsheet extends React.Component {
           </ul>
           <h4>{t('sandbox.quote')}</h4>
           <ul className="hljs">
-            <li>> {t('sandbox.quote1')}</li>
-            <li>> {t('sandbox.quote2')}</li>
+            <li>&gt; {t('sandbox.quote1')}</li>
+            <li>&gt; {t('sandbox.quote2')}</li>
           </ul>
           <ul className="hljs">
-            <li>>> {t('sandbox.quote_nested')}</li>
-            <li>>>> {t('sandbox.quote_nested')}</li>
-            <li>>>>> {t('sandbox.quote_nested')}</li>
+            <li>&gt;&gt; {t('sandbox.quote_nested')}</li>
+            <li>&gt;&gt;&gt; {t('sandbox.quote_nested')}</li>
+            <li>&gt;&gt;&gt;&gt; {t('sandbox.quote_nested')}</li>
           </ul>
           <h4>{t('sandbox.table')}</h4>
           <ul className="hljs text-center">
@@ -87,16 +90,17 @@ class Cheatsheet extends React.Component {
 
           <hr />
           <a href="/Sandbox" className="btn btn-info btn-block" target="_blank">
-            <i className="icon-share-alt"/> {t('sandbox.open_sandbox')}
+            <i className="icon-share-alt" /> {t('sandbox.open_sandbox')}
           </a>
         </div>
       </div>
     );
   }
+
 }
 
 Cheatsheet.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
 };
 
 export default translate()(Cheatsheet);

+ 62 - 57
src/client/js/components/PageHistory.js

@@ -22,52 +22,55 @@ class PageHistory extends React.Component {
     const pageId = this.props.pageId;
 
     if (!pageId) {
-      return ;
+      return;
     }
 
-    this.props.crowi.apiGet('/revisions.ids', {page_id: pageId})
-    .then(res => {
+    this.props.crowi.apiGet('/revisions.ids', { page_id: pageId })
+      .then((res) => {
+
+        const rev = res.revisions;
+        const diffOpened = {};
+        const lastId = rev.length - 1;
+        res.revisions.forEach((revision, i) => {
+          const user = this.props.crowi.findUserById(revision.author);
+          if (user) {
+            rev[i].author = user;
+          }
+
+          if (i === 0 || i === lastId) {
+            diffOpened[revision._id] = true;
+          }
+          else {
+            diffOpened[revision._id] = false;
+          }
+        });
 
-      const rev = res.revisions;
-      let diffOpened = {};
-      const lastId = rev.length - 1;
-      res.revisions.map((revision, i) => {
-        const user = this.props.crowi.findUserById(revision.author);
-        if (user) {
-          rev[i].author = user;
-        }
+        this.setState({
+          revisions: rev,
+          diffOpened,
+        });
 
-        if (i === 0 || i === lastId) {
-          diffOpened[revision._id] = true;
+        // load 0, and last default
+        if (rev[0]) {
+          this.fetchPageRevisionBody(rev[0]);
         }
-        else {
-          diffOpened[revision._id] = false;
+        if (rev[1]) {
+          this.fetchPageRevisionBody(rev[1]);
         }
-      });
-
-      this.setState({
-        revisions: rev,
-        diffOpened: diffOpened,
-      });
-
-      // load 0, and last default
-      if (rev[0]) {
-        this.fetchPageRevisionBody(rev[0]);
-      }
-      if (rev[1]) {
-        this.fetchPageRevisionBody(rev[1]);
-      }
-      if (lastId !== 0 && lastId !== 1 && rev[lastId]) {
-        this.fetchPageRevisionBody(rev[lastId]);
-      }
-    }).catch(err => {
+        if (lastId !== 0 && lastId !== 1 && rev[lastId]) {
+          this.fetchPageRevisionBody(rev[lastId]);
+        }
+      })
+      .catch((err) => {
       // do nothing
-    });
+      });
   }
 
   getPreviousRevision(currentRevision) {
     let cursor = null;
-    for (let revision of this.state.revisions) {
+    for (const revision of this.state.revisions) {
+      // comparing ObjectId
+      // eslint-disable-next-line eqeqeq
       if (cursor && cursor._id == currentRevision._id) {
         cursor = revision;
         break;
@@ -80,12 +83,12 @@ class PageHistory extends React.Component {
   }
 
   onDiffOpenClicked(revision) {
-    const diffOpened = this.state.diffOpened,
-      revisionId = revision._id;
+    const diffOpened = this.state.diffOpened;
+    const revisionId = revision._id;
 
     diffOpened[revisionId] = !(diffOpened[revisionId]);
     this.setState({
-      diffOpened
+      diffOpened,
     });
 
     this.fetchPageRevisionBody(revision);
@@ -94,28 +97,29 @@ class PageHistory extends React.Component {
 
   fetchPageRevisionBody(revision) {
     if (revision.body) {
-      return ;
+      return;
     }
 
     this.props.crowi.apiGet('/revisions.get',
-      { page_id: this.props.pageId, revision_id: revision._id}
-    )
-    .then(res => {
-      if (res.ok) {
-        this.setState({
-          revisions: this.state.revisions.map((rev) => {
-            if (rev._id == res.revision._id) {
-              return res.revision;
-            }
-
-            return rev;
-          })
-        });
-      }
-    })
-    .catch(err => {
+      { page_id: this.props.pageId, revision_id: revision._id })
+      .then((res) => {
+        if (res.ok) {
+          this.setState({
+            revisions: this.state.revisions.map((rev) => {
+              // comparing ObjectId
+              // eslint-disable-next-line eqeqeq
+              if (rev._id == res.revision._id) {
+                return res.revision;
+              }
+
+              return rev;
+            }),
+          });
+        }
+      })
+      .catch((err) => {
 
-    });
+      });
   }
 
   render() {
@@ -131,10 +135,11 @@ class PageHistory extends React.Component {
       </div>
     );
   }
+
 }
 
 PageHistory.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   pageId: PropTypes.string,
   crowi: PropTypes.object.isRequired,
 };

+ 6 - 6
src/client/js/components/ReactUtils.js

@@ -14,14 +14,14 @@ export default class ReactUtils {
    * @memberOf ReactUtils
    */
   static nl2br(text) {
-    var regex = /(\n)/g;
-    return text.split(regex).map(function(line) {
+    const regex = /(\n)/g;
+    return text.split(regex).map((line) => {
       if (line.match(regex)) {
-        return React.createElement('br', {key: Math.random().toString(10).substr(2, 10)});
-      }
-      else {
-        return line;
+        return React.createElement('br', { key: Math.random().toString(10).substr(2, 10) });
       }
+
+      return line;
+
     });
   }
 

+ 5 - 4
src/client/js/components/SearchForm.js

@@ -29,7 +29,7 @@ export default class SearchForm extends React.Component {
   }
 
   onChange(selected) {
-    const page = selected[0];  // should be single page selected
+    const page = selected[0]; // should be single page selected
 
     // navigate to page
     if (page != null) {
@@ -43,7 +43,7 @@ export default class SearchForm extends React.Component {
     return (
       <table className="table m-1 search-help">
         <caption className="text-left text-primary p-2 mb-2">
-          <h5 className="m-1"><i className="icon-magnifier pr-2 mb-2"/>{ t('search_help.title') }</h5>
+          <h5 className="m-1"><i className="icon-magnifier pr-2 mb-2" />{ t('search_help.title') }</h5>
         </caption>
         <tbody>
           <tr>
@@ -55,7 +55,7 @@ export default class SearchForm extends React.Component {
           </tr>
           <tr>
             <th className="text-right pt-2">
-              <code>"This is GROWI"</code><br></br>
+              <code>&quot;This is GROWI&quot;</code><br></br>
               <small>({ t('search_help.phrase.syntax help') })</small>
             </th>
             <td><h6 className="m-0 pt-1">{ t('search_help.phrase.desc', { phrase: 'This is GROWI' }) }</h6></td>
@@ -97,10 +97,11 @@ export default class SearchForm extends React.Component {
       />
     );
   }
+
 }
 
 SearchForm.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   keyword: PropTypes.string,
   onSubmit: PropTypes.func.isRequired,

+ 31 - 32
src/client/js/components/SearchPage.js

@@ -13,12 +13,10 @@ class SearchPage extends React.Component {
     super(props);
 
     this.state = {
-      location: location,
       searchingKeyword: this.props.query.q || '',
       searchedKeyword: '',
       searchedPages: [],
       searchResultMeta: {},
-      searchError: null,
     };
 
     this.search = this.search.bind(this);
@@ -27,17 +25,17 @@ class SearchPage extends React.Component {
 
   componentDidMount() {
     const keyword = this.state.searchingKeyword;
-    if (keyword !== '')  {
-      this.search({keyword});
+    if (keyword !== '') {
+      this.search({ keyword });
     }
   }
 
   static getQueryByLocation(location) {
-    let search = location.search || '';
-    let query = {};
+    const search = location.search || '';
+    const query = {};
 
-    search.replace(/^\?/, '').split('&').forEach(function(element) {
-      let queryParts = element.split('=');
+    search.replace(/^\?/, '').split('&').forEach((element) => {
+      const queryParts = element.split('=');
       query[queryParts[0]] = decodeURIComponent(queryParts[1]).replace(/\+/g, ' ');
     });
 
@@ -45,7 +43,7 @@ class SearchPage extends React.Component {
   }
 
   changeURL(keyword, refreshHash) {
-    let hash = location.hash || '';
+    let hash = window.location.hash || '';
     // TODO 整理する
     if (refreshHash || this.state.searchedKeyword !== '') {
       hash = '';
@@ -62,7 +60,6 @@ class SearchPage extends React.Component {
         searchingKeyword: '',
         searchedPages: [],
         searchResultMeta: {},
-        searchError: null,
       });
 
       return true;
@@ -72,54 +69,56 @@ class SearchPage extends React.Component {
       searchingKeyword: keyword,
     });
 
-    this.props.crowi.apiGet('/search', {q: keyword})
-    .then(res => {
-      this.changeURL(keyword);
-
-      this.setState({
-        searchedKeyword: keyword,
-        searchedPages: res.data,
-        searchResultMeta: res.meta,
-      });
-    }).catch(err => {
-      // TODO error
-      this.setState({
-        searchError: err,
+    this.props.crowi.apiGet('/search', { q: keyword })
+      .then((res) => {
+        this.changeURL(keyword);
+
+        this.setState({
+          searchedKeyword: keyword,
+          searchedPages: res.data,
+          searchResultMeta: res.meta,
+        });
+      })
+      .catch((err) => {
+        // TODO error
+        // this.setState({
+        // });
       });
-    });
   }
 
   render() {
     return (
       <div>
         <div className="search-page-input">
-          <SearchPageForm t={this.props.t}
+          <SearchPageForm
+            t={this.props.t}
             crowi={this.props.crowi}
             onSearchFormChanged={this.search}
             keyword={this.state.searchingKeyword}
-            />
+          />
         </div>
         <SearchResult
-          crowi={this.props.crowi} crowiRenderer={this.props.crowiRenderer}
+          crowi={this.props.crowi}
+          crowiRenderer={this.props.crowiRenderer}
           pages={this.state.searchedPages}
           searchingKeyword={this.state.searchingKeyword}
           searchResultMeta={this.state.searchResultMeta}
-          />
+        />
       </div>
     );
   }
+
 }
 
 SearchPage.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   crowiRenderer: PropTypes.object.isRequired,
   query: PropTypes.object,
 };
 SearchPage.defaultProps = {
-  //pollInterval: 1000,
-  query: SearchPage.getQueryByLocation(location || {}),
-  searchError: null,
+  // pollInterval: 1000,
+  query: SearchPage.getQueryByLocation(window.location || {}),
 };
 
 export default translate()(SearchPage);

+ 25 - 27
src/client/js/components/SearchTypeahead.js

@@ -16,8 +16,6 @@ export default class SearchTypeahead extends React.Component {
 
     this.state = {
       input: this.props.keywordOnInit,
-      keyword: '',
-      searchedKeyword: '',
       pages: [],
       isLoading: false,
       searchError: null,
@@ -39,7 +37,7 @@ export default class SearchTypeahead extends React.Component {
    * Get instance of AsyncTypeahead
    */
   getTypeahead() {
-    return this.refs.typeahead ? this.refs.typeahead.getInstance() : null;
+    return this.typeahead ? this.typeahead.getInstance() : null;
   }
 
   componentDidMount() {
@@ -54,7 +52,7 @@ export default class SearchTypeahead extends React.Component {
   restoreInitialData() {
     // see https://github.com/ericgio/react-bootstrap-typeahead/issues/266#issuecomment-414987723
     const text = this.props.keywordOnInit;
-    const instance = this.refs.typeahead.getInstance();
+    const instance = this.typeahead.getInstance();
     instance.clear();
     instance.setState({ text });
   }
@@ -62,18 +60,14 @@ export default class SearchTypeahead extends React.Component {
   search(keyword) {
 
     if (keyword === '') {
-      this.setState({
-        keyword: '',
-        searchedKeyword: '',
-      });
       return;
     }
 
-    this.setState({isLoading: true});
+    this.setState({ isLoading: true });
 
-    this.crowi.apiGet('/search', {q: keyword})
-      .then(res => { this.onSearchSuccess(res) })
-      .catch(err => { this.onSearchError(err) });
+    this.crowi.apiGet('/search', { q: keyword })
+      .then((res) => { this.onSearchSuccess(res) })
+      .catch((err) => { this.onSearchError(err) });
   }
 
   /**
@@ -83,10 +77,11 @@ export default class SearchTypeahead extends React.Component {
   onSearchSuccess(res) {
     this.setState({
       isLoading: false,
-      keyword: '',
       pages: res.data,
     });
-    this.props.onSearchSuccess && this.props.onSearchSuccess(res);
+    if (this.props.onSearchSuccess != null) {
+      this.props.onSearchSuccess(res);
+    }
   }
 
   /**
@@ -98,14 +93,16 @@ export default class SearchTypeahead extends React.Component {
       isLoading: false,
       searchError: err,
     });
-    this.props.onSearchError && this.props.onSearchError(err);
+    if (this.props.onSearchError != null) {
+      this.props.onSearchError(err);
+    }
   }
 
   onInputChange(text) {
-    this.setState({input: text});
+    this.setState({ input: text });
     this.props.onInputChange(text);
     if (text === '') {
-      this.setState({pages: []});
+      this.setState({ pages: [] });
     }
   }
 
@@ -141,9 +138,9 @@ export default class SearchTypeahead extends React.Component {
    * Get restore form button to initialize button
    */
   getRestoreFormButton() {
-    let isHidden = (this.state.input === this.props.keywordOnInit);
+    const isHidden = (this.state.input === this.props.keywordOnInit);
 
-    return isHidden ? <span></span> : (
+    return isHidden ? <span /> : (
       <button type="button" className="btn btn-link search-clear" onMouseDown={this.restoreInitialData}>
         <i className="icon-close" />
       </button>
@@ -154,16 +151,16 @@ export default class SearchTypeahead extends React.Component {
     const page = option;
     return (
       <span>
-      <UserPicture user={page.lastUpdateUser} size="sm" />
-      <PagePath page={page} />
-      <PageListMeta page={page} />
+        <UserPicture user={page.lastUpdateUser} size="sm" />
+        <PagePath page={page} />
+        <PageListMeta page={page} />
       </span>
     );
   }
 
   render() {
-    const defaultSelected = (this.props.keywordOnInit != '')
-      ? [{path: this.props.keywordOnInit}]
+    const defaultSelected = (this.props.keywordOnInit !== '')
+      ? [{ path: this.props.keywordOnInit }]
       : [];
     const inputProps = { autoComplete: 'off' };
     if (this.props.inputName != null) {
@@ -176,7 +173,7 @@ export default class SearchTypeahead extends React.Component {
       <div className="search-typeahead">
         <AsyncTypeahead
           {...this.props}
-          ref="typeahead"
+          ref={(c) => { this.typeahead = c }}
           inputProps={inputProps}
           isLoading={this.state.isLoading}
           labelKey="path"
@@ -187,8 +184,8 @@ export default class SearchTypeahead extends React.Component {
               // DIRTY HACK
               //  note: The default searchText string has been shown wrongly even if isLoading is false
               //        since upgrade react-bootstrap-typeahead to v3.3.2 -- 2019.02.05 Yuki Takei
-          align='left'
-          submitFormOnEnter={true}
+          align="left"
+          submitFormOnEnter
           onSearch={this.search}
           onInputChange={this.onInputChange}
           onKeyDown={this.onKeyDown}
@@ -201,6 +198,7 @@ export default class SearchTypeahead extends React.Component {
       </div>
     );
   }
+
 }
 
 /**

+ 19 - 17
src/client/js/legacy/crowi-admin.js

@@ -1,3 +1,6 @@
+/* global Switchery */
+/* eslint-disable no-restricted-globals */
+
 require('./thirdparty-js/jQuery.style.switcher');
 
 // see https://github.com/abpetkov/switchery/issues/120
@@ -5,9 +8,9 @@ require('./thirdparty-js/jQuery.style.switcher');
 require('./thirdparty-js/switchery/switchery');
 require('./thirdparty-js/switchery/switchery.css');
 
-$(function() {
+$(() => {
   $('#slackNotificationForm').on('submit', function(e) {
-    $.post('/_api/admin/notification.add', $(this).serialize(), function(res) {
+    $.post('/_api/admin/notification.add', $(this).serialize(), (res) => {
       if (res.ok) {
         // TODO Fix
         location.reload();
@@ -18,7 +21,7 @@ $(function() {
   });
 
   $('form.admin-remove-updatepost').on('submit', function(e) {
-    $.post('/_api/admin/notification.remove', $(this).serialize(), function(res) {
+    $.post('/_api/admin/notification.remove', $(this).serialize(), (res) => {
       if (res.ok) {
         // TODO Fix
         location.reload();
@@ -29,7 +32,7 @@ $(function() {
 
   $('#createdUserModal').modal('show');
 
-  $('#admin-password-reset-modal').on('show.bs.modal', function(button) {
+  $('#admin-password-reset-modal').on('show.bs.modal', (button) => {
     const data = $(button.relatedTarget);
     const userId = data.data('user-id');
     const email = data.data('user-email');
@@ -39,16 +42,16 @@ $(function() {
   });
 
   $('form#admin-users-reset-password').on('submit', function(e) {
-    $.post('/_api/admin/users.resetPassword', $(this).serialize(), function(res) {
+    $.post('/_api/admin/users.resetPassword', $(this).serialize(), (res) => {
       if (res.ok) {
         // TODO Fix
-        //location.reload();
+        // location.reload();
         $('#admin-password-reset-modal').modal('hide');
         $('#admin-password-reset-modal-done').modal('show');
 
         $('#admin-password-reset-done-user').text(res.user.email);
         $('#admin-password-reset-done-password').text(res.newPassword);
-        return ;
+        return;
       }
 
       // fixme
@@ -58,7 +61,7 @@ $(function() {
     return false;
   });
 
-  $('#admin-delete-user-group-modal').on('show.bs.modal', function(button) {
+  $('#admin-delete-user-group-modal').on('show.bs.modal', (button) => {
     const data = $(button.relatedTarget);
     const userGroupId = data.data('user-group-id');
     const userGroupName = data.data('user-group-name');
@@ -68,7 +71,7 @@ $(function() {
   });
 
   $('form#user-group-relation-create').on('submit', function(e) {
-    $.post('/admin/user-group-relation/create', $(this).serialize(), function(res) {
+    $.post('/admin/user-group-relation/create', $(this).serialize(), (res) => {
       $('#admin-add-user-group-relation-modal').modal('hide');
       return;
     });
@@ -78,7 +81,7 @@ $(function() {
   $('#pictureUploadForm input[name=userGroupPicture]').on('change', function() {
     const $form = $('#pictureUploadForm');
     const fd = new FormData($form[0]);
-    if ($(this).val() == '') {
+    if ($(this).val() !== '') {
       return false;
     }
 
@@ -89,9 +92,9 @@ $(function() {
       contentType: false,
       data: fd,
       dataType: 'json',
-      success: function(data) {
+      success(data) {
         if (data.status) {
-          $('#settingUserPicture').attr('src', data.url + '?time=' + (new Date()));
+          $('#settingUserPicture').attr('src', `${data.url}?time=${new Date()}`);
           $('#pictureUploadFormMessage')
             .addClass('alert alert-success')
             .html('変更しました');
@@ -102,7 +105,7 @@ $(function() {
             .html('変更中にエラーが発生しました。');
         }
         $('#pictureUploadFormProgress').html('');
-      }
+      },
     });
     return false;
   });
@@ -112,11 +115,10 @@ $(function() {
 
   // switchery
   const elems = Array.prototype.slice.call(document.querySelectorAll('.js-switch'));
-  elems.forEach(function(elem) {
+  elems.forEach((elem) => {
     const color = elem.dataset.color;
     const size = elem.dataset.size;
-    new Switchery(elem, { color, size });   // eslint-disable-line no-undef
+    // eslint-disable-next-line no-new
+    new Switchery(elem, { color, size });
   });
 });
-
-

+ 4 - 4
src/client/js/legacy/crowi-presentation.js

@@ -15,8 +15,8 @@ Reveal.initialize({
   center: true,
   transition: 'slide',
 
-  //// This specification method can't be used
-  ////   sice deleting symlink prevented `src` from being resolved -- 2017.06.15 Yuki Takei
+  // // This specification method can't be used
+  // //   sice deleting symlink prevented `src` from being resolved -- 2017.06.15 Yuki Takei
   //
   // Optional libraries used to extend on reveal.js
   // dependencies: [
@@ -40,11 +40,11 @@ require.ensure([], () => {
   Reveal.sync();
 });
 
-Reveal.addEventListener('ready', function(event) {
+Reveal.addEventListener('ready', (event) => {
   // event.currentSlide, event.indexh, event.indexv
   $('.reveal section').each(function(e) {
     const $self = $(this);
-    if ($self.children().length == 1) {
+    if ($self.children().length !== 1) {
       $self.addClass('only');
     }
   });

+ 136 - 135
src/client/js/legacy/crowi.js

@@ -1,24 +1,23 @@
-/* jshint browser: true, jquery: true */
-/* Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
-*/
+/* global location */
+/* eslint no-restricted-globals: ['error', 'locaion'] */
 
 import React from 'react';
 import ReactDOM from 'react-dom';
 
 import { debounce } from 'throttle-debounce';
 
+import * as pathUtils from '@commons/util/path-utils';
+import GrowiRenderer from '../util/GrowiRenderer';
+import RevisionLoader from '../components/Page/RevisionLoader';
+
 const entities = require('entities');
 const escapeStringRegexp = require('escape-string-regexp');
 require('jquery.cookie');
 require('bootstrap-select');
 
-import * as pathUtils from '@commons/util/path-utils';
-import GrowiRenderer from '../util/GrowiRenderer';
-import RevisionLoader from '../components/Page/RevisionLoader';
-
 require('./thirdparty-js/agile-admin');
 
-let Crowi = {};
+const Crowi = {};
 
 if (!window) {
   window = {};
@@ -97,7 +96,7 @@ Crowi.modifyScrollTop = function() {
   if (window.scrollY === 0) {
     timeout = 200;
   }
-  setTimeout(function() {
+  setTimeout(() => {
     const sectionHeaderRect = sectionHeader.getBoundingClientRect();
     if (sectionHeaderRect.top >= pageHeaderRect.bottom) {
       return;
@@ -140,10 +139,10 @@ Crowi.initAffix = () => {
     const containerHeight = $affixContentContainer.outerHeight(true);
     $affixContent.affix({
       offset: {
-        top: function() {
+        top() {
           return $('.navbar').outerHeight(true) + containerHeight;
-        }
-      }
+        },
+      },
     });
     $('[data-affix-disable]').on('click', function(e) {
       const $elm = $($(this).data('affix-disable'));
@@ -151,7 +150,7 @@ Crowi.initAffix = () => {
       $elm.removeData('affix').removeClass('affix affix-top affix-bottom');
       return false;
     });
-    $affixContentContainer.css({'min-height': containerHeight});
+    $affixContentContainer.css({ 'min-height': containerHeight });
   }
 };
 
@@ -173,7 +172,7 @@ Crowi.initSlimScrollForRevisionToc = () => {
     // window height - revisionTocTop - .system-version height
     let h = window.innerHeight - revisionTocTop - 20;
 
-    const tocContentHeight = tocContentElem.getBoundingClientRect().height + 15;  // add margin
+    const tocContentHeight = tocContentElem.getBoundingClientRect().height + 15; // add margin
 
     h = Math.min(h, tocContentHeight);
 
@@ -198,11 +197,11 @@ Crowi.initSlimScrollForRevisionToc = () => {
     resetScrollbarDebounced(getCurrentRevisionTocTop());
   });
   // affix on
-  $('#revision-toc').on('affixed.bs.affix', function() {
+  $('#revision-toc').on('affixed.bs.affix', () => {
     resetScrollbar(getCurrentRevisionTocTop());
   });
   // affix off
-  $('#revision-toc').on('affixed-top.bs.affix', function() {
+  $('#revision-toc').on('affixed-top.bs.affix', () => {
     // calculate sum of height (.navbar-header + .bg-title) + margin-top of .main
     const sum = 138;
     resetScrollbar(sum);
@@ -215,13 +214,13 @@ Crowi.findHashFromUrl = function(url) {
   if (match = url.match(/#(.+)$/)) {
     return `#${match[1]}`;
   }
-  /* eslint-enable */
+  /* eslint-enable no-cond-assign */
 
   return '';
 };
 
 Crowi.findSectionHeader = function(hash) {
-  if (hash.length == 0) {
+  if (hash.length === 0) {
     return;
   }
 
@@ -230,7 +229,7 @@ Crowi.findSectionHeader = function(hash) {
   // don't use jQuery and document.querySelector
   //  because hash may containe Base64 encoded strings
   const elem = document.getElementById(id);
-  if (elem != null && elem.tagName.match(/h\d+/i)) {  // match h1, h2, h3...
+  if (elem != null && elem.tagName.match(/h\d+/i)) { // match h1, h2, h3...
     return elem;
   }
 
@@ -264,12 +263,11 @@ Crowi.getCurrentEditorMode = function() {
   if ($('body').hasClass('builtin-editor')) {
     return 'builtin';
   }
-  else {
-    return 'hackmd';
-  }
+
+  return 'hackmd';
 };
 
-$(function() {
+$(() => {
   const crowi = window.crowi;
   const config = JSON.parse(document.getElementById('crowi-context-hydrate').textContent || '{}');
 
@@ -278,14 +276,14 @@ $(function() {
   // const revisionCreatedAt = $('#content-main').data('page-revision-created');
   // const currentUser = $('#content-main').data('current-user');
   const isSeen = $('#content-main').data('page-is-seen');
-  const pagePath= $('#content-main').data('path');
-  const isSavedStatesOfTabChanges = config['isSavedStatesOfTabChanges'];
+  const pagePath = $('#content-main').data('path');
+  const isSavedStatesOfTabChanges = config.isSavedStatesOfTabChanges;
 
   $('[data-toggle="popover"]').popover();
   $('[data-toggle="tooltip"]').tooltip();
   $('[data-tooltip-stay]').tooltip('show');
 
-  $('#toggle-sidebar').click(function(e) {
+  $('#toggle-sidebar').click((e) => {
     const $mainContainer = $('.main-container');
     if ($mainContainer.hasClass('aside-hidden')) {
       $('.main-container').removeClass('aside-hidden');
@@ -298,7 +296,7 @@ $(function() {
     return false;
   });
 
-  if ($.cookie('aside-hidden') == 1) {
+  if ($.cookie('aside-hidden') === 1) {
     $('.main-container').addClass('aside-hidden');
   }
 
@@ -307,14 +305,14 @@ $(function() {
   });
 
 
-  $('#create-page').on('shown.bs.modal', function(e) {
+  $('#create-page').on('shown.bs.modal', (e) => {
     // quick hack: replace from server side rendering "date" to client side "date"
     const today = new Date();
-    const month = ('0' + (today.getMonth() + 1)).slice(-2);
-    const day = ('0' + today.getDate()).slice(-2);
-    const dateString = today.getFullYear() + '/' + month + '/' + day;
-    $('#create-page-today .page-today-suffix').text('/' + dateString + '/');
-    $('#create-page-today .page-today-input2').data('prefix', '/' + dateString + '/');
+    const month = (`0${today.getMonth() + 1}`).slice(-2);
+    const day = (`0${today.getDate()}`).slice(-2);
+    const dateString = `${today.getFullYear()}/${month}/${day}`;
+    $('#create-page-today .page-today-suffix').text(`/${dateString}/`);
+    $('#create-page-today .page-today-input2').data('prefix', `/${dateString}/`);
 
     // focus
     $('#create-page-today .page-today-input2').eq(0).focus();
@@ -331,69 +329,69 @@ $(function() {
     if (input2 === '') {
       prefix2 = prefix2.slice(0, -1);
     }
-    top.location.href = prefix1 + input1 + prefix2 + input2 + '#edit';
+    top.location.href = `${prefix1 + input1 + prefix2 + input2}#edit`;
     return false;
   });
 
   $('#create-page-under-tree').submit(function(e) {
     let name = $('input', this).val();
     if (!name.match(/^\//)) {
-      name = '/' + name;
+      name = `/${name}`;
     }
     if (name.match(/.+\/$/)) {
       name = name.substr(0, name.length - 1);
     }
-    top.location.href = pathUtils.encodePagePath(name) + '#edit';
+    top.location.href = `${pathUtils.encodePagePath(name)}#edit`;
     return false;
   });
 
   // rename/unportalize
-  $('#renamePage, #unportalize').on('shown.bs.modal', function(e) {
+  $('#renamePage, #unportalize').on('shown.bs.modal', (e) => {
     $('#renamePage #newPageName').focus();
     $('#renamePage .msg, #unportalize .msg').hide();
   });
   $('#renamePageForm, #unportalize-form').submit(function(e) {
     // create name-value map
-    let nameValueMap = {};
+    const nameValueMap = {};
     $(this).serializeArray().forEach((obj) => {
       nameValueMap[obj.name] = obj.value; // nameValueMap.new_path is renamed page path
     });
 
-    const data = $(this).serialize() + `&socketClientId=${crowi.getSocketClientId()}`;
+    const data = `${$(this).serialize()}&socketClientId=${crowi.getSocketClientId()}`;
 
     $.ajax({
       type: 'POST',
       url: '/_api/pages.rename',
-      data: data,
-      dataType: 'json'
+      data,
+      dataType: 'json',
     })
-    .done(function(res) {
+      .done((res) => {
       // error
-      if (!res.ok) {
-        const linkPath = pathUtils.normalizePath(nameValueMap.new_path);
-        $('#renamePage .msg, #unportalize .msg').hide();
-        $(`#renamePage .msg-${res.code}, #unportalize .msg-${res.code}`).show();
-        $('#renamePage #linkToNewPage, #unportalize #linkToNewPage').html(`
+        if (!res.ok) {
+          const linkPath = pathUtils.normalizePath(nameValueMap.new_path);
+          $('#renamePage .msg, #unportalize .msg').hide();
+          $(`#renamePage .msg-${res.code}, #unportalize .msg-${res.code}`).show();
+          $('#renamePage #linkToNewPage, #unportalize #linkToNewPage').html(`
           <a href="${linkPath}">${linkPath} <i class="icon-login"></i></a>
         `);
-      }
-      else {
-        const page = res.page;
-        top.location.href = page.path + '?renamed=' + pagePath;
-      }
-    });
+        }
+        else {
+          const page = res.page;
+          top.location.href = `${page.path}?renamed=${pagePath}`;
+        }
+      });
 
     return false;
   });
 
   // duplicate
-  $('#duplicatePage').on('shown.bs.modal', function(e) {
+  $('#duplicatePage').on('shown.bs.modal', (e) => {
     $('#duplicatePage #duplicatePageName').focus();
     $('#duplicatePage .msg').hide();
   });
   $('#duplicatePageForm, #unportalize-form').submit(function(e) {
     // create name-value map
-    let nameValueMap = {};
+    const nameValueMap = {};
     $(this).serializeArray().forEach((obj) => {
       nameValueMap[obj.name] = obj.value; // nameValueMap.new_path is duplicated page path
     });
@@ -402,8 +400,8 @@ $(function() {
       type: 'POST',
       url: '/_api/pages.duplicate',
       data: $(this).serialize(),
-      dataType: 'json'
-    }).done(function(res) {
+      dataType: 'json',
+    }).done((res) => {
       // error
       if (!res.ok) {
         const linkPath = pathUtils.normalizePath(nameValueMap.new_path);
@@ -415,7 +413,7 @@ $(function() {
       }
       else {
         const page = res.page;
-        top.location.href = page.path + '?duplicated=' + pagePath;
+        top.location.href = `${page.path}?duplicated=${pagePath}`;
       }
     });
 
@@ -423,16 +421,16 @@ $(function() {
   });
 
   // delete
-  $('#deletePage').on('shown.bs.modal', function(e) {
+  $('#deletePage').on('shown.bs.modal', (e) => {
     $('#deletePage .msg').hide();
   });
-  $('#delete-page-form').submit(function(e) {
+  $('#delete-page-form').submit((e) => {
     $.ajax({
       type: 'POST',
       url: '/_api/pages.remove',
       data: $('#delete-page-form').serialize(),
-      dataType: 'json'
-    }).done(function(res) {
+      dataType: 'json',
+    }).done((res) => {
       // error
       if (!res.ok) {
         $('#deletePage .msg').hide();
@@ -448,16 +446,16 @@ $(function() {
   });
 
   // Put Back
-  $('#putBackPage').on('shown.bs.modal', function(e) {
+  $('#putBackPage').on('shown.bs.modal', (e) => {
     $('#putBackPage .msg').hide();
   });
-  $('#revert-delete-page-form').submit(function(e) {
+  $('#revert-delete-page-form').submit((e) => {
     $.ajax({
       type: 'POST',
       url: '/_api/pages.revertRemove',
       data: $('#revert-delete-page-form').serialize(),
-      dataType: 'json'
-    }).done(function(res) {
+      dataType: 'json',
+    }).done((res) => {
       // error
       if (!res.ok) {
         $('#putBackPage .msg').hide();
@@ -471,43 +469,43 @@ $(function() {
 
     return false;
   });
-  $('#unlink-page-form').submit(function(e) {
+  $('#unlink-page-form').submit((e) => {
     $.ajax({
       type: 'POST',
       url: '/_api/pages.unlink',
       data: $('#unlink-page-form').serialize(),
-      dataType: 'json'
+      dataType: 'json',
     })
-    .done(function(res) {
-      if (!res.ok) {
-        $('#delete-errors').html('<i class="fa fa-times-circle"></i> ' + res.error);
-        $('#delete-errors').addClass('alert-danger');
-      }
-      else {
-        top.location.href = res.path + '?unlinked=true';
-      }
-    });
+      .done((res) => {
+        if (!res.ok) {
+          $('#delete-errors').html(`<i class="fa fa-times-circle"></i> ${res.error}`);
+          $('#delete-errors').addClass('alert-danger');
+        }
+        else {
+          top.location.href = `${res.path}?unlinked=true`;
+        }
+      });
 
     return false;
   });
 
-  $('#create-portal-button').on('click', function(e) {
+  $('#create-portal-button').on('click', (e) => {
     $('a[data-toggle="tab"][href="#edit"]').tab('show');
 
     $('body').addClass('on-edit');
     $('body').addClass('builtin-editor');
 
     const path = $('.content-main').data('path');
-    if (path != '/' && $('.content-main').data('page-id') == '') {
+    if (path !== '/' && $('.content-main').data('page-id') === '') {
       const upperPage = path.substr(0, path.length - 1);
-      $.get('/_api/pages.get', {path: upperPage}, function(res) {
+      $.get('/_api/pages.get', { path: upperPage }, (res) => {
         if (res.ok && res.page) {
           $('#portal-warning-modal').modal('show');
         }
       });
     }
   });
-  $('#portal-form-close').on('click', function(e) {
+  $('#portal-form-close').on('click', (e) => {
     $('#edit').removeClass('active');
     $('body').removeClass('on-edit');
     $('body').removeClass('builtin-editor');
@@ -519,9 +517,8 @@ $(function() {
    */
   $('#view-list .page-list-ul-flat .page-list-link').each(function() {
     const $link = $(this);
-    /* eslint-disable no-unused-vars */
+    /* eslint-disable-next-line no-unused-vars */
     const text = $link.text();
-    /* eslint-enable */
     let path = decodeURIComponent($link.data('path'));
     const shortPath = decodeURIComponent($link.data('short-path')); // convert to string
 
@@ -531,40 +528,44 @@ $(function() {
     }
 
     path = entities.encodeHTML(path);
-    const pattern = escapeStringRegexp(entities.encodeHTML(shortPath)) + '(/)?$';
+    const 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>`));
   });
 
   // for list page
   let growiRendererForTimeline = null;
-  $('a[data-toggle="tab"][href="#view-timeline"]').on('shown.bs.tab', function() {
+  $('a[data-toggle="tab"][href="#view-timeline"]').on('shown.bs.tab', () => {
     const isShown = $('#view-timeline').data('shown');
 
     if (growiRendererForTimeline == null) {
       const crowi = window.crowi;
       const crowiRenderer = window.crowiRenderer;
-      growiRendererForTimeline = new GrowiRenderer(crowi, crowiRenderer, {mode: 'timeline'});
+      growiRendererForTimeline = new GrowiRenderer(crowi, crowiRenderer, { mode: 'timeline' });
     }
 
-    if (isShown == 0) {
+    if (isShown === 0) {
       $('#view-timeline .timeline-body').each(function() {
         const id = $(this).attr('id');
-        const revisionBody = '#' + id + ' .revision-body';
+        const revisionBody = `#${id} .revision-body`;
         const revisionBodyElem = document.querySelector(revisionBody);
-        /* eslint-disable no-unused-vars */
-        const revisionPath = '#' + id + ' .revision-path';
-        /* eslint-enable */
+        const revisionPath = `#${id} .revision-path`; // eslint-disable-line no-unused-vars
         const timelineElm = document.getElementById(id);
         const pageId = timelineElm.getAttribute('data-page-id');
         const pagePath = timelineElm.getAttribute('data-page-path');
         const revisionId = timelineElm.getAttribute('data-revision');
 
         ReactDOM.render(
-          <RevisionLoader lazy={true}
-            crowi={crowi} crowiRenderer={growiRendererForTimeline}
-            pageId={pageId} pagePath={pagePath} revisionId={revisionId} />,
-          revisionBodyElem);
+          <RevisionLoader
+            lazy
+            crowi={crowi}
+            crowiRenderer={growiRendererForTimeline}
+            pageId={pageId}
+            pagePath={pagePath}
+            revisionId={revisionId}
+          />,
+          revisionBodyElem,
+        );
       });
 
       $('#view-timeline').data('shown', 1);
@@ -572,19 +573,18 @@ $(function() {
   });
 
   if (pageId) {
-
     // for Crowi Template LangProcessor
     $('.template-create-button', $('#revision-body')).on('click', function() {
       const path = $(this).data('path');
       const templateId = $(this).data('template');
-      const template = $('#' + templateId).html();
+      const template = $(`#${templateId}`).html();
 
       crowi.saveDraft(path, template);
       top.location.href = `${path}#edit`;
     });
 
     if (!isSeen) {
-      $.post('/_api/pages.seen', {page_id: pageId}, function(res) {
+      $.post('/_api/pages.seen', { page_id: pageId }, (res) => {
         // ignore unless response has error
         if (res.ok && res.seenUser) {
           $('#content-main').data('page-is-seen', 1);
@@ -593,8 +593,10 @@ $(function() {
     }
 
     // presentation
-    let presentaionInitialized = false
-      , $b = $('body');
+    let presentaionInitialized = false;
+
+
+    const $b = $('body');
 
     $(document).on('click', '.toggle-presentation', function(e) {
       const $a = $(this);
@@ -606,80 +608,79 @@ $(function() {
         presentaionInitialized = true;
 
         $('<iframe />').attr({
-          src: $a.attr('href')
+          src: $a.attr('href'),
         }).appendTo($('#presentation-container'));
       }
-    }).on('click', '.fullscreen-layer', function() {
+    }).on('click', '.fullscreen-layer', () => {
       $b.toggleClass('overlay-on');
     });
-
   } // end if pageId
 
   // tab changing handling
-  $('a[href="#edit"]').on('show.bs.tab', function() {
+  $('a[href="#edit"]').on('show.bs.tab', () => {
     $('body').addClass('on-edit');
     $('body').addClass('builtin-editor');
   });
-  $('a[href="#edit"]').on('hide.bs.tab', function() {
+  $('a[href="#edit"]').on('hide.bs.tab', () => {
     $('body').removeClass('on-edit');
     $('body').removeClass('builtin-editor');
   });
-  $('a[href="#hackmd"]').on('show.bs.tab', function() {
+  $('a[href="#hackmd"]').on('show.bs.tab', () => {
     $('body').addClass('on-edit');
     $('body').addClass('hackmd');
   });
 
-  $('a[href="#hackmd"]').on('hide.bs.tab', function() {
+  $('a[href="#hackmd"]').on('hide.bs.tab', () => {
     $('body').removeClass('on-edit');
     $('body').removeClass('hackmd');
   });
 
   // hash handling
   if (isSavedStatesOfTabChanges) {
-    $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', function() {
+    $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
       window.location.hash = '#revision-history';
       window.history.replaceState('', 'History', '#revision-history');
     });
-    $('a[data-toggle="tab"][href="#edit"]').on('show.bs.tab', function() {
+    $('a[data-toggle="tab"][href="#edit"]').on('show.bs.tab', () => {
       window.location.hash = '#edit';
       window.history.replaceState('', 'Edit', '#edit');
     });
-    $('a[data-toggle="tab"][href="#hackmd"]').on('show.bs.tab', function() {
+    $('a[data-toggle="tab"][href="#hackmd"]').on('show.bs.tab', () => {
       window.location.hash = '#hackmd';
       window.history.replaceState('', 'HackMD', '#hackmd');
     });
-    $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', function() {
+    $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
       // couln't solve https://github.com/weseek/crowi-plus/issues/119 completely -- 2017.07.03 Yuki Takei
       window.location.hash = '#';
       window.history.replaceState('', '', location.href);
     });
   }
   else {
-    $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', function() {
+    $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
       window.history.replaceState('', 'History', '#revision-history');
     });
-    $('a[data-toggle="tab"][href="#edit"]').on('show.bs.tab', function() {
+    $('a[data-toggle="tab"][href="#edit"]').on('show.bs.tab', () => {
       window.history.replaceState('', 'Edit', '#edit');
     });
-    $('a[data-toggle="tab"][href="#hackmd"]').on('show.bs.tab', function() {
+    $('a[data-toggle="tab"][href="#hackmd"]').on('show.bs.tab', () => {
       window.history.replaceState('', 'HackMD', '#hackmd');
     });
-    $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', function() {
-      window.history.replaceState('', '',  location.href.replace(location.hash, ''));
+    $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
+      window.history.replaceState('', '', location.href.replace(location.hash, ''));
     });
     // replace all href="#edit" link behaviors
-    $(document).on('click', 'a[href="#edit"]', function() {
+    $(document).on('click', 'a[href="#edit"]', () => {
       window.location.replace('#edit');
     });
   }
 
   // focus to editor when 'shown.bs.tab' event fired
-  $('a[href="#edit"]').on('shown.bs.tab', function(e) {
+  $('a[href="#edit"]').on('shown.bs.tab', (e) => {
     Crowi.setCaretLineAndFocusToEditor();
   });
 });
 
-window.addEventListener('load', function(e) {
+window.addEventListener('load', (e) => {
   // hash on page
   if (location.hash) {
     if (location.hash === '#edit' || location.hash === '#edit-form') {
@@ -690,47 +691,47 @@ window.addEventListener('load', function(e) {
       // focus
       Crowi.setCaretLineAndFocusToEditor();
     }
-    else if (location.hash == '#hackmd') {
+    else if (location.hash === '#hackmd') {
       $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
       $('body').addClass('on-edit');
       $('body').addClass('hackmd');
     }
-    else if (location.hash == '#revision-history') {
+    else if (location.hash === '#revision-history') {
       $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
     }
   }
 
   const crowi = window.crowi;
-  if (crowi && crowi.users && crowi.users.length != 0) {
+  if (crowi && crowi.users && crowi.users.length !== 0) {
     const totalUsers = crowi.users.length;
     const $listLiker = $('.page-list-liker');
-    $listLiker.each(function(i, liker) {
+    $listLiker.each((i, liker) => {
       const count = $(liker).data('count') || 0;
-      if (count/totalUsers > 0.05) {
+      if (count / totalUsers > 0.05) {
         $(liker).addClass('popular-page-high');
         // 5%
       }
-      else if (count/totalUsers > 0.02) {
+      else if (count / totalUsers > 0.02) {
         $(liker).addClass('popular-page-mid');
         // 2%
       }
-      else if (count/totalUsers > 0.005) {
+      else if (count / totalUsers > 0.005) {
         $(liker).addClass('popular-page-low');
         // 0.5%
       }
     });
     const $listSeer = $('.page-list-seer');
-    $listSeer.each(function(i, seer) {
+    $listSeer.each((i, seer) => {
       const count = $(seer).data('count') || 0;
-      if (count/totalUsers > 0.10) {
+      if (count / totalUsers > 0.10) {
         // 10%
         $(seer).addClass('popular-page-high');
       }
-      else if (count/totalUsers > 0.05) {
+      else if (count / totalUsers > 0.05) {
         // 5%
         $(seer).addClass('popular-page-mid');
       }
-      else if (count/totalUsers > 0.02) {
+      else if (count / totalUsers > 0.02) {
         // 2%
         $(seer).addClass('popular-page-low');
       }
@@ -743,7 +744,7 @@ window.addEventListener('load', function(e) {
   Crowi.initAffix();
 });
 
-window.addEventListener('hashchange', function(e) {
+window.addEventListener('hashchange', (e) => {
   Crowi.unhighlightSelectedSection(Crowi.findHashFromUrl(e.oldURL));
   Crowi.highlightSelectedSection(Crowi.findHashFromUrl(e.newURL));
   Crowi.modifyScrollTop();
@@ -753,10 +754,10 @@ window.addEventListener('hashchange', function(e) {
     if (location.hash === '#edit') {
       $('a[data-toggle="tab"][href="#edit"]').tab('show');
     }
-    else if (location.hash == '#hackmd') {
+    else if (location.hash === '#hackmd') {
       $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
     }
-    else if (location.hash == '#revision-history') {
+    else if (location.hash === '#revision-history') {
       $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
     }
   }
@@ -790,8 +791,8 @@ window.addEventListener('keydown', (event) => {
         Crowi.handleKeyCtrlSlashHandler(event);
       }
       break;
+    default:
   }
-
 });
 
 // adjust min-height of page for print temporarily

+ 14 - 13
src/client/js/models/MarkdownTable.js

@@ -5,7 +5,7 @@ import csvToMarkdown from 'csv-to-markdown-table';
 // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
 // https://regex101.com/r/7BN2fR/7
 const tableAlignmentLineRE = /^[-:|][-:|\s]*$/;
-const tableAlignmentLineNegRE = /^[^-:]*$/;  // it is need to check to ignore empty row which is matched above RE
+const tableAlignmentLineNegRE = /^[^-:]*$/; // it is need to check to ignore empty row which is matched above RE
 const linePartOfTableRE = /^\|[^\r\n]*|[^\r\n]*\|$|([^|\r\n]+\|[^|\r\n]*)+/; // own idea
 
 // set up DOMParser
@@ -33,7 +33,7 @@ export default class MarkdownTable {
    * (This method clones only the table field.)
    */
   clone() {
-    let newTable = [];
+    const newTable = [];
     for (let i = 0; i < this.table.length; i++) {
       newTable.push([].concat(this.table[i]));
     }
@@ -75,11 +75,11 @@ export default class MarkdownTable {
     const tableElement = dom.querySelector('table');
     const trElements = tableElement.querySelectorAll('tr');
 
-    let table = [];
+    const table = [];
     let maxRowSize = 0;
     for (let i = 0; i < trElements.length; i++) {
-      let row = [];
-      let cellElements = trElements[i].querySelectorAll('th,td');
+      const row = [];
+      const cellElements = trElements[i].querySelectorAll('th,td');
       for (let j = 0; j < cellElements.length; j++) {
         row.push(cellElements[j].innerHTML);
       }
@@ -88,12 +88,12 @@ export default class MarkdownTable {
       if (maxRowSize < row.length) maxRowSize = row.length;
     }
 
-    let align = [];
+    const align = [];
     for (let i = 0; i < maxRowSize; i++) {
       align.push('');
     }
 
-    return new MarkdownTable(table, {align: align});
+    return new MarkdownTable(table, { align });
   }
 
   /**
@@ -110,7 +110,7 @@ export default class MarkdownTable {
    */
   static fromMarkdownString(str) {
     const arrMDTableLines = str.split(/(\r\n|\r|\n)/);
-    let contents = [];
+    const contents = [];
     let aligns = [];
     for (let n = 0; n < arrMDTableLines.length; n++) {
       const line = arrMDTableLines[n];
@@ -119,15 +119,15 @@ export default class MarkdownTable {
         // parse line which described alignment
         const alignRuleRE = [
           { align: 'c', regex: /^:-+:$/ },
-          { align: 'l', regex: /^:-+$/  },
-          { align: 'r', regex: /^-+:$/  },
+          { align: 'l', regex: /^:-+$/ },
+          { align: 'r', regex: /^-+:$/ },
         ];
         let lineText = '';
         lineText = line.replace(/^\||\|$/g, ''); // strip off pipe charactor which is placed head of line and last of line.
         lineText = lineText.replace(/\s*/g, '');
-        aligns = lineText.split(/\|/).map(col => {
-          const rule = alignRuleRE.find(rule => col.match(rule.regex));
-          return (rule != undefined) ? rule.align : '';
+        aligns = lineText.split(/\|/).map((col) => {
+          const rule = alignRuleRE.find((rule) => { return col.match(rule.regex) });
+          return (rule != null) ? rule.align : '';
         });
       }
       else if (linePartOfTableRE.test(line)) {
@@ -141,4 +141,5 @@ export default class MarkdownTable {
     }
     return (new MarkdownTable(contents, { align: aligns, stringLength: stringWidth }));
   }
+
 }

+ 44 - 44
src/client/js/util/Crowi.js

@@ -15,6 +15,7 @@ import {
 } from './interceptor/detach-code-blocks';
 
 export default class Crowi {
+
   constructor(context, window) {
     this.context = context;
     this.config = {};
@@ -36,8 +37,8 @@ export default class Crowi {
     this.apiRequest = this.apiRequest.bind(this);
 
     this.interceptorManager = new InterceptorManager();
-    this.interceptorManager.addInterceptor(new DetachCodeBlockInterceptor(this), 10);       // process as soon as possible
-    this.interceptorManager.addInterceptor(new RestoreCodeBlockInterceptor(this), 900);     // process as late as possible
+    this.interceptorManager.addInterceptor(new DetachCodeBlockInterceptor(this), 10); // process as soon as possible
+    this.interceptorManager.addInterceptor(new RestoreCodeBlockInterceptor(this), 900); // process as late as possible
 
     // FIXME
     this.me = context.me;
@@ -46,7 +47,7 @@ export default class Crowi {
 
     this.users = [];
     this.userByName = {};
-    this.userById   = {};
+    this.userById = {};
     this.draft = {};
     this.editorOptions = {};
 
@@ -100,7 +101,7 @@ export default class Crowi {
       'previewOptions',
     ];
 
-    keys.forEach(key => {
+    keys.forEach((key) => {
       const keyContent = this.localStorage[key];
       if (keyContent) {
         try {
@@ -114,35 +115,36 @@ export default class Crowi {
   }
 
   fetchUsers() {
-    const interval = 1000*60*15; // 15min
+    const interval = 1000 * 60 * 15; // 15min
     const currentTime = new Date();
     if (this.localStorage.lastFetched && interval > currentTime - new Date(this.localStorage.lastFetched)) {
-      return ;
+      return;
     }
 
     this.apiGet('/users.list', {})
-    .then(data => {
-      this.users = data.users;
-      this.localStorage.users = JSON.stringify(data.users);
-
-      let userByName = {};
-      let userById = {};
-      for (let i = 0; i < data.users.length; i++) {
-        const user = data.users[i];
-        userByName[user.username] = user;
-        userById[user._id] = user;
-      }
-      this.userByName = userByName;
-      this.localStorage.userByName = JSON.stringify(userByName);
+      .then((data) => {
+        this.users = data.users;
+        this.localStorage.users = JSON.stringify(data.users);
+
+        const userByName = {};
+        const userById = {};
+        for (let i = 0; i < data.users.length; i++) {
+          const user = data.users[i];
+          userByName[user.username] = user;
+          userById[user._id] = user;
+        }
+        this.userByName = userByName;
+        this.localStorage.userByName = JSON.stringify(userByName);
 
-      this.userById = userById;
-      this.localStorage.userById = JSON.stringify(userById);
+        this.userById = userById;
+        this.localStorage.userById = JSON.stringify(userById);
 
-      this.localStorage.lastFetched = new Date();
-    }).catch(err => {
-      this.localStorage.removeItem('lastFetched');
+        this.localStorage.lastFetched = new Date();
+      })
+      .catch((err) => {
+        this.localStorage.removeItem('lastFetched');
       // ignore errors
-    });
+      });
   }
 
   setCaretLine(line) {
@@ -192,9 +194,9 @@ export default class Crowi {
   }
 
   findUserByIds(userIds) {
-    let users = [];
-    for (let userId of userIds) {
-      let user = this.findUserById(userId);
+    const users = [];
+    for (const userId of userIds) {
+      const user = this.findUserById(userId);
       if (user) {
         users.push(user);
       }
@@ -217,7 +219,7 @@ export default class Crowi {
       body: markdown,
     });
     return this.apiPost('/pages.create', params)
-      .then(res => {
+      .then((res) => {
         if (!res.ok) {
           throw new Error(res.error);
         }
@@ -232,7 +234,7 @@ export default class Crowi {
       body: markdown,
     });
     return this.apiPost('/pages.update', params)
-      .then(res => {
+      .then((res) => {
         if (!res.ok) {
           throw new Error(res.error);
         }
@@ -251,7 +253,7 @@ export default class Crowi {
   }
 
   apiGet(path, params) {
-    return this.apiRequest('get', path, {params: params});
+    return this.apiRequest('get', path, { params });
   }
 
   apiPost(path, params) {
@@ -263,22 +265,20 @@ export default class Crowi {
   }
 
   apiRequest(method, path, params) {
-
     return new Promise((resolve, reject) => {
       axios[method](`/_api${path}`, params)
-      .then(res => {
-        if (res.data.ok) {
-          resolve(res.data);
-        }
-        else {
-          reject(new Error(res.data.error));
-        }
-      })
-      .catch(res => {
-        reject(res);
-      });
+        .then((res) => {
+          if (res.data.ok) {
+            resolve(res.data);
+          }
+          else {
+            reject(new Error(res.data.error));
+          }
+        })
+        .catch((res) => {
+          reject(res);
+        });
     });
   }
 
 }
-

+ 17 - 14
src/client/js/util/GrowiRenderer.js

@@ -1,8 +1,8 @@
 import MarkdownIt from 'markdown-it';
 
-import Linker        from './PreProcessor/Linker';
-import CsvToTable    from './PreProcessor/CsvToTable';
-import XssFilter     from './PreProcessor/XssFilter';
+import Linker from './PreProcessor/Linker';
+import CsvToTable from './PreProcessor/CsvToTable';
+import XssFilter from './PreProcessor/XssFilter';
 import CrowiTemplate from './PostProcessor/CrowiTemplate';
 
 import EmojiConfigurer from './markdown-it/emoji';
@@ -32,9 +32,10 @@ export default class GrowiRenderer {
   constructor(crowi, originRenderer, options) {
     this.crowi = crowi;
     this.originRenderer = originRenderer || {};
-    this.options = Object.assign( // merge options
-      { isAutoSetup: true },      // default options
-      options || {});             // specified options
+    this.options = Object.assign( //  merge options
+      { isAutoSetup: true }, //       default options
+      options || {}, //               specified options
+    );
 
     // initialize processors
     //  that will be retrieved if originRenderer exists
@@ -89,21 +90,21 @@ export default class GrowiRenderer {
           new TocAndAnchorConfigurer(crowi, options.renderToc),
           new HeaderLineNumberConfigurer(crowi),
           new HeaderWithEditLinkConfigurer(crowi),
-          new TableWithHandsontableButtonConfigurer(crowi)
+          new TableWithHandsontableButtonConfigurer(crowi),
         ]);
         break;
       case 'editor':
         this.markdownItConfigurers = this.markdownItConfigurers.concat([
           new FooternoteConfigurer(crowi),
           new HeaderLineNumberConfigurer(crowi),
-          new TableConfigurer(crowi)
+          new TableConfigurer(crowi),
         ]);
         break;
       // case 'comment':
       //   break;
       default:
         this.markdownItConfigurers = this.markdownItConfigurers.concat([
-          new TableConfigurer(crowi)
+          new TableConfigurer(crowi),
         ]);
         break;
     }
@@ -115,7 +116,7 @@ export default class GrowiRenderer {
   setup() {
     const crowiConfig = this.crowi.config;
 
-    let isEnabledLinebreaks = undefined;
+    let isEnabledLinebreaks;
     switch (this.options.mode) {
       case 'comment':
         isEnabledLinebreaks = crowiConfig.isEnabledLinebreaksInComments;
@@ -137,14 +138,15 @@ export default class GrowiRenderer {
   }
 
   preProcess(markdown) {
+    let processed = markdown;
     for (let i = 0; i < this.preProcessors.length; i++) {
       if (!this.preProcessors[i].process) {
         continue;
       }
-      markdown = this.preProcessors[i].process(markdown);
+      processed = this.preProcessors[i].process(markdown);
     }
 
-    return markdown;
+    return processed;
   }
 
   process(markdown) {
@@ -152,14 +154,15 @@ export default class GrowiRenderer {
   }
 
   postProcess(html) {
+    let processed = html;
     for (let i = 0; i < this.postProcessors.length; i++) {
       if (!this.postProcessors[i].process) {
         continue;
       }
-      html = this.postProcessors[i].process(html);
+      processed = this.postProcessors[i].process(html);
     }
 
-    return html;
+    return processed;
   }
 
   codeRenderer(code, langExt) {

+ 13 - 13
src/client/js/util/PostProcessor/CrowiTemplate.js

@@ -4,10 +4,10 @@ export default class CrowiTemplate {
 
   constructor(crowi) {
     this.templatePattern = {
-      'year': this.getYear,
-      'month': this.getMonth,
-      'date': this.getDate,
-      'user': this.getUser,
+      year: this.getYear,
+      month: this.getMonth,
+      date: this.getDate,
+      user: this.getUser,
     };
   }
 
@@ -30,13 +30,13 @@ export default class CrowiTemplate {
 
       return (
         /* eslint-disable quotes */
-        `<div class="page-template-builder">` +
-          `<button class="template-create-button btn btn-default" data-template="${templateId}" data-path="${pageName}">` +
-            `<i class="fa fa-pencil"></i> ${pageName}` +
-          `</button>` +
-          `<pre><code id="${templateId}" class="lang-${lang}">${code}\n</code></pre>` +
-        `</div>`
-        /* eslint-enable */
+        `<div class="page-template-builder">`
+          + `<button class="template-create-button btn btn-default" data-template="${templateId}" data-path="${pageName}">`
+            + `<i class="fa fa-pencil"></i> ${pageName}`
+          + `</button>`
+          + `<pre><code id="${templateId}" class="lang-${lang}">${code}\n</code></pre>`
+        + `</div>`
+        /* eslint-enable quotes */
       );
     });
   }
@@ -67,8 +67,8 @@ export default class CrowiTemplate {
   parseTemplateString(templateString) {
     let parsed = templateString;
 
-    Object.keys(this.templatePattern).forEach(key => {
-      const k = key .replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+    Object.keys(this.templatePattern).forEach((key) => {
+      const k = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
       const matcher = new RegExp(`{${k}}`, 'g');
       if (parsed.match(matcher)) {
         const replacer = this.templatePattern[key]();

+ 2 - 1
src/client/js/util/PreProcessor/CsvToTable.js

@@ -1,8 +1,8 @@
 import csvToMarkdownTable from 'csv-to-markdown-table';
 
 export default class CsvToTable {
-  process(markdown) {
 
+  process(markdown) {
     // see: https://regex101.com/r/WR6IvX/3
     return markdown.replace(/:::\s*(\S+)[\r\n]((.|[\r\n])*?)[\r\n]:::/gm, (all, group1, group2) => {
       switch (group1) {
@@ -19,4 +19,5 @@ export default class CsvToTable {
       }
     });
   }
+
 }

+ 3 - 3
src/client/js/util/PreProcessor/Linker.js

@@ -1,14 +1,14 @@
 
 export default class Linker {
-  process(markdown) {
 
+  process(markdown) {
     return markdown
       // process angle branckets like '</Level1/Level2>'
       // see: https://regex101.com/r/rxAy4F/2
       .replace(/<((\/[^>\n]+?){2,})>/g, '<a href="$1">$1</a>') // ページ間リンク: <> でかこまれてて / から始まり、 / が2個以上
       // process square branckets like '[/Level1]'
       // see: https://regex101.com/r/QSt1yu/5
-      .replace(/\[(\/[^\]\n]+?)\](?!\()/g, '<a href="$1">$1</a>')
-    ;
+      .replace(/\[(\/[^\]\n]+?)\](?!\()/g, '<a href="$1">$1</a>');
   }
+
 }

+ 4 - 5
src/client/js/util/PreProcessor/XssFilter.js

@@ -1,5 +1,5 @@
 import Xss from '@commons/service/xss';
-import xssOption from '@commons/service/xss/xssOption';
+import XssOption from '@commons/service/xss/xssOption';
 
 export default class XssFilter {
 
@@ -7,7 +7,7 @@ export default class XssFilter {
     this.crowi = crowi;
 
     if (crowi.config.isEnabledXssPrevention) {
-      this.xssOption = new xssOption(crowi.config);
+      this.xssOption = new XssOption(crowi.config);
       this.xss = new Xss(this.xssOption);
     }
   }
@@ -16,9 +16,8 @@ export default class XssFilter {
     if (this.crowi.config.isEnabledXssPrevention) {
       return this.xss.process(markdown);
     }
-    else {
-      return markdown;
-    }
+
+    return markdown;
   }
 
 }

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

@@ -21,7 +21,7 @@ class UpdateDisplayUtil {
     const update = new DisplayUpdate(cm, cm.getViewport());
 
     // Compute a suitable new viewport (from & to)
-    let end = doc.first + doc.size;
+    const 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);

+ 14 - 11
src/client/js/util/interceptor/detach-code-blocks.js

@@ -2,9 +2,11 @@ import { BasicInterceptor } from 'growi-pluginkit';
 
 
 class DetachCodeBlockUtil {
+
   static createReplaceStr(replaceId) {
     return `<pre class="detached-code-block">${replaceId}</pre>`;
   }
+
 }
 
 /**
@@ -31,7 +33,7 @@ export class DetachCodeBlockInterceptor extends BasicInterceptor {
     if (contextName === 'prePreProcess') {
       return 'markdown';
     }
-    else if (contextName === 'prePostProcess') {
+    if (contextName === 'prePostProcess') {
       return 'parsedHTML';
     }
   }
@@ -42,22 +44,21 @@ export class DetachCodeBlockInterceptor extends BasicInterceptor {
   process(contextName, ...args) {
     this.logger.debug(`processing: 'contextName'=${contextName}`);
 
-    const context = Object.assign(args[0]);   // clone
+    const context = Object.assign(args[0]); // clone
     const targetKey = this.getTargetKey(contextName);
-    /* eslint-disable no-unused-vars */
-    const currentPagePath = context.currentPagePath;
-    /* eslint-enable */
+    const currentPagePath = context.currentPagePath; // eslint-disable-line no-unused-vars
 
     context.dcbContextMap = {};
 
     // see: https://regex101.com/r/8PAEcC/5
+    // eslint-disable-next-line max-len
     context[targetKey] = context[targetKey].replace(/(^(```|~~~)(.|[\r\n])*?(```|~~~)$)|(`[^\r\n]*?`)|(<pre>(.|[\r\n])*?<\/pre>)|(<pre\s[^>]*>(.|[\r\n])*?<\/pre>)/gm, (all) => {
       // create ID
-      const replaceId = 'dcb-' + this.createRandomStr(8);
+      const replaceId = `dcb-${this.createRandomStr(8)}`;
       this.logger.debug(`'replaceId'=${replaceId} : `, all);
 
       // register to context
-      let dcbContext = {};
+      const dcbContext = {};
       dcbContext.content = all;
       dcbContext.substituteContent = DetachCodeBlockUtil.createReplaceStr(replaceId);
       context.dcbContextMap[replaceId] = dcbContext;
@@ -79,11 +80,12 @@ export class DetachCodeBlockInterceptor extends BasicInterceptor {
   createRandomStr(length) {
     const bag = 'abcdefghijklmnopqrstuvwxyz0123456789';
     let generated = '';
-    for (var i = 0; i < length; i++) {
+    for (let i = 0; i < length; i++) {
       generated += bag[Math.floor(Math.random() * bag.length)];
     }
     return generated;
   }
+
 }
 
 
@@ -111,7 +113,7 @@ export class RestoreCodeBlockInterceptor extends BasicInterceptor {
     if (contextName === 'postPreProcess') {
       return 'markdown';
     }
-    else if (contextName === 'preRenderHtml' || contextName === 'preRenderPreviewHtml'
+    if (contextName === 'preRenderHtml' || contextName === 'preRenderPreviewHtml'
         || contextName === 'preRenderCommentHtml' || contextName === 'preRenderCommentPreviewHtml') {
       return 'parsedHTML';
     }
@@ -123,13 +125,13 @@ export class RestoreCodeBlockInterceptor extends BasicInterceptor {
   process(contextName, ...args) {
     this.logger.debug(`processing: 'contextName'=${contextName}`);
 
-    const context = Object.assign(args[0]);   // clone
+    const context = Object.assign(args[0]); // clone
     const targetKey = this.getTargetKey(contextName);
 
     // forEach keys of dcbContextMap
     Object.keys(context.dcbContextMap).forEach((replaceId) => {
       // get context object from context
-      let dcbContext = context.dcbContextMap[replaceId];
+      const dcbContext = context.dcbContextMap[replaceId];
 
       // replace it with content by using getter function so that the doller sign does not work
       // see: https://github.com/weseek/growi/issues/285
@@ -139,4 +141,5 @@ export class RestoreCodeBlockInterceptor extends BasicInterceptor {
     // resolve
     return Promise.resolve(context);
   }
+
 }

+ 1 - 0
src/client/js/util/markdown-it/blockdiag.js

@@ -13,4 +13,5 @@ export default class BlockdiagConfigurer {
       marker: ':::',
     });
   }
+
 }

+ 4 - 2
src/client/js/util/markdown-it/emoji.js

@@ -9,13 +9,15 @@ export default class EmojiConfigurer {
 
     const emojiShortnameUnicodeMap = {};
 
-    for (let unicode in emojiStrategy) {
+    /* eslint-disable guard-for-in, no-restricted-syntax */
+    for (const unicode in emojiStrategy) {
       const data = emojiStrategy[unicode];
       const shortname = data.shortname.replace(/:/g, '');
       emojiShortnameUnicodeMap[shortname] = String.fromCharCode(unicode);
     }
+    /* eslint-enable guard-for-in, no-restricted-syntax */
 
-    md.use(require('markdown-it-emoji'), {defs: emojiShortnameUnicodeMap});
+    md.use(require('markdown-it-emoji'), { defs: emojiShortnameUnicodeMap });
 
     // integrate markdown-it-emoji and emojione
     md.renderer.rules.emoji = (token, idx) => {

+ 3 - 3
src/client/js/util/markdown-it/header-line-number.js

@@ -27,9 +27,9 @@ export default class HeaderLineNumberConfigurer {
       if (original) {
         return original(tokens, idx, options, env, self);
       }
-      else {
-        return self.renderToken(tokens, idx, options, env, self);
-      }
+
+      return self.renderToken(tokens, idx, options, env, self);
     };
   }
+
 }

+ 1 - 0
src/client/js/util/markdown-it/header-with-edit-link.js

@@ -13,4 +13,5 @@ export default class HeaderWithEditLinkConfigurer {
               </span></${tokens[idx].tag}>`;
     };
   }
+
 }

+ 3 - 3
src/client/js/util/markdown-it/header.js

@@ -18,9 +18,8 @@ export default class HeaderConfigurer {
       if (original) {
         return original(tokens, idx, options, env, self);
       }
-      else {
-        return self.renderToken(tokens, idx, options, env, self);
-      }
+
+      return self.renderToken(tokens, idx, options, env, self);
     };
   }
 
@@ -32,4 +31,5 @@ export default class HeaderConfigurer {
       tokens[idx].attrJoin('class', 'revision-head');
     }
   }
+
 }

+ 1 - 1
src/client/js/util/markdown-it/mathjax.js

@@ -4,7 +4,7 @@ export default class MathJaxConfigurer {
     this.crowi = crowi;
 
     const config = crowi.getConfig();
-    this.isEnabled = !!config.env.MATHJAX;  // convert to boolean
+    this.isEnabled = !!config.env.MATHJAX; // convert to boolean
   }
 
   configure(md) {

+ 1 - 0
src/client/js/util/markdown-it/plantuml.js

@@ -22,4 +22,5 @@ export default class PlantUMLConfigurer {
     const zippedCode = plantumlEncoder.encode(`@startuml\n${umlCode}\n@enduml`);
     return urljoin(this.serverUrl, 'svg', zippedCode);
   }
+
 }

+ 3 - 1
src/client/js/util/markdown-it/table-with-handsontable-button.js

@@ -7,7 +7,8 @@ export default class TableWithHandsontableButtonConfigurer {
   configure(md) {
     md.renderer.rules.table_open = (tokens, idx) => {
       const beginLine = tokens[idx].map[0] + 1;
-      const endLine  = tokens[idx].map[1];
+      const endLine = tokens[idx].map[1];
+      // eslint-disable-next-line max-len
       return `<div class="editable-with-handsontable"><button class="handsontable-modal-trigger" onClick="crowi.launchHandsontableModal('page', ${beginLine}, ${endLine})"><i class="icon-note"></i></button><table class="table table-bordered">`;
     };
 
@@ -15,4 +16,5 @@ export default class TableWithHandsontableButtonConfigurer {
       return '</table></div>';
     };
   }
+
 }

+ 37 - 34
src/client/js/util/reveal/plugins/growi-renderer.js

@@ -5,17 +5,20 @@ import GrowiRenderer from '../../GrowiRenderer';
  */
 (function(root, factory) {
   // parent window DOM (crowi.js) of presentation window.
-  let parentWindow = window.parent;
+  const parentWindow = window.parent;
 
   // create GrowiRenderer instance and setup.
-  let growiRenderer = new GrowiRenderer(parentWindow.crowi, parentWindow.crowiRenderer, {mode: 'editor'});
+  const growiRenderer = new GrowiRenderer(parentWindow.crowi, parentWindow.crowiRenderer, { mode: 'editor' });
 
-  let growiRendererPlugin = factory(growiRenderer);
+  const growiRendererPlugin = factory(growiRenderer);
   growiRendererPlugin.initialize();
-}(this, function(growiRenderer) {
-  const DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
-    DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
-    DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
+}(this, (growiRenderer) => {
+  /* eslint-disable no-useless-escape */
+  const DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$';
+  const DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$';
+  const DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
+  /* eslint-enable no-useless-escape */
+
   let marked;
 
   /**
@@ -23,16 +26,16 @@ import GrowiRenderer from '../../GrowiRenderer';
    * starting with '#' to markdown.
    */
   function divideSlides() {
-    let sections = document.querySelectorAll('[data-markdown]');
+    const sections = document.querySelectorAll('[data-markdown]');
     for (let i = 0, len = sections.length; i < len; i++) {
-      let section = sections[i];
-      let markdown = marked.getMarkdownFromSlide(section);
-      let context = {markdown};
+      const section = sections[i];
+      const markdown = marked.getMarkdownFromSlide(section);
+      const context = { markdown };
       const interceptorManager = growiRenderer.crowi.interceptorManager;
-      let dataSeparator = section.getAttribute( 'data-separator' ) || DEFAULT_SLIDE_SEPARATOR;
+      let dataSeparator = section.getAttribute('data-separator') || DEFAULT_SLIDE_SEPARATOR;
       // replace string '\n' to LF code.
       dataSeparator = dataSeparator.replace(/\\n/g, '\n');
-      const replaceValue = dataSeparator + '#';
+      const replaceValue = `${dataSeparator}#`;
       // detach code block.
       interceptorManager.process('prePreProcess', context);
       // if there is only '\n' in the first line, replace it.
@@ -49,50 +52,50 @@ import GrowiRenderer from '../../GrowiRenderer';
    * Converts data-markdown slides to HTML slides by GrowiRenderer.
    */
   function convertSlides() {
-    let sections = document.querySelectorAll('[data-markdown]');
+    const sections = document.querySelectorAll('[data-markdown]');
     let markdown;
     const interceptorManager = growiRenderer.crowi.interceptorManager;
 
     for (let i = 0, len = sections.length; i < len; i++) {
-      let section = sections[i];
+      const section = sections[i];
 
       // Only parse the same slide once
       if (!section.getAttribute('data-markdown-parsed')) {
         section.setAttribute('data-markdown-parsed', 'true');
-        let notes = section.querySelector( 'aside.notes' );
+        const notes = section.querySelector('aside.notes');
         markdown = marked.getMarkdownFromSlide(section);
-        let context = { markdown };
+        const context = { markdown };
 
         interceptorManager.process('preRender', context)
-          .then(() => interceptorManager.process('prePreProcess', context))
+          .then(() => { return interceptorManager.process('prePreProcess', context) })
           .then(() => {
             context.markdown = growiRenderer.preProcess(context.markdown);
           })
-          .then(() => interceptorManager.process('postPreProcess', context))
+          .then(() => { return interceptorManager.process('postPreProcess', context) })
           .then(() => {
-            context['parsedHTML'] = growiRenderer.process(context.markdown);
+            context.parsedHTML = growiRenderer.process(context.markdown);
           })
-          .then(() => interceptorManager.process('prePostProcess', context))
+          .then(() => { return interceptorManager.process('prePostProcess', context) })
           .then(() => {
             context.parsedHTML = growiRenderer.postProcess(context.parsedHTML);
           })
-          .then(() => interceptorManager.process('postPostProcess', context))
-          .then(() => interceptorManager.process('preRenderHtml', context))
-          .then(() => interceptorManager.process('postRenderHtml', context))
+          .then(() => { return interceptorManager.process('postPostProcess', context) })
+          .then(() => { return interceptorManager.process('preRenderHtml', context) })
+          .then(() => { return interceptorManager.process('postRenderHtml', context) })
           .then(() => {
             section.innerHTML = context.parsedHTML;
           });
-        marked.addAttributes(   section, section, null, section.getAttribute( 'data-element-attributes' ) ||
-          section.parentNode.getAttribute( 'data-element-attributes' ) ||
-          DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
-        section.getAttribute( 'data-attributes' ) ||
-          section.parentNode.getAttribute( 'data-attributes' ) ||
-          DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
+        marked.addAttributes(section, section, null, section.getAttribute('data-element-attributes')
+          || section.parentNode.getAttribute('data-element-attributes')
+          || DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
+        section.getAttribute('data-attributes')
+          || section.parentNode.getAttribute('data-attributes')
+          || DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
 
         // If there were notes, we need to re-add them after
         // having overwritten the section's HTML
-        if ( notes ) {
-          section.appendChild( notes );
+        if (notes) {
+          section.appendChild(notes);
         }
       }
     }
@@ -100,12 +103,12 @@ import GrowiRenderer from '../../GrowiRenderer';
 
   // API
   return {
-    initialize: async function() {
+    async initialize() {
       growiRenderer.setup();
       marked = require('./markdown').default(growiRenderer.process);
       divideSlides();
       marked.processSlides();
       convertSlides();
-    }
+    },
   };
 }));

+ 16 - 13
src/lib/service/cdn-resources-downloader.js

@@ -26,13 +26,14 @@ class CdnResourcesDownloader {
     const opts = Object.assign({}, options);
     const ext = opts.ext || 'js';
 
-    const promises = cdnResources.map(cdnResource => {
+    const promises = cdnResources.map((cdnResource) => {
       this.logger.info(`Processing CdnResource '${cdnResource.name}'`);
 
       return this.downloadAndWriteToFS(
         cdnResource.url,
         cdnResource.outDir,
-        `${cdnResource.name}.${ext}`);
+        `${cdnResource.name}.${ext}`,
+      );
     });
 
     return Promise.all(promises);
@@ -52,7 +53,7 @@ class CdnResourcesDownloader {
 
     // styles
     const assetsResourcesStore = [];
-    const promisesForStyle = cdnResources.map(cdnResource => {
+    const promisesForStyle = cdnResources.map((cdnResource) => {
       this.logger.info(`Processing CdnResource '${cdnResource.name}'`);
 
       let urlReplacer = null;
@@ -66,7 +67,8 @@ class CdnResourcesDownloader {
         cdnResource.url,
         cdnResource.outDir,
         `${cdnResource.name}.${ext}`,
-        urlReplacer);
+        urlReplacer,
+      );
     });
 
     // wait until all styles are downloaded
@@ -75,13 +77,14 @@ class CdnResourcesDownloader {
     this.logger.debug('Downloading assets', assetsResourcesStore);
 
     // assets in css
-    const promisesForAssets = assetsResourcesStore.map(cdnResource => {
+    const promisesForAssets = assetsResourcesStore.map((cdnResource) => {
       this.logger.info(`Processing assts in css '${cdnResource.name}'`);
 
       return this.downloadAndWriteToFS(
         cdnResource.url,
         cdnResource.outDir,
-        cdnResource.name);
+        cdnResource.name,
+      );
     });
 
     return Promise.all(promisesForAssets);
@@ -100,12 +103,12 @@ class CdnResourcesDownloader {
    */
   generateReplaceUrlInCssStream(cdnResource, assetsResourcesStore, webroot) {
     return replaceStream(
-      /url\((?!['"]?data:)["']?(.+?)["']?\)/g,    // https://regex101.com/r/Sds38A/3
+      /url\((?!['"]?data:)["']?(.+?)["']?\)/g, // https://regex101.com/r/Sds38A/3
       (match, url) => {
         // generate URL Object
         const parsedUrl = url.startsWith('http')
-          ? new URL(url)                    // when url is fqcn
-          : new URL(url, cdnResource.url);  // when url is relative
+          ? new URL(url) // when url is fqcn
+          : new URL(url, cdnResource.url); // when url is relative
         const basename = path.basename(parsedUrl.pathname);
 
         this.logger.debug(`${cdnResource.name} has ${parsedUrl.toString()}`);
@@ -115,13 +118,14 @@ class CdnResourcesDownloader {
           new CdnResource(
             basename,
             parsedUrl.toString(),
-            path.join(cdnResource.outDir, cdnResource.name)
-          )
+            path.join(cdnResource.outDir, cdnResource.name),
+          ),
         );
 
         const replaceUrl = urljoin(webroot, cdnResource.name, basename);
         return `url(${replaceUrl})`;
-      });
+      },
+    );
   }
 
   async downloadAndWriteToFS(url, outDir, fileName, replacestream) {
@@ -140,7 +144,6 @@ class CdnResourcesDownloader {
 
     return streamToPromise(stream);
   }
-
 }
 
 module.exports = CdnResourcesDownloader;

+ 12 - 13
src/lib/service/cdn-resources-service.js

@@ -24,14 +24,14 @@ class CdnResourcesService {
 
   getScriptManifestByName(name) {
     const manifests = this.cdnManifests.js
-      .filter(manifest => manifest.name === name);
+      .filter((manifest) => { return manifest.name === name });
 
     return (manifests.length > 0) ? manifests[0] : null;
   }
 
   getStyleManifestByName(name) {
     const manifests = this.cdnManifests.style
-      .filter(manifest => manifest.name === name);
+      .filter((manifest) => { return manifest.name === name });
 
     return (manifests.length > 0) ? manifests[0] : null;
   }
@@ -46,11 +46,11 @@ class CdnResourcesService {
   async downloadAndWriteAll(cdnResourceDownloader) {
     const CdnResource = require('@commons/models/cdn-resource');
 
-    const cdnScriptResources = this.cdnManifests.js.map(manifest => {
+    const cdnScriptResources = this.cdnManifests.js.map((manifest) => {
       const outDir = helpers.root(cdnLocalScriptRoot);
       return new CdnResource(manifest.name, manifest.url, outDir);
     });
-    const cdnStyleResources = this.cdnManifests.style.map(manifest => {
+    const cdnStyleResources = this.cdnManifests.style.map((manifest) => {
       const outDir = helpers.root(cdnLocalStyleRoot);
       return new CdnResource(manifest.name, manifest.url, outDir);
     });
@@ -58,7 +58,7 @@ class CdnResourcesService {
     const dlStylesOptions = {
       replaceUrl: {
         webroot: cdnLocalStyleWebRoot,
-      }
+      },
     };
 
     return Promise.all([
@@ -87,7 +87,7 @@ class CdnResourcesService {
     // TODO process integrity
 
     const url = noCdn
-      ? urljoin(cdnLocalScriptWebRoot, manifest.name) + '.js'
+      ? `${urljoin(cdnLocalScriptWebRoot, manifest.name)}.js`
       : manifest.url;
     return `<script src="${url}" ${attrs.join(' ')}></script>`;
   }
@@ -99,10 +99,10 @@ class CdnResourcesService {
 
   getScriptTagsByGroup(group) {
     return this.cdnManifests.js
-      .filter(manifest => {
+      .filter((manifest) => {
         return manifest.groups != null && manifest.groups.includes(group);
       })
-      .map(manifest => {
+      .map((manifest) => {
         return this.generateScriptTag(manifest, this.noCdn);
       });
   }
@@ -127,7 +127,7 @@ class CdnResourcesService {
     // TODO process integrity
 
     const url = noCdn
-      ? urljoin(cdnLocalStyleWebRoot, manifest.name) + '.css'
+      ? `${urljoin(cdnLocalStyleWebRoot, manifest.name)}.css`
       : manifest.url;
 
     return `<link rel="stylesheet" href="${url}" ${attrs.join(' ')}>`;
@@ -140,10 +140,10 @@ class CdnResourcesService {
 
   getStyleTagsByGroup(group) {
     return this.cdnManifests.style
-      .filter(manifest => {
+      .filter((manifest) => {
         return manifest.groups != null && manifest.groups.includes(group);
       })
-      .map(manifest => {
+      .map((manifest) => {
         return this.generateStyleTag(manifest, this.noCdn);
       });
   }
@@ -153,7 +153,7 @@ class CdnResourcesService {
 
     // replace style
     if (!this.noCdn) {
-      const url = new URL(`${styleName}.css`, manifest.url);  // resolve `${styleName}.css` from manifest.url
+      const url = new URL(`${styleName}.css`, manifest.url); // resolve `${styleName}.css` from manifest.url
 
       // clone manifest
       manifest = Object.assign(manifest, { url: url.toString() });
@@ -161,7 +161,6 @@ class CdnResourcesService {
 
     return this.generateStyleTag(manifest, this.noCdn);
   }
-
 }
 
 module.exports = CdnResourcesService;

+ 13 - 15
src/lib/service/interceptor-manager.js

@@ -4,7 +4,6 @@ const logger = require('@alias/logger')('growi:InterceptorManager');
  * the manager class of Interceptor
  */
 class InterceptorManager {
-
   constructor() {
     this.interceptorAndOrders = []; /* [
                                           {interceptor: instanceA, order: 200 },
@@ -31,25 +30,25 @@ class InterceptorManager {
   addInterceptors(interceptors, order) {
     let isDefaultOrder = false;
     if (order == null) {
-      order = 100;
+      order = 100; // eslint-disable-line
       isDefaultOrder = true;
     }
 
-    const interceptorIds = interceptors.map((i) => i.getId());
+    const interceptorIds = interceptors.map((i) => { return i.getId() });
     logger.info(`'addInterceptors' invoked. adding interceptors '${interceptorIds}' at order=${order}${isDefaultOrder ? '(default)' : ''}`);
 
     this.interceptorAndOrders = this.interceptorAndOrders.concat(
-      interceptors.map(interceptor => {
+      interceptors.map((interceptor) => {
         return { interceptor, order };
-      })
+      }),
     );
 
     // sort asc
-    this.interceptorAndOrders.sort((a, b) => a.order - b.order);
+    this.interceptorAndOrders.sort((a, b) => { return a.order - b.order });
     // store sorted list
-    this.interceptors = this.interceptorAndOrders.map(obj => obj.interceptor);
+    this.interceptors = this.interceptorAndOrders.map((obj) => { return obj.interceptor });
 
-    const thisInterceptorIds = this.interceptors.map((i) => i.getId());
+    const thisInterceptorIds = this.interceptors.map((i) => { return i.getId() });
     logger.info(`interceptors list has initialized: ${thisInterceptorIds}`);
   }
 
@@ -63,10 +62,10 @@ class InterceptorManager {
     logger.debug(`processing the context '${contextName}'`);
 
     // filter only contexts matches to specified 'contextName'
-    const matchInterceptors = this.interceptors.filter((i) => i.isInterceptWhen(contextName));
+    const matchInterceptors = this.interceptors.filter((i) => { return i.isInterceptWhen(contextName) });
 
-    const parallels = matchInterceptors.filter((i) => i.isProcessableParallel());
-    const sequentials = matchInterceptors.filter((i) => !i.isProcessableParallel());
+    const parallels = matchInterceptors.filter((i) => { return i.isProcessableParallel() });
+    const sequentials = matchInterceptors.filter((i) => { return !i.isProcessableParallel() });
 
     logger.debug(`${parallels.length} parallel interceptors found.`);
     logger.debug(`${sequentials.length} sequencial interceptors found.`);
@@ -79,12 +78,11 @@ class InterceptorManager {
       // sequential
       .concat([
         sequentials.reduce((prevPromise, nextInterceptor) => {
-          return prevPromise.then((...results) => this.doProcess(nextInterceptor, contextName, ...results));
-        }, Promise.resolve(...args)/* initial Promise */)
-      ])
+          return prevPromise.then((...results) => { return this.doProcess(nextInterceptor, contextName, ...results) });
+        }, Promise.resolve(...args)/* initial Promise */),
+      ]),
     ).then(() => {
       logger.debug(`end processing the context '${contextName}'`);
-      return;
     });
   }
 

+ 11 - 10
src/lib/service/logger/index.js

@@ -1,14 +1,14 @@
-const bunyan = require('bunyan');   // will be replaced to browser-bunyan on browser by webpack
+const bunyan = require('bunyan'); // will be replaced to browser-bunyan on browser by webpack
 const minimatch = require('minimatch');
 
 const isBrowser = typeof window !== 'undefined';
 const isProd = process.env.NODE_ENV === 'production';
 
-let config = require('@root/config').logger;
-let stream = isProd ? require('./stream.prod') : require('./stream.dev');
+const config = require('@root/config').logger;
+const stream = isProd ? require('./stream.prod') : require('./stream.dev');
 
 // logger store
-let loggers = {};
+const loggers = {};
 
 
 // merge configuration from environment variables
@@ -19,11 +19,11 @@ const envLevelMap = {
   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,...'
+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
+    envVars.split(',').forEach((ns) => { // ['growi:routes:page', 'growi:models.page', ...].forEach
       config[ns.trim()] = level;
     });
   }
@@ -36,17 +36,18 @@ Object.keys(envLevelMap).forEach(envName => {   // ['INFO', 'DEBUG', ...].forEac
  */
 function determineLoggerLevel(name) {
   if (isBrowser && isProd) {
-    'error';
+    return 'error';
   }
 
   let level = config.default;
 
+  /* eslint-disable array-callback-return, no-useless-return */
   // retrieve configured level
-  Object.keys(config).some(key => { // breakable forEach
+  Object.keys(config).some((key) => { //  breakable forEach
     // test whether 'name' matches to 'key'(blob)
     if (minimatch(name, key)) {
       level = config[key];
-      return;                       // break if match
+      return; //                          break if match
     }
   });
 

+ 1 - 1
src/lib/service/logger/stream.dev.js

@@ -1,6 +1,6 @@
 const isBrowser = typeof window !== 'undefined';
 
-let stream = undefined;
+let stream;
 
 // browser settings
 if (isBrowser) {

+ 1 - 1
src/lib/service/logger/stream.prod.js

@@ -1,6 +1,6 @@
 const isBrowser = typeof window !== 'undefined';
 
-let stream = undefined;
+let stream;
 
 // browser settings
 if (isBrowser) {

+ 6 - 8
src/lib/service/xss/index.js

@@ -1,25 +1,24 @@
 class Xss {
-
   constructor(xssOption) {
     const xss = require('xss');
 
-    xssOption = xssOption || {};
+    xssOption = xssOption || {}; // eslint-disable-line no-param-reassign
 
     const tagWhiteList = xssOption.tagWhiteList || [];
     const attrWhiteList = xssOption.attrWhiteList || [];
 
-    let whiteListContent = {};
+    const whiteListContent = {};
 
     // default
-    let option = {
+    const option = {
       stripIgnoreTag: true,
-      stripIgnoreTagBody: false,    // see https://github.com/weseek/growi/pull/505
+      stripIgnoreTagBody: false, // see https://github.com/weseek/growi/pull/505
       css: false,
       whiteList: whiteListContent,
-      escapeHtml: (html) => html,   // resolve https://github.com/weseek/growi/issues/221
+      escapeHtml: (html) => { return html }, // resolve https://github.com/weseek/growi/issues/221
     };
 
-    tagWhiteList.forEach(tag => {
+    tagWhiteList.forEach((tag) => {
       whiteListContent[tag] = attrWhiteList;
     });
 
@@ -30,7 +29,6 @@ class Xss {
   process(document) {
     return this.myxss.process(document);
   }
-
 }
 
 module.exports = Xss;

+ 2 - 2
src/lib/service/xss/recommendedXssWhiteList.js

@@ -14,6 +14,6 @@ const tags = [
 const attrs = ['src', 'href', 'class', 'id', 'width', 'height', 'alt', 'title', 'style'];
 
 module.exports = {
-  'tags': tags,
-  'attrs': attrs,
+  tags,
+  attrs,
 };

+ 0 - 2
src/lib/service/xss/xssOption.js

@@ -1,5 +1,4 @@
 class XssOption {
-
   constructor(config) {
     const recommendedXssWhiteList = require('./recommendedXssWhiteList');
     const initializedConfig = (config != null) ? config : {};
@@ -8,6 +7,5 @@ class XssOption {
     this.tagWhiteList = initializedConfig.tagWhiteList || recommendedXssWhiteList.tags;
     this.attrWhiteList = initializedConfig.attrWhiteList || recommendedXssWhiteList.attrs;
   }
-
 }
 module.exports = XssOption;

+ 9 - 10
src/lib/util/path-utils.js

@@ -1,7 +1,14 @@
-'use strict';
+
+function encodePagePath(path) {
+  const paths = path.split('/');
+  paths.forEach((item, index) => {
+    paths[index] = encodeURIComponent(item);
+  });
+  return paths.join('/');
+}
 
 function encodePagesPath(pages) {
-  pages.forEach(function(page) {
+  pages.forEach((page) => {
     if (!page.path) {
       return;
     }
@@ -10,14 +17,6 @@ function encodePagesPath(pages) {
   return pages;
 }
 
-function encodePagePath(path) {
-  const paths = path.split('/');
-  paths.forEach(function(item, index) {
-    paths[index] = encodeURIComponent(item);
-  });
-  return paths.join('/');
-}
-
 function matchSlashes(path) {
   // https://regex101.com/r/Z21fEd/5
   return path.match(/^((\/+)?(.+?))(\/+)?$/);

+ 4 - 4
src/lib/util/template-checker.js

@@ -2,12 +2,12 @@
  * templateChecker
  */
 
-module.exports = function(path) {
-  'use strict';
-
+function checkTemplatePath(path) {
   if (path.match(/.*\/_{1,2}template$/)) {
     return true;
   }
 
   return false;
-};
+}
+
+module.exports = checkTemplatePath;

+ 3 - 5
src/migrations/20180926134048-make-email-unique.js

@@ -1,5 +1,3 @@
-'use strict';
-
 require('module-alias/register');
 const logger = require('@alias/logger')('growi:migrate:make-email-unique');
 
@@ -15,13 +13,13 @@ module.exports = {
     const User = require('@server/models/user')();
 
     // get all users who has 'deleted@deleted' email
-    const users = await User.find({email: 'deleted@deleted'});
+    const users = await User.find({ email: 'deleted@deleted' });
     if (users.length > 0) {
       logger.info(`${users.length} users found. Replace email...`, users);
     }
 
     // make email unique
-    const promises = users.map(user => {
+    const promises = users.map((user) => {
       const now = new Date();
       const deletedLabel = `deleted_at_${now.getTime()}`;
       user.email = `${deletedLabel}@deleted`;
@@ -42,6 +40,6 @@ module.exports = {
   down(db, next) {
     // do not rollback
     next();
-  }
+  },
 
 };

+ 5 - 5
src/migrations/20180927102719-init-serverurl.js

@@ -1,4 +1,4 @@
-'use strict';
+
 
 require('module-alias/register');
 const logger = require('@alias/logger')('growi:migrate:init-serverurl');
@@ -43,7 +43,7 @@ module.exports = {
         { key: 'security:passport-google:callbackUrl' },
         { key: 'security:passport-twitter:callbackUrl' },
         { key: 'security:passport-saml:callbackUrl' },
-      ]
+      ],
     });
 
     // determine serverUrl
@@ -53,11 +53,11 @@ module.exports = {
       logger.info(configs);
 
       // extract domain
-      const siteUrls = configs.map(config => {
+      const siteUrls = configs.map((config) => {
         // see https://regex101.com/r/Q0Isjo/2
         const match = config.value.match(/^"(https?:\/\/[^/]+).*"$/);
         return (match != null) ? match[1] : null;
-      }).filter(value => value != null);
+      }).filter((value) => { return value != null });
 
       // determine serverUrl if all values are same
       if (siteUrls.length > 0 && isAllValuesSame(siteUrls)) {
@@ -84,6 +84,6 @@ module.exports = {
     });
 
     logger.info('Migration has successfully undoed');
-  }
+  },
 
 };

+ 7 - 5
src/migrations/20181019114028-abolish-page-group-relation.js

@@ -1,5 +1,3 @@
-'use strict';
-
 require('module-alias/register');
 const logger = require('@alias/logger')('growi:migrate:abolish-page-group-relation');
 
@@ -45,7 +43,8 @@ module.exports = {
     // retrieve all documents from 'pagegrouprelations'
     const relations = await db.collection('pagegrouprelations').find().toArray();
 
-    for (let relation of relations) {
+    /* eslint-disable no-await-in-loop */
+    for (const relation of relations) {
       const page = await Page.findOne({ _id: relation.targetPage });
 
       // skip if grant mismatch
@@ -63,6 +62,7 @@ module.exports = {
       page.grantedGroup = userGroup;
       await page.save();
     }
+    /* eslint-enable no-await-in-loop */
 
     // drop collection
     await db.collection('pagegrouprelations').drop();
@@ -80,7 +80,8 @@ module.exports = {
     // retrieve all Page documents which granted by UserGroup
     const relatedPages = await Page.find({ grant: Page.GRANT_USER_GROUP });
     const insertDocs = [];
-    for (let page of relatedPages) {
+    /* eslint-disable no-await-in-loop */
+    for (const page of relatedPages) {
       if (page.grantedGroup == null) {
         continue;
       }
@@ -103,10 +104,11 @@ module.exports = {
       page.grantedGroup = undefined;
       await page.save();
     }
+    /* eslint-enable no-await-in-loop */
 
     await db.collection('pagegrouprelations').insertMany(insertDocs);
 
     logger.info('Migration has successfully undoed');
-  }
+  },
 
 };

+ 4 - 4
src/server/app.js

@@ -12,9 +12,9 @@ const helpers = require('@commons/util/helpers');
 const growi = new (require('./crowi'))(helpers.root());
 
 
-/************************************
+/** **********************************
  *          Main Process
- ***********************************/
+ ********************************** */
 process.on('uncaughtException', (err) => {
   logger.error('Uncaught Exception: ', err);
 });
@@ -24,7 +24,7 @@ process.on('unhandledRejection', (reason, p) => {
 });
 
 growi.start()
-  .then(server => {
+  .then((server) => {
     if (helpers.hasProcessFlag('ci')) {
       logger.info('"--ci" flag is detected. Exit process.');
       server.close(() => {
@@ -32,7 +32,7 @@ growi.start()
       });
     }
   })
-  .catch(err => {
+  .catch((err) => {
     logger.error('An error occurred, unable to start the server');
     logger.error(err);
     process.exit(1);

+ 4 - 6
src/server/crowi/dev.js

@@ -7,7 +7,6 @@ const onHeaders = require('on-headers');
 
 
 class CrowiDev {
-
   /**
    * Creates an instance of CrowiDev.
    * @param {Crowi} crowi
@@ -27,7 +26,7 @@ class CrowiDev {
 
   initPromiseRejectionWarningHandler() {
     // https://qiita.com/syuilo/items/0800d7e44e93203c7285
-    process.on('unhandledRejection', console.dir);  // eslint-disable-line no-console
+    process.on('unhandledRejection', console.dir); // eslint-disable-line no-console
   }
 
   initSwig() {
@@ -40,10 +39,10 @@ class CrowiDev {
   requireForAutoReloadServer() {
     // load all json files for live reloading
     fs.readdirSync(this.crowi.localeDir)
-      .filter(filename => {
+      .filter((filename) => {
         return fs.statSync(path.join(this.crowi.localeDir, filename)).isDirectory();
       })
-      .map((dirname) => {
+      .forEach((dirname) => {
         require(path.join(this.crowi.localeDir, dirname, 'translation.json'));
       });
   }
@@ -119,7 +118,7 @@ class CrowiDev {
         `${this.crowi.viewsDir}/**/*.html`,
         `${this.crowi.publicDir}/**/*.js`,
         `${this.crowi.publicDir}/**/*.css`,
-      ]
+      ],
     });
     app.use(require('connect-browser-sync')(bs));
   }
@@ -127,7 +126,6 @@ class CrowiDev {
   loadPlugins(app) {
     if (process.env.PLUGIN_NAMES_TOBE_LOADED !== undefined
         && process.env.PLUGIN_NAMES_TOBE_LOADED.length > 0) {
-
       const pluginNames = process.env.PLUGIN_NAMES_TOBE_LOADED.split(',');
       logger.debug('[development] loading Plugins', pluginNames);
 

+ 51 - 53
src/server/crowi/express-init.js

@@ -1,33 +1,31 @@
-'use strict';
 
-module.exports = function(crowi, app) {
-  const debug = require('debug')('growi:crowi:express-init')
-    , path           = require('path')
-    , express        = require('express')
-    , helmet         = require('helmet')
-    , bodyParser     = require('body-parser')
-    , cookieParser   = require('cookie-parser')
-    , methodOverride = require('method-override')
-    , passport       = require('passport')
-    , session        = require('express-session')
-    , sanitizer      = require('express-sanitizer')
-    , basicAuth      = require('basic-auth-connect')
-    , flash          = require('connect-flash')
-    , swig           = require('swig-templates')
-    , webpackAssets  = require('express-webpack-assets')
-    , i18next        = require('i18next')
-    , i18nFsBackend  = require('i18next-node-fs-backend')
-    , i18nSprintf    = require('i18next-sprintf-postprocessor')
-    , i18nMiddleware = require('i18next-express-middleware')
-    , i18nUserSettingDetector  = require('../util/i18nUserSettingDetector')
-    , env            = crowi.node_env
-    , config         = crowi.getConfig()
-    , middleware     = require('../util/middlewares')
-
-    , Config = crowi.model('Config')
-    , User = crowi.model('User')
-    ;
 
+module.exports = function(crowi, app) {
+  const debug = require('debug')('growi:crowi:express-init');
+  const path = require('path');
+  const express = require('express');
+  const helmet = require('helmet');
+  const bodyParser = require('body-parser');
+  const cookieParser = require('cookie-parser');
+  const methodOverride = require('method-override');
+  const passport = require('passport');
+  const session = require('express-session');
+  const sanitizer = require('express-sanitizer');
+  const basicAuth = require('basic-auth-connect');
+  const flash = require('connect-flash');
+  const swig = require('swig-templates');
+  const webpackAssets = require('express-webpack-assets');
+  const i18next = require('i18next');
+  const i18nFsBackend = require('i18next-node-fs-backend');
+  const i18nSprintf = require('i18next-sprintf-postprocessor');
+  const i18nMiddleware = require('i18next-express-middleware');
+  const i18nUserSettingDetector = require('../util/i18nUserSettingDetector');
+  const env = crowi.node_env;
+  const config = crowi.getConfig();
+  const middleware = require('../util/middlewares');
+
+  const Config = crowi.model('Config');
+  const User = crowi.model('User');
   const lngDetector = new i18nMiddleware.LanguageDetector();
   lngDetector.addDetector(i18nUserSettingDetector);
 
@@ -38,9 +36,9 @@ module.exports = function(crowi, app) {
     .init({
       // debug: true,
       fallbackLng: [User.LANG_EN_US],
-      whitelist: Object.keys(User.getLanguageLabels()).map((k) => User[k]),
+      whitelist: Object.keys(User.getLanguageLabels()).map((k) => { return User[k] }),
       backend: {
-        loadPath: crowi.localeDir + '{{lng}}/translation.json'
+        loadPath: `${crowi.localeDir}{{lng}}/translation.json`,
       },
       detection: {
         order: ['userSettingDetector', 'header', 'navigator'],
@@ -48,31 +46,31 @@ module.exports = function(crowi, app) {
       overloadTranslationOptionHandler: i18nSprintf.overloadTranslationOptionHandler,
 
       // change nsSeparator from ':' to '::' because ':' is used in config keys and these are used in i18n keys
-      nsSeparator: '::'
+      nsSeparator: '::',
     });
 
   app.use(helmet());
 
-  app.use(function(req, res, next) {
-    const now = new Date()
-      , tzoffset = -(config.crowi['app:timezone'] || 9) * 60 // for datez
-      , Page = crowi.model('Page')
-      , User = crowi.model('User')
-      , Config = crowi.model('Config')
-      ;
+  app.use((req, res, next) => {
+    const now = new Date();
+    const tzoffset = -(config.crowi['app:timezone'] || 9) * 60;
+    // for datez
 
+    const Page = crowi.model('Page');
+    const User = crowi.model('User');
+    const Config = crowi.model('Config');
     app.set('tzoffset', tzoffset);
 
     req.config = config;
     req.csrfToken = null;
 
-    res.locals.req      = req;
-    res.locals.baseUrl  = crowi.configManager.getSiteUrl();
-    res.locals.config   = config;
-    res.locals.env      = env;
-    res.locals.now      = now;
+    res.locals.req = req;
+    res.locals.baseUrl = crowi.configManager.getSiteUrl();
+    res.locals.config = config;
+    res.locals.env = env;
+    res.locals.now = now;
     res.locals.tzoffset = tzoffset;
-    res.locals.consts   = {
+    res.locals.consts = {
       pageGrants: Page.getGrantLabels(),
       userStatus: User.getUserStatusLabels(),
       language:   User.getLanguageLabels(),
@@ -85,25 +83,25 @@ module.exports = function(crowi, app) {
   });
 
   app.set('port', crowi.port);
-  const staticOption = (crowi.node_env === 'production') ? {maxAge: '30d'} : {};
+  const staticOption = (crowi.node_env === 'production') ? { maxAge: '30d' } : {};
   app.use(express.static(crowi.publicDir, staticOption));
   app.engine('html', swig.renderFile);
   app.use(webpackAssets(
     path.join(crowi.publicDir, 'manifest.json'),
-    { devMode: (crowi.node_env === 'development') })
-  );
+    { devMode: (crowi.node_env === 'development') },
+  ));
   // app.set('view cache', false);  // Default: true in production, otherwise undefined. -- 2017.07.04 Yuki Takei
   app.set('view engine', 'html');
   app.set('views', crowi.viewsDir);
   app.use(methodOverride());
   app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
-  app.use(bodyParser.json({limit: '50mb'}));
+  app.use(bodyParser.json({ limit: '50mb' }));
   app.use(sanitizer());
   app.use(cookieParser());
   app.use(session(crowi.sessionConfig));
 
   // Set basic auth middleware
-  app.use(function(req, res, next) {
+  app.use((req, res, next) => {
     if (req.query.access_token || req.body.access_token) {
       return next();
     }
@@ -119,11 +117,11 @@ module.exports = function(crowi, app) {
     if (config.crowi['security:basicName'] && config.crowi['security:basicSecret']) {
       return basicAuth(
         config.crowi['security:basicName'],
-        config.crowi['security:basicSecret'])(req, res, next);
-    }
-    else {
-      next();
+        config.crowi['security:basicSecret'],
+      )(req, res, next);
     }
+
+    next();
   });
 
   // passport

+ 71 - 79
src/server/crowi/index.js

@@ -1,37 +1,35 @@
-'use strict';
 
 
-const debug = require('debug')('growi:crowi')
-  , logger = require('@alias/logger')('growi:crowi')
-  , pkg = require('@root/package.json')
-  , InterceptorManager = require('@commons/service/interceptor-manager')
-  , CdnResourcesService = require('@commons/service/cdn-resources-service')
-  , Xss = require('@commons/service/xss')
-  , path = require('path')
-  , sep = path.sep
+const debug = require('debug')('growi:crowi');
+const logger = require('@alias/logger')('growi:crowi');
+const pkg = require('@root/package.json');
+const InterceptorManager = require('@commons/service/interceptor-manager');
+const CdnResourcesService = require('@commons/service/cdn-resources-service');
+const Xss = require('@commons/service/xss');
+const path = require('path');
 
-  , mongoose    = require('mongoose')
+const sep = path.sep;
 
-  , models = require('../models')
+const mongoose = require('mongoose');
 
-  ;
+const models = require('../models');
 
 function Crowi(rootdir) {
   const self = this;
 
   this.version = pkg.version;
-  this.runtimeVersions = undefined;   // initialized by scanRuntimeVersions()
-
-  this.rootDir     = rootdir;
-  this.pluginDir   = path.join(this.rootDir, 'node_modules') + sep;
-  this.publicDir   = path.join(this.rootDir, 'public') + sep;
-  this.libDir      = path.join(this.rootDir, 'src/server') + sep;
-  this.eventsDir   = path.join(this.libDir, 'events') + sep;
-  this.viewsDir    = path.join(this.libDir, 'views') + sep;
+  this.runtimeVersions = undefined; // initialized by scanRuntimeVersions()
+
+  this.rootDir = rootdir;
+  this.pluginDir = path.join(this.rootDir, 'node_modules') + sep;
+  this.publicDir = path.join(this.rootDir, 'public') + sep;
+  this.libDir = path.join(this.rootDir, 'src/server') + sep;
+  this.eventsDir = path.join(this.libDir, 'events') + sep;
+  this.viewsDir = path.join(this.libDir, 'views') + sep;
   this.resourceDir = path.join(this.rootDir, 'resource') + sep;
-  this.localeDir   = path.join(this.resourceDir, 'locales') + sep;
-  this.tmpDir      = path.join(this.rootDir, 'tmp') + sep;
-  this.cacheDir    = path.join(this.tmpDir, 'cache');
+  this.localeDir = path.join(this.resourceDir, 'locales') + sep;
+  this.tmpDir = path.join(this.rootDir, 'tmp') + sep;
+  this.cacheDir = path.join(this.tmpDir, 'cache');
 
   this.config = {};
   this.configManager = null;
@@ -54,20 +52,19 @@ function Crowi(rootdir) {
   this.port = this.env.PORT || 3000;
 
   this.events = {
-    user: new (require(self.eventsDir + 'user'))(this),
-    page: new (require(self.eventsDir + 'page'))(this),
-    search: new (require(self.eventsDir + 'search'))(this),
-    bookmark: new (require(self.eventsDir + 'bookmark'))(this),
+    user: new (require(`${self.eventsDir}user`))(this),
+    page: new (require(`${self.eventsDir}page`))(this),
+    search: new (require(`${self.eventsDir}search`))(this),
+    bookmark: new (require(`${self.eventsDir}bookmark`))(this),
   };
-
 }
 
 function getMongoUrl(env) {
-  return env.MONGOLAB_URI || // for B.C.
-    env.MONGODB_URI || // MONGOLAB changes their env name
-    env.MONGOHQ_URL ||
-    env.MONGO_URI ||
-    ((process.env.NODE_ENV === 'test') ? 'mongodb://localhost/growi_test' : 'mongodb://localhost/growi');
+  return env.MONGOLAB_URI // for B.C.
+    || env.MONGODB_URI // MONGOLAB changes their env name
+    || env.MONGOHQ_URL
+    || env.MONGO_URI
+    || ((process.env.NODE_ENV === 'test') ? 'mongodb://localhost/growi_test' : 'mongodb://localhost/growi');
 }
 
 Crowi.prototype.init = async function() {
@@ -126,7 +123,7 @@ Crowi.prototype.model = function(name, model) {
 // getter/setter of event instance
 Crowi.prototype.event = function(name, event) {
   if (event) {
-    return this.events[name] = event;
+    this.events[name] = event;
   }
 
   return this.events[name];
@@ -142,15 +139,15 @@ Crowi.prototype.setupDatabase = function() {
 };
 
 Crowi.prototype.setupSessionConfig = function() {
-  const self = this
-    , session  = require('express-session')
-    , sessionAge = (1000*3600*24*30)
-    , redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URI || this.env.REDIS_URL || null
-    , mongoUrl = getMongoUrl(this.env)
-    ;
+  const self = this;
+  const session = require('express-session');
+  const sessionAge = (1000 * 3600 * 24 * 30);
+  const redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URI || this.env.REDIS_URL || null;
+
+  const mongoUrl = getMongoUrl(this.env);
   let sessionConfig;
 
-  return new Promise(function(resolve, reject) {
+  return new Promise(((resolve, reject) => {
     sessionConfig = {
       rolling: true,
       secret: self.env.SECRET_TOKEN || 'this is default session secret',
@@ -178,13 +175,13 @@ Crowi.prototype.setupSessionConfig = function() {
 
     self.sessionConfig = sessionConfig;
     resolve();
-  });
+  }));
 };
 
 Crowi.prototype.setupAppConfig = function() {
   return new Promise((resolve, reject) => {
     this.model('Config', require('../models/config')(this));
-    var Config = this.model('Config');
+    const Config = this.model('Config');
     Config.loadAllConfig((err, doc) => {
       if (err) {
         return reject();
@@ -200,19 +197,17 @@ Crowi.prototype.setupAppConfig = function() {
 Crowi.prototype.setupConfigManager = async function() {
   const ConfigManager = require('../service/config-manager');
   this.configManager = new ConfigManager(this.model('Config'));
-  return await this.configManager.loadConfigs();
+  return this.configManager.loadConfigs();
 };
 
 Crowi.prototype.setupModels = function() {
-  var self = this
-    ;
-
-  return new Promise(function(resolve, reject) {
-    Object.keys(models).forEach(function(key) {
+  const self = this;
+  return new Promise(((resolve, reject) => {
+    Object.keys(models).forEach((key) => {
       self.model(key, models[key](self));
     });
     resolve();
-  });
+  }));
 };
 
 Crowi.prototype.getIo = function() {
@@ -220,11 +215,10 @@ Crowi.prototype.getIo = function() {
 };
 
 Crowi.prototype.scanRuntimeVersions = function() {
-  var self = this
-    , check = require('check-node-version')
-    ;
+  const self = this;
 
 
+  const check = require('check-node-version');
   return new Promise((resolve, reject) => {
     check((err, result) => {
       if (err) {
@@ -289,13 +283,11 @@ Crowi.prototype.setupPassport = function() {
 };
 
 Crowi.prototype.setupSearcher = function() {
-  var self = this;
-  var searcherUri = this.env.ELASTICSEARCH_URI
+  const self = this;
+  const searcherUri = this.env.ELASTICSEARCH_URI
     || this.env.BONSAI_URL
-    || null
-    ;
-
-  return new Promise(function(resolve, reject) {
+    || null;
+  return new Promise(((resolve, reject) => {
     if (searcherUri) {
       try {
         self.searcher = new (require(path.join(self.libDir, 'util', 'search')))(self, searcherUri);
@@ -306,23 +298,23 @@ Crowi.prototype.setupSearcher = function() {
       }
     }
     resolve();
-  });
+  }));
 };
 
 Crowi.prototype.setupMailer = function() {
-  var self = this;
-  return new Promise(function(resolve, reject) {
+  const self = this;
+  return new Promise(((resolve, reject) => {
     self.mailer = require('../util/mailer')(self);
     resolve();
-  });
+  }));
 };
 
 Crowi.prototype.setupSlack = function() {
-  var self = this;
-  var config = this.getConfig();
-  var Config = this.model('Config');
+  const self = this;
+  const config = this.getConfig();
+  const Config = this.model('Config');
 
-  return new Promise(function(resolve, reject) {
+  return new Promise(((resolve, reject) => {
     if (!Config.hasSlackConfig(config)) {
       self.slack = {};
     }
@@ -331,11 +323,11 @@ Crowi.prototype.setupSlack = function() {
     }
 
     resolve();
-  });
+  }));
 };
 
 Crowi.prototype.setupCsrf = function() {
-  var Tokens = require('csrf');
+  const Tokens = require('csrf');
   this.tokens = new Tokens();
 
   return Promise.resolve();
@@ -368,7 +360,7 @@ Crowi.prototype.start = async function() {
 
   // setup WebSocket
   const io = require('socket.io')(serverListening);
-  io.sockets.on('connection', function(socket) {
+  io.sockets.on('connection', (socket) => {
   });
   this.io = io;
 
@@ -379,27 +371,27 @@ Crowi.prototype.start = async function() {
 };
 
 Crowi.prototype.buildServer = function() {
-  var express = require('express')()
-    , env = this.node_env
-    ;
+  const express = require('express')();
+  const env = this.node_env;
+
   require('./express-init')(this, express);
 
   // import plugins
-  var Config = this.model('Config');
-  var isEnabledPlugins = Config.isEnabledPlugins(this.config);
+  const Config = this.model('Config');
+  const isEnabledPlugins = Config.isEnabledPlugins(this.config);
   if (isEnabledPlugins) {
     debug('Plugins are enabled');
-    var PluginService = require('../plugins/plugin.service');
-    var pluginService = new PluginService(this, express);
+    const PluginService = require('../plugins/plugin.service');
+    const pluginService = new PluginService(this, express);
     pluginService.autoDetectAndLoadPlugins();
 
-    if (env == 'development') {
+    if (env === 'development') {
       this.crowiDev.loadPlugins(express);
     }
   }
 
   // use bunyan
-  if (env == 'production') {
+  if (env === 'production') {
     const expressBunyanLogger = require('express-bunyan-logger');
     const logger = require('@alias/logger')('express');
     express.use(expressBunyanLogger({

+ 3 - 3
src/server/events/page.js

@@ -1,6 +1,6 @@
-var debug = require('debug')('growi:events:page');
-var util = require('util');
-var events = require('events');
+const debug = require('debug')('growi:events:page');
+const util = require('util');
+const events = require('events');
 
 function PageEvent(crowi) {
   this.crowi = crowi;

+ 3 - 5
src/server/form/admin/app.js

@@ -1,12 +1,10 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[app:title]').trim(),
   field('settingForm[app:confidential]'),
   field('settingForm[app:globalLang]'),
-  field('settingForm[app:fileUpload]').trim().toBooleanStrict()
+  field('settingForm[app:fileUpload]').trim().toBooleanStrict(),
 );
-

+ 3 - 5
src/server/form/admin/aws.js

@@ -1,12 +1,10 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[aws:region]', 'リージョン').trim().is(/^[a-z]+-[a-z]+-\d+$/, 'リージョンには、AWSリージョン名を入力してください。 例: ap-northeast-1'),
   field('settingForm[aws:bucket]', 'バケット名').trim(),
   field('settingForm[aws:accessKeyId]', 'Access Key Id').trim().is(/^[\da-zA-Z]+$/),
-  field('settingForm[aws:secretAccessKey]', 'Secret Access Key').trim()
+  field('settingForm[aws:secretAccessKey]', 'Secret Access Key').trim(),
 );
-

+ 3 - 5
src/server/form/admin/custombehavior.js

@@ -1,9 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[customize:behavior]')
+  field('settingForm[customize:behavior]'),
 );

+ 3 - 5
src/server/form/admin/customcss.js

@@ -1,9 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[customize:css]')
+  field('settingForm[customize:css]'),
 );

+ 3 - 5
src/server/form/admin/customfeatures.js

@@ -1,12 +1,10 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[customize:isEnabledTimeline]').trim().toBooleanStrict(),
   field('settingForm[customize:isSavedStatesOfTabChanges]').trim().toBooleanStrict(),
   field('settingForm[customize:isEnabledAttachTitleHeader]').trim().toBooleanStrict(),
-  field('settingForm[customize:showRecentCreatedNumber]').trim().toInt()
+  field('settingForm[customize:showRecentCreatedNumber]').trim().toInt(),
 );
-

+ 3 - 5
src/server/form/admin/customheader.js

@@ -1,9 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[customize:header]')
+  field('settingForm[customize:header]'),
 );

+ 3 - 5
src/server/form/admin/customhighlightJsStyle.js

@@ -1,10 +1,8 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[customize:highlightJsStyle]'),
-  field('settingForm[customize:highlightJsStyleBorder]').trim().toBooleanStrict()
+  field('settingForm[customize:highlightJsStyleBorder]').trim().toBooleanStrict(),
 );

+ 3 - 5
src/server/form/admin/customlayout.js

@@ -1,10 +1,8 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[customize:layout]'),
-  field('settingForm[customize:theme]')
+  field('settingForm[customize:theme]'),
 );

+ 3 - 5
src/server/form/admin/customscript.js

@@ -1,9 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[customize:script]')
+  field('settingForm[customize:script]'),
 );

+ 3 - 5
src/server/form/admin/customtheme.js

@@ -1,9 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[customize:theme]')
+  field('settingForm[customize:theme]'),
 );

+ 3 - 5
src/server/form/admin/customtitle.js

@@ -1,9 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[customize:title]')
+  field('settingForm[customize:title]'),
 );

+ 2 - 4
src/server/form/admin/importerEsa.js

@@ -1,10 +1,8 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[importer:esa:access_token]').required(),
   field('settingForm[importer:esa:team_name]').required(),
 );
-

+ 2 - 4
src/server/form/admin/importerQiita.js

@@ -1,10 +1,8 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[importer:qiita:access_token]').required(),
   field('settingForm[importer:qiita:team_name]').required(),
 );
-

+ 3 - 6
src/server/form/admin/mail.js

@@ -1,14 +1,11 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[mail:from]', 'メールFrom').trim(),
   field('settingForm[mail:smtpHost]', 'SMTPホスト').trim(),
   field('settingForm[mail:smtpPort]', 'SMTPポート').trim().toInt(),
   field('settingForm[mail:smtpUser]', 'SMTPユーザー').trim(),
-  field('settingForm[mail:smtpPassword]', 'SMTPパスワード').trim()
+  field('settingForm[mail:smtpPassword]', 'SMTPパスワード').trim(),
 );
-
-

+ 3 - 5
src/server/form/admin/markdown.js

@@ -1,10 +1,8 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('markdownSetting[markdown:isEnabledLinebreaks]').trim().toBooleanStrict(),
-  field('markdownSetting[markdown:isEnabledLinebreaksInComments]').trim().toBooleanStrict()
+  field('markdownSetting[markdown:isEnabledLinebreaksInComments]').trim().toBooleanStrict(),
 );
-

+ 3 - 4
src/server/form/admin/markdownPresentation.js

@@ -1,9 +1,8 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('markdownSetting[markdown:presentation:pageBreakSeparator]').trim().toInt(),
-  field('markdownSetting[markdown:presentation:pageBreakCustomSeparator]').trim()
+  field('markdownSetting[markdown:presentation:pageBreakCustomSeparator]').trim(),
 );

+ 3 - 4
src/server/form/admin/markdownXss.js

@@ -1,11 +1,10 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('markdownSetting[markdown:xss:isEnabledPrevention]').trim().toBooleanStrict(),
   field('markdownSetting[markdown:xss:option]').trim().toInt(),
   field('markdownSetting[markdown:xss:tagWhiteList]').trim(),
-  field('markdownSetting[markdown:xss:attrWhiteList]').trim()
+  field('markdownSetting[markdown:xss:attrWhiteList]').trim(),
 );

+ 1 - 3
src/server/form/admin/notificationGlobal.js

@@ -1,6 +1,5 @@
-'use strict';
-
 const form = require('express-form');
+
 const field = form.field;
 
 module.exports = form(
@@ -16,4 +15,3 @@ module.exports = form(
   field('notificationGlobal[triggerEvent:pageLike]').trim(),
   field('notificationGlobal[triggerEvent:comment]').trim(),
 );
-

+ 3 - 5
src/server/form/admin/plugin.js

@@ -1,9 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[plugin:isEnabledPlugins]').trim().toBooleanStrict()
+  field('settingForm[plugin:isEnabledPlugins]').trim().toBooleanStrict(),
 );
-

+ 1 - 2
src/server/form/admin/securityGeneral.js

@@ -1,6 +1,5 @@
-'use strict';
+const form = require('express-form');
 
-const form = require('express-form')
 const field = form.field;
 const stringToArray = require('../../util/formUtil').stringToArrayFilter;
 const normalizeCRLF = require('../../util/formUtil').normalizeCRLFFilter;

+ 4 - 6
src/server/form/admin/securityGoogle.js

@@ -1,10 +1,8 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[google:clientId]').trim().is(/^[\d\.a-z\-\.]+$/),
-  field('settingForm[google:clientSecret]').trim().is(/^[\da-zA-Z\-_]+$/)
+  field('settingForm[google:clientId]').trim().is(/^[\da-z\-.]+$/),
+  field('settingForm[google:clientSecret]').trim().is(/^[\da-zA-Z\-_]+$/),
 );
-

+ 3 - 5
src/server/form/admin/securityMechanism.js

@@ -1,9 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[security:isEnabledPassport]').trim().toBooleanStrict()
+  field('settingForm[security:isEnabledPassport]').trim().toBooleanStrict(),
 );
-

+ 2 - 4
src/server/form/admin/securityPassportGitHub.js

@@ -1,8 +1,6 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[security:passport-github:isEnabled]').trim().toBooleanStrict().required(),

+ 2 - 4
src/server/form/admin/securityPassportGoogle.js

@@ -1,8 +1,6 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[security:passport-google:isEnabled]').trim().toBooleanStrict().required(),

+ 7 - 9
src/server/form/admin/securityPassportLdap.js

@@ -1,18 +1,16 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[security:passport-ldap:isEnabled]').trim().toBooleanStrict().required(),
   field('settingForm[security:passport-ldap:serverUrl]').trim()
-      // https://regex101.com/r/E0UL6D/1
-      .is(/^ldaps?:\/\/([^\/\s]+)\/([^\/\s]+)$/, 'Server URL is invalid. <small><a href="https://regex101.com/r/E0UL6D/1">&gt;&gt; Regex</a></small>'),
+  // https://regex101.com/r/E0UL6D/1
+    .is(/^ldaps?:\/\/([^/\s]+)\/([^/\s]+)$/, 'Server URL is invalid. <small><a href="https://regex101.com/r/E0UL6D/1">&gt;&gt; Regex</a></small>'),
   field('settingForm[security:passport-ldap:isUserBind]').trim().toBooleanStrict(),
   field('settingForm[security:passport-ldap:bindDN]').trim()
-      // https://regex101.com/r/jK8lpO/1
-      .is(/^(,?[^,=\s]+=[^,=\s]+){1,}$|^[^@\s]+@[^@\s]+$/, 'Bind DN is invalid. <small><a href="https://regex101.com/r/jK8lpO/3">&gt;&gt; Regex</a></small>'),
+  // https://regex101.com/r/jK8lpO/1
+    .is(/^(,?[^,=\s]+=[^,=\s]+){1,}$|^[^@\s]+@[^@\s]+$/, 'Bind DN is invalid. <small><a href="https://regex101.com/r/jK8lpO/3">&gt;&gt; Regex</a></small>'),
   field('settingForm[security:passport-ldap:bindDNPassword]'),
   field('settingForm[security:passport-ldap:searchFilter]'),
   field('settingForm[security:passport-ldap:attrMapUsername]'),
@@ -21,5 +19,5 @@ module.exports = form(
   field('settingForm[security:passport-ldap:isSameUsernameTreatedAsIdenticalUser]').trim().toBooleanStrict(),
   field('settingForm[security:passport-ldap:groupSearchBase]'),
   field('settingForm[security:passport-ldap:groupSearchFilter]'),
-  field('settingForm[security:passport-ldap:groupDnProperty]')
+  field('settingForm[security:passport-ldap:groupDnProperty]'),
 );

+ 1 - 2
src/server/form/admin/securityPassportSaml.js

@@ -1,6 +1,5 @@
-'use strict';
-
 const form = require('express-form');
+
 const field = form.field;
 
 module.exports = form(

+ 2 - 4
src/server/form/admin/securityPassportTwitter.js

@@ -1,8 +1,6 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field
-  ;
+const field = form.field;
 
 module.exports = form(
   field('settingForm[security:passport-twitter:isEnabled]').trim().toBooleanStrict().required(),

+ 3 - 4
src/server/form/admin/siteUrl.js

@@ -1,8 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
-  field('settingForm[app:siteUrl]').trim().isUrl()
+  field('settingForm[app:siteUrl]').trim().isUrl(),
 );

+ 3 - 5
src/server/form/admin/slackIwhSetting.js

@@ -1,10 +1,8 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
   field('slackIwhSetting[slack:incomingWebhookUrl]', 'Webhook URL'),
-  field('slackIwhSetting[slack:isIncomingWebhookPrioritized]', 'Prioritize Incoming Webhook than Slack App ').trim().toBooleanStrict()
+  field('slackIwhSetting[slack:isIncomingWebhookPrioritized]', 'Prioritize Incoming Webhook than Slack App ').trim().toBooleanStrict(),
 );
-

+ 3 - 4
src/server/form/admin/slackSetting.js

@@ -1,8 +1,7 @@
-'use strict';
+const form = require('express-form');
 
-var form = require('express-form')
-  , field = form.field;
+const field = form.field;
 
 module.exports = form(
-  field('slackSetting[slack:token]', 'token')
+  field('slackSetting[slack:token]', 'token'),
 );

Some files were not shown because too many files changed in this diff