Browse Source

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

yusuketk 7 years ago
parent
commit
a951ca15c0
71 changed files with 755 additions and 621 deletions
  1. 11 30
      .eslintrc.js
  2. 2 1
      .stylelintrc.json
  3. 1 2
      .vscode/settings.json
  4. 1 1
      package.json
  5. 125 87
      src/client/js/app.js
  6. 0 1
      src/client/js/components/Common/UserDate.js
  7. 174 120
      src/client/js/components/PageEditor/CodeMirrorEditor.js
  8. 17 17
      src/client/js/components/PageEditor/EmojiAutoCompleteHelper.js
  9. 1 1
      src/client/js/components/PageEditor/MarkdownListUtil.js
  10. 3 6
      src/client/js/components/PageEditor/MarkdownTableInterceptor.js
  11. 13 13
      src/client/js/components/PageEditor/MarkdownTableUtil.js
  12. 68 42
      src/client/js/components/PageEditor/OptionsSelector.js
  13. 3 1
      src/client/js/components/PageEditor/PasteHelper.js
  14. 3 6
      src/client/js/components/PageEditor/PreventMarkdownListInterceptor.js
  15. 8 9
      src/client/js/components/PageEditor/Preview.js
  16. 38 36
      src/client/js/components/PageEditor/ScrollSyncHelper.js
  17. 10 8
      src/client/js/components/PageEditor/SimpleCheatsheet.js
  18. 23 18
      src/client/js/components/PageEditor/TextAreaEditor.js
  19. 8 5
      src/client/js/components/PageHistory/RevisionDiff.js
  20. 3 3
      src/client/js/components/PageList/ListView.js
  21. 3 4
      src/client/js/components/PageList/Page.js
  22. 2 3
      src/client/js/components/PageList/PageListMeta.js
  23. 4 6
      src/client/js/components/PageList/PagePath.js
  24. 4 8
      src/client/js/components/SearchPage/DeletePageListModal.js
  25. 23 19
      src/client/js/components/SearchPage/SearchPageForm.js
  26. 88 70
      src/client/js/components/SearchPage/SearchResult.js
  27. 3 4
      src/client/js/components/SearchPage/SearchResultList.js
  28. 6 7
      src/client/js/components/User/User.js
  29. 9 9
      src/client/js/components/User/UserPicture.js
  30. 20 19
      src/client/js/hackmd-agent.js
  31. 1 1
      src/client/js/hackmd-styles.js
  32. 2 2
      src/client/js/i18n.js
  33. 1 1
      src/client/js/installer.js
  34. 1 1
      src/client/js/plugin.js
  35. 2 0
      src/client/styles/scss/_attachments.scss
  36. 1 0
      src/client/styles/scss/_editor-attachment.scss
  37. 1 1
      src/client/styles/scss/_editor-overlay.scss
  38. 1 0
      src/client/styles/scss/_hljs.scss
  39. 1 0
      src/client/styles/scss/_layout.scss
  40. 1 0
      src/client/styles/scss/_layout_crowi_sidebar.scss
  41. 1 0
      src/client/styles/scss/_layout_kibela.scss
  42. 1 0
      src/client/styles/scss/_on-edit.scss
  43. 1 0
      src/client/styles/scss/_page.scss
  44. 2 0
      src/client/styles/scss/_page_list.scss
  45. 3 0
      src/client/styles/scss/_search.scss
  46. 1 0
      src/client/styles/scss/style-app.scss
  47. 2 0
      src/client/styles/scss/theme/_override-agileadmin.scss
  48. 2 0
      src/lib/models/cdn-resource.js
  49. 2 0
      src/lib/service/cdn-resources-downloader.js
  50. 2 0
      src/lib/service/cdn-resources-service.js
  51. 7 5
      src/lib/service/interceptor-manager.js
  52. 2 0
      src/lib/service/xss/index.js
  53. 2 0
      src/lib/service/xss/xssOption.js
  54. 2 0
      src/server/crowi/dev.js
  55. 2 0
      src/server/models/GlobalNotificationSetting/index.js
  56. 4 0
      src/server/models/external-account.js
  57. 2 0
      src/server/models/page-group-relation.js
  58. 2 0
      src/server/models/page.js
  59. 2 0
      src/server/models/user-group-relation.js
  60. 2 0
      src/server/models/user-group.js
  61. 2 0
      src/server/models/user.js
  62. 2 0
      src/server/plugins/plugin-utils-v2.js
  63. 2 0
      src/server/plugins/plugin-utils.js
  64. 2 0
      src/server/plugins/plugin.service.js
  65. 2 0
      src/server/service/config-loader.js
  66. 2 0
      src/server/service/config-manager.js
  67. 2 0
      src/server/service/file-uploader/index.js
  68. 2 0
      src/server/service/global-notification.js
  69. 2 0
      src/server/service/passport.js
  70. 2 0
      src/server/service/rest-qiita-API.js
  71. 5 54
      wercker.yml

+ 11 - 30
.eslintrc.js

@@ -71,7 +71,7 @@ module.exports = {
     'no-underscore-dangle': 'off',
     'no-underscore-dangle': 'off',
     'no-useless-return': 'off',
     'no-useless-return': 'off',
     'prefer-destructuring': 'off',
     'prefer-destructuring': 'off',
-    indent: [
+    'indent': [
       'error',
       'error',
       2,
       2,
       {
       {
@@ -82,20 +82,6 @@ module.exports = {
         FunctionExpression: { body: 1, parameters: 2 },
         FunctionExpression: { body: 1, parameters: 2 },
       },
       },
     ],
     ],
-    // "key-spacing": [
-    //   "error", {
-    //     "beforeColon": false,
-    //     "afterColon": true,
-    //     "mode": "minimum"
-    //   }
-    // ],
-    // "keyword-spacing": [
-    //   "error", {}
-    // ],
-    // "linebreak-style": [
-    //   "error",
-    //   "unix"
-    // ],
     'no-unused-vars': [
     'no-unused-vars': [
       'error',
       'error',
       { args: 'none' },
       { args: 'none' },
@@ -105,6 +91,15 @@ module.exports = {
       { classes: 'always' },
       { classes: 'always' },
     ],
     ],
     'radix': 'off',
     'radix': 'off',
+    'semi': [
+      'error',
+      'always',
+      { omitLastInOneLineBlock: true },
+    ],
+    'space-before-function-paren': [
+      'error',
+      'never',
+    ],
     'react/destructuring-assignment': 'off',
     'react/destructuring-assignment': 'off',
     'react/forbid-prop-types': 'off',
     'react/forbid-prop-types': 'off',
     'react/jsx-filename-extension': [
     'react/jsx-filename-extension': [
@@ -118,22 +113,8 @@ module.exports = {
     'react/require-default-props': 'off',
     'react/require-default-props': 'off',
     'react/self-closing-comp': 'off',
     'react/self-closing-comp': 'off',
     'react/sort-comp': 'off',
     'react/sort-comp': 'off',
-    // "react/jsx-uses-vars": 1,
-    // "react/no-string-refs": "off",
     'jsx-a11y/img-redundant-alt': 'off',
     'jsx-a11y/img-redundant-alt': 'off',
-    semi: [
-      'error',
-      'always',
-      { omitLastInOneLineBlock: true },
-    ],
-    // "space-before-blocks": [
-    //   "error",
-    //   "always"
-    // ],
-    'space-before-function-paren': [
-      'error',
-      'never',
-    ],
+    // eslint-plugin-import rules
     'import/no-extraneous-dependencies': 'off',
     'import/no-extraneous-dependencies': 'off',
     'import/no-dynamic-require': 'off',
     'import/no-dynamic-require': 'off',
     'import/no-unresolved': [2, { ignore: ['^@'] }], // ignore @alias/..., @commons/..., ...
     'import/no-unresolved': [2, { ignore: ['^@'] }], // ignore @alias/..., @commons/..., ...

+ 2 - 1
.stylelintrc.json

@@ -10,7 +10,8 @@
     "indentation": 2,
     "indentation": 2,
     "string-quotes": "single",
     "string-quotes": "single",
     "rule-empty-line-before": [ "always-multi-line", {
     "rule-empty-line-before": [ "always-multi-line", {
-      "ignore": ["after-comment", "stylelint-commands"]
+      "except": ["after-single-line-comment", "first-nested"],
+      "ignore": ["after-comment", "inside-block"]
     } ],
     } ],
     "selector-combinator-space-before": "always",
     "selector-combinator-space-before": "always",
     "selector-combinator-space-after": "always"
     "selector-combinator-space-after": "always"

+ 1 - 2
.vscode/settings.json

@@ -7,13 +7,12 @@
   },
   },
 
 
   // for vscode-eslint
   // for vscode-eslint
-  "prettier.eslintIntegration": true,
   "eslint.autoFixOnSave": true,
   "eslint.autoFixOnSave": true,
   "[javascript]": {
   "[javascript]": {
     "editor.formatOnSave": false
     "editor.formatOnSave": false
   },
   },
 
 
-  // for vecode-prettier + prettier-vscode
+  // for prettier-vecode + prettier-stylelint
   "prettier.stylelintIntegration": true,
   "prettier.stylelintIntegration": true,
   "[scss]": {
   "[scss]": {
     "editor.formatOnSave": true
     "editor.formatOnSave": true

+ 1 - 1
package.json

@@ -32,7 +32,7 @@
     "clean:report": "rimraf -- report",
     "clean:report": "rimraf -- report",
     "clean": "npm-run-all -p clean:*",
     "clean": "npm-run-all -p clean:*",
     "heroku-postbuild": "sh bin/heroku/install-plugins.sh && npm run build:prod",
     "heroku-postbuild": "sh bin/heroku/install-plugins.sh && npm run build:prod",
-    "lint:js:fix": "eslint src/migrations --fix",
+    "lint:js:fix": "eslint . --fix",
     "lint:js": "eslint .",
     "lint:js": "eslint .",
     "lint:styles:fix": "prettier-stylelint --quiet --write src/client/styles/scss/**/*.scss",
     "lint:styles:fix": "prettier-stylelint --quiet --write src/client/styles/scss/**/*.scss",
     "lint:styles": "stylelint src/client/styles/scss/**/*.scss",
     "lint:styles": "stylelint src/client/styles/scss/**/*.scss",

+ 125 - 87
src/client/js/app.js

@@ -1,45 +1,50 @@
+/* eslint-disable max-len */
+
 import React from 'react';
 import React from 'react';
 import ReactDOM from 'react-dom';
 import ReactDOM from 'react-dom';
 import { I18nextProvider } from 'react-i18next';
 import { I18nextProvider } from 'react-i18next';
 import * as toastr from 'toastr';
 import * as toastr from 'toastr';
 
 
-import i18nFactory from './i18n';
-
 import loggerFactory from '@alias/logger';
 import loggerFactory from '@alias/logger';
 import Xss from '@commons/service/xss';
 import Xss from '@commons/service/xss';
+import * as entities from 'entities';
+import i18nFactory from './i18n';
+
 
 
 import Crowi from './util/Crowi';
 import Crowi from './util/Crowi';
 // import CrowiRenderer from './util/CrowiRenderer';
 // import CrowiRenderer from './util/CrowiRenderer';
 import GrowiRenderer from './util/GrowiRenderer';
 import GrowiRenderer from './util/GrowiRenderer';
 
 
-import HeaderSearchBox  from './components/HeaderSearchBox';
-import SearchPage       from './components/SearchPage';
-import PageEditor       from './components/PageEditor';
-import OptionsSelector  from './components/PageEditor/OptionsSelector';
+import HeaderSearchBox from './components/HeaderSearchBox';
+import SearchPage from './components/SearchPage';
+import PageEditor from './components/PageEditor';
+// eslint-disable-next-line import/no-duplicates
+import OptionsSelector from './components/PageEditor/OptionsSelector';
+// eslint-disable-next-line import/no-duplicates
 import { EditorOptions, PreviewOptions } from './components/PageEditor/OptionsSelector';
 import { EditorOptions, PreviewOptions } from './components/PageEditor/OptionsSelector';
 import SavePageControls from './components/SavePageControls';
 import SavePageControls from './components/SavePageControls';
 import PageEditorByHackmd from './components/PageEditorByHackmd';
 import PageEditorByHackmd from './components/PageEditorByHackmd';
-import Page             from './components/Page';
-import PageHistory      from './components/PageHistory';
-import PageComments     from './components/PageComments';
+import Page from './components/Page';
+import PageHistory from './components/PageHistory';
+import PageComments from './components/PageComments';
 import CommentForm from './components/PageComment/CommentForm';
 import CommentForm from './components/PageComment/CommentForm';
-import PageAttachment   from './components/PageAttachment';
-import PageStatusAlert  from './components/PageStatusAlert';
-import RevisionPath     from './components/Page/RevisionPath';
-import PageTagForm      from './components/PageTagForm';
-import RevisionUrl      from './components/Page/RevisionUrl';
-import BookmarkButton   from './components/BookmarkButton';
-import LikeButton       from './components/LikeButton';
+import PageAttachment from './components/PageAttachment';
+import PageStatusAlert from './components/PageStatusAlert';
+import RevisionPath from './components/Page/RevisionPath';
+// TODO GC-1430 activate
+// import PageTagForm from './components/PageTagForm';
+import RevisionUrl from './components/Page/RevisionUrl';
+import BookmarkButton from './components/BookmarkButton';
+import LikeButton from './components/LikeButton';
 import PagePathAutoComplete from './components/PagePathAutoComplete';
 import PagePathAutoComplete from './components/PagePathAutoComplete';
 import RecentCreated from './components/RecentCreated/RecentCreated';
 import RecentCreated from './components/RecentCreated/RecentCreated';
-import UserPictureList  from './components/Common/UserPictureList';
+import UserPictureList from './components/Common/UserPictureList';
 
 
-import CustomCssEditor  from './components/Admin/CustomCssEditor';
+import CustomCssEditor from './components/Admin/CustomCssEditor';
 import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import AdminRebuildSearch from './components/Admin/AdminRebuildSearch';
 import AdminRebuildSearch from './components/Admin/AdminRebuildSearch';
 
 
-import * as entities from 'entities';
 
 
 const logger = loggerFactory('growi:app');
 const logger = loggerFactory('growi:app');
 
 
@@ -65,8 +70,9 @@ let pagePath;
 let pageContent = '';
 let pageContent = '';
 let markdown = '';
 let markdown = '';
 let slackChannels;
 let slackChannels;
-let currentPageTags = '';
-let newPageTags = '';
+// TODO GC-1430 activate
+// const currentPageTags = '';
+// let newPageTags = '';
 if (mainContent !== null) {
 if (mainContent !== null) {
   pageId = mainContent.getAttribute('data-page-id') || null;
   pageId = mainContent.getAttribute('data-page-id') || null;
   pageRevisionId = mainContent.getAttribute('data-page-revision-id');
   pageRevisionId = mainContent.getAttribute('data-page-revision-id');
@@ -100,8 +106,8 @@ const socketClientId = crowi.getSocketClientId();
 
 
 const crowiRenderer = new GrowiRenderer(crowi, null, {
 const crowiRenderer = new GrowiRenderer(crowi, null, {
   mode: 'page',
   mode: 'page',
-  isAutoSetup: false,                                     // manually setup because plugins may configure it
-  renderToc: crowi.getCrowiForJquery().renderTocContent,  // function for rendering Table Of Contents
+  isAutoSetup: false, // manually setup because plugins may configure it
+  renderToc: crowi.getCrowiForJquery().renderTocContent, // function for rendering Table Of Contents
 });
 });
 window.crowiRenderer = crowiRenderer;
 window.crowiRenderer = crowiRenderer;
 
 
@@ -116,14 +122,15 @@ if (isEnabledPlugins) {
  * get new tags from page tag form
  * get new tags from page tag form
  * @param {String} tags new tags [TODO] String -> Array
  * @param {String} tags new tags [TODO] String -> Array
  */
  */
-const getNewPageTags = function(tags) {
-  newPageTags = tags;
-};
+// TODO GC-1430 activate
+// const getNewPageTags = function(tags) {
+//   newPageTags = tags;
+// };
 
 
 /**
 /**
  * component store
  * component store
  */
  */
-let componentInstances = {};
+const componentInstances = {};
 
 
 /**
 /**
  * save success handler when reloading is not needed
  * save success handler when reloading is not needed
@@ -198,7 +205,8 @@ const saveWithShortcut = function(markdown) {
   // get options
   // get options
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
   options.socketClientId = socketClientId;
   options.socketClientId = socketClientId;
-  options.pageTags = newPageTags;
+  // TODO GC-1430 activate
+  // options.pageTags = newPageTags;
 
 
   if (editorMode === 'hackmd') {
   if (editorMode === 'hackmd') {
     // set option to sync
     // set option to sync
@@ -207,7 +215,7 @@ const saveWithShortcut = function(markdown) {
     revisionId = componentInstances.pageEditorByHackmd.getRevisionIdHackmdSynced();
     revisionId = componentInstances.pageEditorByHackmd.getRevisionIdHackmdSynced();
   }
   }
 
 
-  let promise = undefined;
+  let promise;
   if (pageId == null) {
   if (pageId == null) {
     promise = crowi.createPage(pagePath, markdown, options);
     promise = crowi.createPage(pagePath, markdown, options);
   }
   }
@@ -222,7 +230,7 @@ const saveWithShortcut = function(markdown) {
 
 
 const saveWithSubmitButtonSuccessHandler = function() {
 const saveWithSubmitButtonSuccessHandler = function() {
   crowi.clearDraft(pagePath);
   crowi.clearDraft(pagePath);
-  location.href = pagePath;
+  window.location.href = pagePath;
 };
 };
 
 
 const saveWithSubmitButton = function(submitOpts) {
 const saveWithSubmitButton = function(submitOpts) {
@@ -236,12 +244,13 @@ const saveWithSubmitButton = function(submitOpts) {
   // get options
   // get options
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
   options.socketClientId = socketClientId;
   options.socketClientId = socketClientId;
-  options.pageTags = newPageTags;
+  // TODO GC-1430 activate
+  // options.pageTags = newPageTags;
 
 
   // set 'submitOpts.overwriteScopesOfDescendants' to options
   // set 'submitOpts.overwriteScopesOfDescendants' to options
   options.overwriteScopesOfDescendants = submitOpts ? !!submitOpts.overwriteScopesOfDescendants : false;
   options.overwriteScopesOfDescendants = submitOpts ? !!submitOpts.overwriteScopesOfDescendants : false;
 
 
-  let promise = undefined;
+  let promise;
   if (editorMode === 'hackmd') {
   if (editorMode === 'hackmd') {
     // get markdown
     // get markdown
     promise = componentInstances.pageEditorByHackmd.getMarkdown();
     promise = componentInstances.pageEditorByHackmd.getMarkdown();
@@ -256,12 +265,12 @@ const saveWithSubmitButton = function(submitOpts) {
   }
   }
   // create or update
   // create or update
   if (pageId == null) {
   if (pageId == null) {
-    promise = promise.then(markdown => {
+    promise = promise.then((markdown) => {
       return crowi.createPage(pagePath, markdown, options);
       return crowi.createPage(pagePath, markdown, options);
     });
     });
   }
   }
   else {
   else {
-    promise = promise.then(markdown => {
+    promise = promise.then((markdown) => {
       return crowi.updatePage(pageId, revisionId, markdown, options);
       return crowi.updatePage(pageId, revisionId, markdown, options);
     });
     });
   }
   }
@@ -290,11 +299,11 @@ const componentMappings = {
   'search-sidebar': <HeaderSearchBox crowi={crowi} />,
   'search-sidebar': <HeaderSearchBox crowi={crowi} />,
   'search-page': <I18nextProvider i18n={i18n}><SearchPage crowi={crowi} crowiRenderer={crowiRenderer} /></I18nextProvider>,
   'search-page': <I18nextProvider i18n={i18n}><SearchPage crowi={crowi} crowiRenderer={crowiRenderer} /></I18nextProvider>,
 
 
-  //'revision-history': <PageHistory pageId={pageId} />,
+  // 'revision-history': <PageHistory pageId={pageId} />,
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
   'bookmark-button-lg': <BookmarkButton pageId={pageId} crowi={crowi} size="lg" />,
   'bookmark-button-lg': <BookmarkButton pageId={pageId} crowi={crowi} size="lg" />,
 
 
-  'create-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} addTrailingSlash={true} />,
+  'create-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} addTrailingSlash />,
   'rename-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
   'rename-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
   'duplicate-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
   'duplicate-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
 
 
@@ -305,7 +314,7 @@ if (pageId) {
   componentMappings['page-attachment'] = <PageAttachment pageId={pageId} markdown={markdown} crowi={crowi} />;
   componentMappings['page-attachment'] = <PageAttachment pageId={pageId} markdown={markdown} crowi={crowi} />;
 }
 }
 if (pagePath) {
 if (pagePath) {
-  componentMappings['page'] = <Page crowi={crowi} crowiRenderer={crowiRenderer} markdown={markdown} pagePath={pagePath} showHeadEditButton={true} onSaveWithShortcut={saveWithShortcut} />;
+  componentMappings.page = <Page crowi={crowi} crowiRenderer={crowiRenderer} markdown={markdown} pagePath={pagePath} showHeadEditButton onSaveWithShortcut={saveWithShortcut} />;
   componentMappings['revision-path'] = <RevisionPath pagePath={pagePath} crowi={crowi} />;
   componentMappings['revision-path'] = <RevisionPath pagePath={pagePath} crowi={crowi} />;
   // componentMappings['page-tag'] = <PageTagForm pageTags={currentPageTags} submitTags={getNewPageTags} />; [pagetag]
   // componentMappings['page-tag'] = <PageTagForm pageTags={currentPageTags} submitTags={getNewPageTags} />; [pagetag]
   componentMappings['revision-url'] = <RevisionUrl pageId={pageId} pagePath={pagePath} />;
   componentMappings['revision-url'] = <RevisionUrl pageId={pageId} pagePath={pagePath} />;
@@ -319,8 +328,8 @@ Object.keys(componentMappings).forEach((key) => {
 });
 });
 
 
 // set page if exists
 // set page if exists
-if (componentInstances['page'] != null) {
-  crowi.setPage(componentInstances['page']);
+if (componentInstances.page != null) {
+  crowi.setPage(componentInstances.page);
 }
 }
 
 
 // render LikeButton
 // render LikeButton
@@ -329,7 +338,7 @@ if (likeButtonElem) {
   const isLiked = likeButtonElem.dataset.liked === 'true';
   const isLiked = likeButtonElem.dataset.liked === 'true';
   ReactDOM.render(
   ReactDOM.render(
     <LikeButton crowi={crowi} pageId={pageId} isLiked={isLiked} />,
     <LikeButton crowi={crowi} pageId={pageId} isLiked={isLiked} />,
-    likeButtonElem
+    likeButtonElem,
   );
   );
 }
 }
 
 
@@ -340,7 +349,7 @@ if (seenUserListElem) {
   const userIds = userIdsStr.split(',');
   const userIds = userIdsStr.split(',');
   ReactDOM.render(
   ReactDOM.render(
     <UserPictureList crowi={crowi} userIds={userIds} />,
     <UserPictureList crowi={crowi} userIds={userIds} />,
-    seenUserListElem
+    seenUserListElem,
   );
   );
 }
 }
 // render UserPictureList for liker-list
 // render UserPictureList for liker-list
@@ -350,7 +359,7 @@ if (likerListElem) {
   const userIds = userIdsStr.split(',');
   const userIds = userIdsStr.split(',');
   ReactDOM.render(
   ReactDOM.render(
     <UserPictureList crowi={crowi} userIds={userIds} />,
     <UserPictureList crowi={crowi} userIds={userIds} />,
-    likerListElem
+    likerListElem,
   );
   );
 }
 }
 
 
@@ -363,16 +372,23 @@ if (savePageControlsElem) {
   const grantGroupName = savePageControlsElem.dataset.grantGroupName;
   const grantGroupName = savePageControlsElem.dataset.grantGroupName;
   ReactDOM.render(
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
     <I18nextProvider i18n={i18n}>
-      <SavePageControls crowi={crowi} onSubmit={saveWithSubmitButton}
-          ref={(elem) => {
+      <SavePageControls
+        crowi={crowi}
+        onSubmit={saveWithSubmitButton}
+        ref={(elem) => {
             if (savePageControls == null) {
             if (savePageControls == null) {
               savePageControls = elem.getWrappedInstance();
               savePageControls = elem.getWrappedInstance();
             }
             }
           }}
           }}
-          pageId={pageId} pagePath={pagePath} slackChannels={slackChannels}
-          grant={grant} grantGroupId={grantGroupId} grantGroupName={grantGroupName} />
+        pageId={pageId}
+        pagePath={pagePath}
+        slackChannels={slackChannels}
+        grant={grant}
+        grantGroupId={grantGroupId}
+        grantGroupName={grantGroupName}
+      />
     </I18nextProvider>,
     </I18nextProvider>,
-    savePageControlsElem
+    savePageControlsElem,
   );
   );
   componentInstances.savePageControls = savePageControls;
   componentInstances.savePageControls = savePageControls;
 }
 }
@@ -380,13 +396,13 @@ if (savePageControlsElem) {
 const recentCreatedControlsElem = document.getElementById('user-created-list');
 const recentCreatedControlsElem = document.getElementById('user-created-list');
 if (recentCreatedControlsElem) {
 if (recentCreatedControlsElem) {
   let limit = crowi.getConfig().recentCreatedLimit;
   let limit = crowi.getConfig().recentCreatedLimit;
-  if (null == limit) {
+  if (limit == null) {
     limit = 10;
     limit = 10;
   }
   }
   ReactDOM.render(
   ReactDOM.render(
-    <RecentCreated  crowi={crowi} pageId={pageId} limit={limit} >
+    <RecentCreated crowi={crowi} pageId={pageId} limit={limit}>
 
 
-    </RecentCreated>, document.getElementById('user-created-list')
+    </RecentCreated>, document.getElementById('user-created-list'),
   );
   );
 }
 }
 
 
@@ -398,12 +414,17 @@ let pageEditorByHackmd = null;
 const pageEditorWithHackmdElem = document.getElementById('page-editor-with-hackmd');
 const pageEditorWithHackmdElem = document.getElementById('page-editor-with-hackmd');
 if (pageEditorWithHackmdElem) {
 if (pageEditorWithHackmdElem) {
   pageEditorByHackmd = ReactDOM.render(
   pageEditorByHackmd = ReactDOM.render(
-    <PageEditorByHackmd crowi={crowi}
-        pageId={pageId} revisionId={pageRevisionId}
-        pageIdOnHackmd={pageIdOnHackmd} revisionIdHackmdSynced={pageRevisionIdHackmdSynced} hasDraftOnHackmd={hasDraftOnHackmd}
-        markdown={markdown}
-        onSaveWithShortcut={saveWithShortcut} />,
-    pageEditorWithHackmdElem
+    <PageEditorByHackmd
+      crowi={crowi}
+      pageId={pageId}
+      revisionId={pageRevisionId}
+      pageIdOnHackmd={pageIdOnHackmd}
+      revisionIdHackmdSynced={pageRevisionIdHackmdSynced}
+      hasDraftOnHackmd={hasDraftOnHackmd}
+      markdown={markdown}
+      onSaveWithShortcut={saveWithShortcut}
+    />,
+    pageEditorWithHackmdElem,
   );
   );
   componentInstances.pageEditorByHackmd = pageEditorByHackmd;
   componentInstances.pageEditorByHackmd = pageEditorByHackmd;
 }
 }
@@ -419,12 +440,18 @@ const previewOptions = new PreviewOptions(crowi.previewOptions);
 const pageEditorElem = document.getElementById('page-editor');
 const pageEditorElem = document.getElementById('page-editor');
 if (pageEditorElem) {
 if (pageEditorElem) {
   pageEditor = ReactDOM.render(
   pageEditor = ReactDOM.render(
-    <PageEditor crowi={crowi} crowiRenderer={crowiRenderer}
-        pageId={pageId} revisionId={pageRevisionId} pagePath={pagePath}
-        markdown={markdown}
-        editorOptions={editorOptions} previewOptions={previewOptions}
-        onSaveWithShortcut={saveWithShortcut} />,
-    pageEditorElem
+    <PageEditor
+      crowi={crowi}
+      crowiRenderer={crowiRenderer}
+      pageId={pageId}
+      revisionId={pageRevisionId}
+      pagePath={pagePath}
+      markdown={markdown}
+      editorOptions={editorOptions}
+      previewOptions={previewOptions}
+      onSaveWithShortcut={saveWithShortcut}
+    />,
+    pageEditorElem,
   );
   );
   componentInstances.pageEditor = pageEditor;
   componentInstances.pageEditor = pageEditor;
   // set refs for setCaretLine/forceToFocus when tab is changed
   // set refs for setCaretLine/forceToFocus when tab is changed
@@ -441,15 +468,18 @@ if (writeCommentElem) {
     }
     }
   };
   };
   ReactDOM.render(
   ReactDOM.render(
-    <CommentForm crowi={crowi}
+    <CommentForm
+      crowi={crowi}
       crowiOriginRenderer={crowiRenderer}
       crowiOriginRenderer={crowiRenderer}
       pageId={pageId}
       pageId={pageId}
       pagePath={pagePath}
       pagePath={pagePath}
       revisionId={pageRevisionId}
       revisionId={pageRevisionId}
       onPostComplete={postCompleteHandler}
       onPostComplete={postCompleteHandler}
       editorOptions={editorOptions}
       editorOptions={editorOptions}
-      slackChannels = {slackChannels}/>,
-    writeCommentElem);
+      slackChannels={slackChannels}
+    />,
+    writeCommentElem,
+  );
 }
 }
 
 
 // render OptionsSelector
 // render OptionsSelector
@@ -457,8 +487,10 @@ const pageEditorOptionsSelectorElem = document.getElementById('page-editor-optio
 if (pageEditorOptionsSelectorElem) {
 if (pageEditorOptionsSelectorElem) {
   ReactDOM.render(
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
     <I18nextProvider i18n={i18n}>
-      <OptionsSelector crowi={crowi}
-        editorOptions={editorOptions} previewOptions={previewOptions}
+      <OptionsSelector
+        crowi={crowi}
+        editorOptions={editorOptions}
+        previewOptions={previewOptions}
         onChange={(newEditorOptions, newPreviewOptions) => { // set onChange event handler
         onChange={(newEditorOptions, newPreviewOptions) => { // set onChange event handler
           // set options
           // set options
           pageEditor.setEditorOptions(newEditorOptions);
           pageEditor.setEditorOptions(newEditorOptions);
@@ -466,9 +498,10 @@ if (pageEditorOptionsSelectorElem) {
           // save
           // save
           crowi.saveEditorOptions(newEditorOptions);
           crowi.saveEditorOptions(newEditorOptions);
           crowi.savePreviewOptions(newPreviewOptions);
           crowi.savePreviewOptions(newPreviewOptions);
-        }} />
+        }}
+      />
     </I18nextProvider>,
     </I18nextProvider>,
-    pageEditorOptionsSelectorElem
+    pageEditorOptionsSelectorElem,
   );
   );
 }
 }
 
 
@@ -478,15 +511,19 @@ const pageStatusAlertElem = document.getElementById('page-status-alert');
 if (pageStatusAlertElem) {
 if (pageStatusAlertElem) {
   ReactDOM.render(
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
     <I18nextProvider i18n={i18n}>
-      <PageStatusAlert crowi={crowi}
-          ref={(elem) => {
+      <PageStatusAlert
+        crowi={crowi}
+        ref={(elem) => {
             if (pageStatusAlert == null) {
             if (pageStatusAlert == null) {
               pageStatusAlert = elem.getWrappedInstance();
               pageStatusAlert = elem.getWrappedInstance();
             }
             }
           }}
           }}
-          revisionId={pageRevisionId} revisionIdHackmdSynced={pageRevisionIdHackmdSynced} hasDraftOnHackmd={hasDraftOnHackmd} />
+        revisionId={pageRevisionId}
+        revisionIdHackmdSynced={pageRevisionIdHackmdSynced}
+        hasDraftOnHackmd={hasDraftOnHackmd}
+      />
     </I18nextProvider>,
     </I18nextProvider>,
-    pageStatusAlertElem
+    pageStatusAlertElem,
   );
   );
   componentInstances.pageStatusAlert = pageStatusAlert;
   componentInstances.pageStatusAlert = pageStatusAlert;
 }
 }
@@ -499,7 +536,7 @@ if (customCssEditorElem != null) {
 
 
   ReactDOM.render(
   ReactDOM.render(
     <CustomCssEditor inputElem={customCssInputElem} />,
     <CustomCssEditor inputElem={customCssInputElem} />,
-    customCssEditorElem
+    customCssEditorElem,
   );
   );
 }
 }
 const customScriptEditorElem = document.getElementById('custom-script-editor');
 const customScriptEditorElem = document.getElementById('custom-script-editor');
@@ -509,7 +546,7 @@ if (customScriptEditorElem != null) {
 
 
   ReactDOM.render(
   ReactDOM.render(
     <CustomScriptEditor inputElem={customScriptInputElem} />,
     <CustomScriptEditor inputElem={customScriptInputElem} />,
-    customScriptEditorElem
+    customScriptEditorElem,
   );
   );
 }
 }
 const customHeaderEditorElem = document.getElementById('custom-header-editor');
 const customHeaderEditorElem = document.getElementById('custom-header-editor');
@@ -519,14 +556,14 @@ if (customHeaderEditorElem != null) {
 
 
   ReactDOM.render(
   ReactDOM.render(
     <CustomHeaderEditor inputElem={customHeaderInputElem} />,
     <CustomHeaderEditor inputElem={customHeaderInputElem} />,
-    customHeaderEditorElem
+    customHeaderEditorElem,
   );
   );
 }
 }
 const adminRebuildSearchElem = document.getElementById('admin-rebuild-search');
 const adminRebuildSearchElem = document.getElementById('admin-rebuild-search');
 if (adminRebuildSearchElem != null) {
 if (adminRebuildSearchElem != null) {
   ReactDOM.render(
   ReactDOM.render(
     <AdminRebuildSearch crowi={crowi} />,
     <AdminRebuildSearch crowi={crowi} />,
-    adminRebuildSearchElem
+    adminRebuildSearchElem,
   );
   );
 }
 }
 
 
@@ -540,7 +577,7 @@ function updatePageStatusAlert(page, user) {
     pageStatusAlert.setLastUpdateUsername(user.name);
     pageStatusAlert.setLastUpdateUsername(user.name);
   }
   }
 }
 }
-socket.on('page:create', function(data) {
+socket.on('page:create', (data) => {
   // skip if triggered myself
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
     return;
@@ -549,11 +586,11 @@ socket.on('page:create', function(data) {
   logger.debug({ obj: data }, `websocket on 'page:create'`); // eslint-disable-line quotes
   logger.debug({ obj: data }, `websocket on 'page:create'`); // eslint-disable-line quotes
 
 
   // update PageStatusAlert
   // update PageStatusAlert
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     updatePageStatusAlert(data.page, data.user);
     updatePageStatusAlert(data.page, data.user);
   }
   }
 });
 });
-socket.on('page:update', function(data) {
+socket.on('page:update', (data) => {
   // skip if triggered myself
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
     return;
@@ -561,7 +598,7 @@ socket.on('page:update', function(data) {
 
 
   logger.debug({ obj: data }, `websocket on 'page:update'`); // eslint-disable-line quotes
   logger.debug({ obj: data }, `websocket on 'page:update'`); // eslint-disable-line quotes
 
 
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     // update PageStatusAlert
     // update PageStatusAlert
     updatePageStatusAlert(data.page, data.user);
     updatePageStatusAlert(data.page, data.user);
     // update PageEditorByHackmd
     // update PageEditorByHackmd
@@ -573,7 +610,7 @@ socket.on('page:update', function(data) {
     }
     }
   }
   }
 });
 });
-socket.on('page:delete', function(data) {
+socket.on('page:delete', (data) => {
   // skip if triggered myself
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
     return;
@@ -582,11 +619,11 @@ socket.on('page:delete', function(data) {
   logger.debug({ obj: data }, `websocket on 'page:delete'`); // eslint-disable-line quotes
   logger.debug({ obj: data }, `websocket on 'page:delete'`); // eslint-disable-line quotes
 
 
   // update PageStatusAlert
   // update PageStatusAlert
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     updatePageStatusAlert(data.page, data.user);
     updatePageStatusAlert(data.page, data.user);
   }
   }
 });
 });
-socket.on('page:editingWithHackmd', function(data) {
+socket.on('page:editingWithHackmd', (data) => {
   // skip if triggered myself
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
     return;
@@ -594,7 +631,7 @@ socket.on('page:editingWithHackmd', function(data) {
 
 
   logger.debug({ obj: data }, `websocket on 'page:editingWithHackmd'`); // eslint-disable-line quotes
   logger.debug({ obj: data }, `websocket on 'page:editingWithHackmd'`); // eslint-disable-line quotes
 
 
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     // update PageStatusAlert
     // update PageStatusAlert
     const pageStatusAlert = componentInstances.pageStatusAlert;
     const pageStatusAlert = componentInstances.pageStatusAlert;
     if (pageStatusAlert != null) {
     if (pageStatusAlert != null) {
@@ -609,9 +646,10 @@ socket.on('page:editingWithHackmd', function(data) {
 });
 });
 
 
 // うわーもうー (commented by Crowi team -- 2018.03.23 Yuki Takei)
 // うわーもうー (commented by Crowi team -- 2018.03.23 Yuki Takei)
-$('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', function() {
+$('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
   ReactDOM.render(
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
     <I18nextProvider i18n={i18n}>
       <PageHistory pageId={pageId} crowi={crowi} />
       <PageHistory pageId={pageId} crowi={crowi} />
-    </I18nextProvider>, document.getElementById('revision-history'));
+    </I18nextProvider>, document.getElementById('revision-history'),
+  );
 });
 });

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

@@ -29,7 +29,6 @@ UserDate.propTypes = {
 };
 };
 
 
 UserDate.defaultProps = {
 UserDate.defaultProps = {
-  dateTime: 'now',
   format: 'YYYY/MM/DD HH:mm:ss',
   format: 'YYYY/MM/DD HH:mm:ss',
   className: '',
   className: '',
 };
 };

+ 174 - 120
src/client/js/components/PageEditor/CodeMirrorEditor.js

@@ -3,14 +3,25 @@ import PropTypes from 'prop-types';
 
 
 import Modal from 'react-bootstrap/es/Modal';
 import Modal from 'react-bootstrap/es/Modal';
 import Button from 'react-bootstrap/es/Button';
 import Button from 'react-bootstrap/es/Button';
+import urljoin from 'url-join';
+import * as codemirror from 'codemirror';
+
+import { UnControlled as ReactCodeMirror } from 'react-codemirror2';
 
 
 import InterceptorManager from '@commons/service/interceptor-manager';
 import InterceptorManager from '@commons/service/interceptor-manager';
 
 
-import urljoin from 'url-join';
+import AbstractEditor from './AbstractEditor';
+import SimpleCheatsheet from './SimpleCheatsheet';
+import Cheatsheet from './Cheatsheet';
+import pasteHelper from './PasteHelper';
+import EmojiAutoCompleteHelper from './EmojiAutoCompleteHelper';
+import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
+import MarkdownTableInterceptor from './MarkdownTableInterceptor';
+import mtu from './MarkdownTableUtil';
+import HandsontableModal from './HandsontableModal';
+
 const loadScript = require('simple-load-script');
 const loadScript = require('simple-load-script');
 const loadCssSync = require('load-css-file');
 const loadCssSync = require('load-css-file');
-
-import * as codemirror from 'codemirror';
 // set save handler
 // set save handler
 codemirror.commands.save = (instance) => {
 codemirror.commands.save = (instance) => {
   if (instance.codeMirrorEditor != null) {
   if (instance.codeMirrorEditor != null) {
@@ -19,9 +30,6 @@ codemirror.commands.save = (instance) => {
 };
 };
 // set CodeMirror instance as 'CodeMirror' so that CDN addons can reference
 // set CodeMirror instance as 'CodeMirror' so that CDN addons can reference
 window.CodeMirror = require('codemirror');
 window.CodeMirror = require('codemirror');
-
-
-import { UnControlled as ReactCodeMirror } from 'react-codemirror2';
 require('codemirror/addon/display/placeholder');
 require('codemirror/addon/display/placeholder');
 require('codemirror/addon/edit/matchbrackets');
 require('codemirror/addon/edit/matchbrackets');
 require('codemirror/addon/edit/matchtags');
 require('codemirror/addon/edit/matchtags');
@@ -42,19 +50,6 @@ require('codemirror/addon/display/placeholder');
 require('codemirror/mode/gfm/gfm');
 require('codemirror/mode/gfm/gfm');
 require('../../util/codemirror/autorefresh.ext');
 require('../../util/codemirror/autorefresh.ext');
 
 
-import AbstractEditor from './AbstractEditor';
-
-import SimpleCheatsheet from './SimpleCheatsheet';
-import Cheatsheet from './Cheatsheet';
-
-import pasteHelper from './PasteHelper';
-import EmojiAutoCompleteHelper from './EmojiAutoCompleteHelper';
-
-import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
-import MarkdownTableInterceptor from './MarkdownTableInterceptor';
-import mtu from './MarkdownTableUtil';
-import HandsontableModal from './HandsontableModal';
-
 export default class CodeMirrorEditor extends AbstractEditor {
 export default class CodeMirrorEditor extends AbstractEditor {
 
 
   constructor(props) {
   constructor(props) {
@@ -110,14 +105,14 @@ export default class CodeMirrorEditor extends AbstractEditor {
       new MarkdownTableInterceptor(),
       new MarkdownTableInterceptor(),
     ]);
     ]);
 
 
-    this.loadedThemeSet = new Set(['eclipse', 'elegant']);   // themes imported in _vendor.scss
+    this.loadedThemeSet = new Set(['eclipse', 'elegant']); // themes imported in _vendor.scss
     this.loadedKeymapSet = new Set();
     this.loadedKeymapSet = new Set();
   }
   }
 
 
   componentWillMount() {
   componentWillMount() {
     if (this.props.emojiStrategy != null) {
     if (this.props.emojiStrategy != null) {
       this.emojiAutoCompleteHelper = new EmojiAutoCompleteHelper(this.props.emojiStrategy);
       this.emojiAutoCompleteHelper = new EmojiAutoCompleteHelper(this.props.emojiStrategy);
-      this.setState({isEnabledEmojiAutoComplete: true});
+      this.setState({ isEnabledEmojiAutoComplete: true });
     }
     }
   }
   }
 
 
@@ -137,7 +132,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
   }
   }
 
 
   getCodeMirror() {
   getCodeMirror() {
-    return this.refs.cm.editor;
+    return this.cm.editor;
   }
   }
 
 
   /**
   /**
@@ -185,14 +180,14 @@ export default class CodeMirrorEditor extends AbstractEditor {
    * @inheritDoc
    * @inheritDoc
    */
    */
   setCaretLine(line) {
   setCaretLine(line) {
-    if (isNaN(line)) {
+    if (Number.isNaN(line)) {
       return;
       return;
     }
     }
 
 
     const editor = this.getCodeMirror();
     const editor = this.getCodeMirror();
     const linePosition = Math.max(0, line);
     const linePosition = Math.max(0, line);
 
 
-    editor.setCursor({line: linePosition});   // leave 'ch' field as null/undefined to indicate the end of line
+    editor.setCursor({ line: linePosition }); // leave 'ch' field as null/undefined to indicate the end of line
     this.setScrollTopByLine(linePosition);
     this.setScrollTopByLine(linePosition);
   }
   }
 
 
@@ -200,13 +195,13 @@ export default class CodeMirrorEditor extends AbstractEditor {
    * @inheritDoc
    * @inheritDoc
    */
    */
   setScrollTopByLine(line) {
   setScrollTopByLine(line) {
-    if (isNaN(line)) {
+    if (Number.isNaN(line)) {
       return;
       return;
     }
     }
 
 
     const editor = this.getCodeMirror();
     const editor = this.getCodeMirror();
     // get top position of the line
     // get top position of the line
-    const top = editor.charCoords({line, ch: 0}, 'local').top;
+    const top = editor.charCoords({ line, ch: 0 }, 'local').top;
     editor.scrollTo(null, top);
     editor.scrollTo(null, top);
   }
   }
 
 
@@ -331,11 +326,11 @@ export default class CodeMirrorEditor extends AbstractEditor {
    */
    */
   loadKeymapMode(keymapMode) {
   loadKeymapMode(keymapMode) {
     const loadCss = this.loadCss;
     const loadCss = this.loadCss;
-    let scriptList = [];
-    let cssList = [];
+    const scriptList = [];
+    const cssList = [];
 
 
     // add dependencies
     // add dependencies
-    if (this.loadedKeymapSet.size == 0) {
+    if (this.loadedKeymapSet.size === 0) {
       const dialogScriptUrl = this.props.noCdn
       const dialogScriptUrl = this.props.noCdn
         ? urljoin(this.cmNoCdnScriptRoot, 'codemirror-dialog.js')
         ? urljoin(this.cmNoCdnScriptRoot, 'codemirror-dialog.js')
         : urljoin(this.cmCdnRoot, 'addon/dialog/dialog.min.js');
         : urljoin(this.cmCdnRoot, 'addon/dialog/dialog.min.js');
@@ -394,14 +389,14 @@ export default class CodeMirrorEditor extends AbstractEditor {
     }
     }
 
 
     const context = {
     const context = {
-      handlers: [],  // list of handlers which process enter key
+      handlers: [], // list of handlers which process enter key
       editor: this,
       editor: this,
     };
     };
 
 
     const interceptorManager = this.interceptorManager;
     const interceptorManager = this.interceptorManager;
     interceptorManager.process('preHandleEnter', context)
     interceptorManager.process('preHandleEnter', context)
       .then(() => {
       .then(() => {
-        if (context.handlers.length == 0) {
+        if (context.handlers.length === 0) {
           codemirror.commands.newlineAndIndentContinueMarkdownList(this.getCodeMirror());
           codemirror.commands.newlineAndIndentContinueMarkdownList(this.getCodeMirror());
         }
         }
       });
       });
@@ -432,13 +427,14 @@ export default class CodeMirrorEditor extends AbstractEditor {
     if (mtu.isEndOfLine(editor) && mtu.linePartOfTableRE.test(strFromBol)) {
     if (mtu.isEndOfLine(editor) && mtu.linePartOfTableRE.test(strFromBol)) {
       if (!hasCustomClass) {
       if (!hasCustomClass) {
         additionalClassSet.add(autoformatTableClass);
         additionalClassSet.add(autoformatTableClass);
-        this.setState({additionalClassSet});
+        this.setState({ additionalClassSet });
       }
       }
     }
     }
     else {
     else {
+      // eslint-disable-next-line no-lonely-if
       if (hasCustomClass) {
       if (hasCustomClass) {
         additionalClassSet.delete(autoformatTableClass);
         additionalClassSet.delete(autoformatTableClass);
-        this.setState({additionalClassSet});
+        this.setState({ additionalClassSet });
       }
       }
     }
     }
   }
   }
@@ -479,16 +475,13 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
 
   /**
   /**
    * update states which related to cheatsheet
    * update states which related to cheatsheet
-   * @param {boolean} isGfmMode (use state.isGfmMode if null is set)
-   * @param {string} value (get value from codemirror if null is set)
+   * @param {boolean} isGfmModeTmp (use state.isGfmMode if null is set)
+   * @param {string} valueTmp (get value from codemirror if null is set)
    */
    */
-  updateCheatsheetStates(isGfmMode, value) {
-    if (isGfmMode == null) {
-      isGfmMode = this.state.isGfmMode;
-    }
-    if (value == null) {
-      value = this.getCodeMirror().getDoc().getValue();
-    }
+  updateCheatsheetStates(isGfmModeTmp, valueTmp) {
+    const isGfmMode = isGfmModeTmp || this.state.isGfmMode;
+    const value = valueTmp || this.getCodeMirror().getDoc().getValue();
+
     // update isSimpleCheatsheetShown, isCheatsheetModalButtonShown
     // update isSimpleCheatsheetShown, isCheatsheetModalButtonShown
     const isSimpleCheatsheetShown = isGfmMode && value.length === 0;
     const isSimpleCheatsheetShown = isGfmMode && value.length === 0;
     const isCheatsheetModalButtonShown = isGfmMode && value.length > 0;
     const isCheatsheetModalButtonShown = isGfmMode && value.length > 0;
@@ -505,11 +498,13 @@ export default class CodeMirrorEditor extends AbstractEditor {
     };
     };
 
 
     return this.state.isLoadingKeymap
     return this.state.isLoadingKeymap
-      ? <div className="overlay overlay-loading-keymap">
+      ? (
+        <div className="overlay overlay-loading-keymap">
           <span style={style} className="overlay-content">
           <span style={style} className="overlay-content">
             <div className="speeding-wheel d-inline-block"></div> Loading Keymap ...
             <div className="speeding-wheel d-inline-block"></div> Loading Keymap ...
           </span>
           </span>
         </div>
         </div>
+      )
       : '';
       : '';
   }
   }
 
 
@@ -523,27 +518,27 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
 
   renderCheatsheetModalButton() {
   renderCheatsheetModalButton() {
     const showCheatsheetModal = () => {
     const showCheatsheetModal = () => {
-      this.setState({isCheatsheetModalShown: true});
+      this.setState({ isCheatsheetModalShown: true });
     };
     };
 
 
     const hideCheatsheetModal = () => {
     const hideCheatsheetModal = () => {
-      this.setState({isCheatsheetModalShown: false});
+      this.setState({ isCheatsheetModalShown: false });
     };
     };
 
 
     return (
     return (
       <React.Fragment>
       <React.Fragment>
         <Modal className="modal-gfm-cheatsheet" show={this.state.isCheatsheetModalShown} onHide={() => { hideCheatsheetModal() }}>
         <Modal className="modal-gfm-cheatsheet" show={this.state.isCheatsheetModalShown} onHide={() => { hideCheatsheetModal() }}>
           <Modal.Header closeButton>
           <Modal.Header closeButton>
-            <Modal.Title><i className="icon-fw icon-question"/>Markdown Help</Modal.Title>
+            <Modal.Title><i className="icon-fw icon-question" />Markdown Help</Modal.Title>
           </Modal.Header>
           </Modal.Header>
           <Modal.Body className="pt-1">
           <Modal.Body className="pt-1">
             { this.renderCheatsheetModalBody() }
             { this.renderCheatsheetModalBody() }
           </Modal.Body>
           </Modal.Body>
         </Modal>
         </Modal>
 
 
-        <a className="gfm-cheatsheet-modal-link text-muted small" onClick={() => { showCheatsheetModal() }}>
+        <button type="button" className="btn-link gfm-cheatsheet-modal-link text-muted small mr-3" onClick={() => { showCheatsheetModal() }}>
           <i className="icon-question" /> Markdown
           <i className="icon-question" /> Markdown
-        </a>
+        </button>
       </React.Fragment>
       </React.Fragment>
     );
     );
   }
   }
@@ -590,8 +585,8 @@ export default class CodeMirrorEditor extends AbstractEditor {
       for (let i = startLineNum; i <= endLineNum; i++) {
       for (let i = startLineNum; i <= endLineNum; i++) {
         lines.push(prefix + cm.getDoc().getLine(i));
         lines.push(prefix + cm.getDoc().getLine(i));
       }
       }
-      const replacement = lines.join('\n') + '\n';
-      cm.getDoc().replaceRange(replacement, {line: startLineNum, ch: 0}, {line: endLineNum + 1, ch: 0});
+      const replacement = `${lines.join('\n')}\n`;
+      cm.getDoc().replaceRange(replacement, { line: startLineNum, ch: 0 }, { line: endLineNum + 1, ch: 0 });
 
 
       cm.setCursor(endLineNum, cm.getDoc().getLine(endLineNum).length);
       cm.setCursor(endLineNum, cm.getDoc().getLine(endLineNum).length);
       cm.focus();
       cm.focus();
@@ -611,69 +606,123 @@ export default class CodeMirrorEditor extends AbstractEditor {
     if (!line.startsWith('#')) {
     if (!line.startsWith('#')) {
       prefix += ' ';
       prefix += ' ';
     }
     }
-    cm.getDoc().replaceRange(prefix, {line: lineNum, ch: 0}, {line: lineNum, ch: 0});
+    cm.getDoc().replaceRange(prefix, { line: lineNum, ch: 0 }, { line: lineNum, ch: 0 });
     cm.focus();
     cm.focus();
   }
   }
 
 
   showHandsonTableHandler() {
   showHandsonTableHandler() {
-    this.refs.handsontableModal.show(mtu.getMarkdownTable(this.getCodeMirror()));
+    this.handsontableModal.show(mtu.getMarkdownTable(this.getCodeMirror()));
   }
   }
 
 
   getNavbarItems() {
   getNavbarItems() {
     // The following styles will be removed after creating icons for the editor navigation bar.
     // The following styles will be removed after creating icons for the editor navigation bar.
-    const paddingTopBottom54 = {'paddingTop': '6px', 'paddingBottom': '5px'};
-    const paddingBottom6 = {'paddingBottom': '7px'};
-    const fontSize18 = {'fontSize': '18px'};
+    const paddingTopBottom54 = { paddingTop: '6px', paddingBottom: '5px' };
+    const paddingBottom6 = { paddingBottom: '7px' };
+    const fontSize18 = { fontSize: '18px' };
 
 
     return [
     return [
-      <Button key='nav-item-bold' bsSize="small" title={'Bold'}
-              onClick={ this.createReplaceSelectionHandler('**', '**') }>
-        <i className={'fa fa-bold'}></i>
+      <Button
+        key="nav-item-bold"
+        bsSize="small"
+        title="Bold"
+        onClick={this.createReplaceSelectionHandler('**', '**')}
+      >
+        <i className="fa fa-bold"></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-italic' bsSize="small" title={'Italic'}
-              onClick={ this.createReplaceSelectionHandler('*', '*') }>
-        <i className={'fa fa-italic'}></i>
+      <Button
+        key="nav-item-italic"
+        bsSize="small"
+        title="Italic"
+        onClick={this.createReplaceSelectionHandler('*', '*')}
+      >
+        <i className="fa fa-italic"></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-strikethough' bsSize="small" title={'Strikethrough'}
-              onClick={ this.createReplaceSelectionHandler('~~', '~~') }>
-        <i className={'fa fa-strikethrough'}></i>
+      <Button
+        key="nav-item-strikethough"
+        bsSize="small"
+        title="Strikethrough"
+        onClick={this.createReplaceSelectionHandler('~~', '~~')}
+      >
+        <i className="fa fa-strikethrough"></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-header' bsSize="small" title={'Heading'}
-              onClick={ this.makeHeaderHandler }>
-        <i className={'fa fa-header'}></i>
+      <Button
+        key="nav-item-header"
+        bsSize="small"
+        title="Heading"
+        onClick={this.makeHeaderHandler}
+      >
+        <i className="fa fa-header"></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-code' bsSize="small" title={'Inline Code'}
-              onClick={ this.createReplaceSelectionHandler('`', '`') }>
-        <i className={'fa fa-code'}></i>
+      <Button
+        key="nav-item-code"
+        bsSize="small"
+        title="Inline Code"
+        onClick={this.createReplaceSelectionHandler('`', '`')}
+      >
+        <i className="fa fa-code"></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-quote' bsSize="small" title={'Quote'}
-              onClick={ this.createAddPrefixToEachLinesHandler('> ') } style={paddingBottom6}>
-        <i className={'ti-quote-right'}></i>
+      <Button
+        key="nav-item-quote"
+        bsSize="small"
+        title="Quote"
+        onClick={this.createAddPrefixToEachLinesHandler('> ')}
+        style={paddingBottom6}
+      >
+        <i className="ti-quote-right"></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-ul' bsSize="small" title={'List'}
-              onClick={ this.createAddPrefixToEachLinesHandler('- ') } style={paddingTopBottom54}>
-        <i className={'ti-list'} style={fontSize18}></i>
+      <Button
+        key="nav-item-ul"
+        bsSize="small"
+        title="List"
+        onClick={this.createAddPrefixToEachLinesHandler('- ')}
+        style={paddingTopBottom54}
+      >
+        <i className="ti-list" style={fontSize18}></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-ol' bsSize="small" title={'Numbered List'}
-              onClick={ this.createAddPrefixToEachLinesHandler('1. ') } style={paddingTopBottom54}>
-        <i className={'ti-list-ol'} style={fontSize18}></i>
+      <Button
+        key="nav-item-ol"
+        bsSize="small"
+        title="Numbered List"
+        onClick={this.createAddPrefixToEachLinesHandler('1. ')}
+        style={paddingTopBottom54}
+      >
+        <i className="ti-list-ol" style={fontSize18}></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-checkbox' bsSize="small" title={'Check List'}
-              onClick={ this.createAddPrefixToEachLinesHandler('- [ ] ') } style={paddingBottom6}>
-        <i className={'ti-check-box'}></i>
+      <Button
+        key="nav-item-checkbox"
+        bsSize="small"
+        title="Check List"
+        onClick={this.createAddPrefixToEachLinesHandler('- [ ] ')}
+        style={paddingBottom6}
+      >
+        <i className="ti-check-box"></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-link' bsSize="small" title={'Link'}
-              onClick={ this.createReplaceSelectionHandler('[', ']()') } style={paddingBottom6}>
-        <i className={'icon-link'}></i>
+      <Button
+        key="nav-item-link"
+        bsSize="small"
+        title="Link"
+        onClick={this.createReplaceSelectionHandler('[', ']()')}
+        style={paddingBottom6}
+      >
+        <i className="icon-link"></i>
       </Button>,
       </Button>,
-      <Button key='nav-item-image' bsSize="small" title={'Image'}
-              onClick={ this.createReplaceSelectionHandler('![', ']()') } style={paddingBottom6}>
-        <i className={'icon-picture'}></i>
+      <Button
+        key="nav-item-image"
+        bsSize="small"
+        title="Image"
+        onClick={this.createReplaceSelectionHandler('![', ']()')}
+        style={paddingBottom6}
+      >
+        <i className="icon-picture"></i>
+      </Button>,
+      <Button
+        key="nav-item-table"
+        bsSize="small"
+        title="Table"
+        onClick={this.showHandsonTableHandler}
+      >
+        <img src="/images/icons/editor/table.svg" alt="icon-table" width="14" height="14" />
       </Button>,
       </Button>,
-      <Button key='nav-item-table' bsSize="small" title={'Table'}
-              onClick={ this.showHandsonTableHandler }>
-        <img src="/images/icons/editor/table.svg" width="14" height="14" />
-      </Button>
     ];
     ];
   }
   }
 
 
@@ -688,50 +737,51 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
 
     const placeholder = this.state.isGfmMode ? 'Input with Markdown..' : 'Input with Plane Text..';
     const placeholder = this.state.isGfmMode ? 'Input with Markdown..' : 'Input with Plane Text..';
 
 
-    return <React.Fragment>
+    return (
+      <React.Fragment>
 
 
-      <ReactCodeMirror
-        ref="cm"
-        className={additionalClasses}
-        placeholder="search"
-        editorDidMount={(editor) => {
+        <ReactCodeMirror
+          ref={(c) => { this.cm = c }}
+          className={additionalClasses}
+          placeholder="search"
+          editorDidMount={(editor) => {
           // add event handlers
           // add event handlers
           editor.on('paste', this.pasteHandler);
           editor.on('paste', this.pasteHandler);
           editor.on('scrollCursorIntoView', this.scrollCursorIntoViewHandler);
           editor.on('scrollCursorIntoView', this.scrollCursorIntoViewHandler);
         }}
         }}
-        value={this.state.value}
-        options={{
-          mode: mode,
+          value={this.state.value}
+          options={{
+          mode,
           theme: editorOptions.theme,
           theme: editorOptions.theme,
           styleActiveLine: editorOptions.styleActiveLine,
           styleActiveLine: editorOptions.styleActiveLine,
           lineNumbers: this.props.lineNumbers,
           lineNumbers: this.props.lineNumbers,
           tabSize: 4,
           tabSize: 4,
           indentUnit: 4,
           indentUnit: 4,
           lineWrapping: true,
           lineWrapping: true,
-          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
           autoCloseTags: true,
           autoCloseTags: true,
-          placeholder: placeholder,
+          placeholder,
           matchBrackets: true,
           matchBrackets: true,
-          matchTags: {bothTags: true},
+          matchTags: { bothTags: true },
           // folding
           // folding
           foldGutter: this.props.lineNumbers,
           foldGutter: this.props.lineNumbers,
           gutters: this.props.lineNumbers ? ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'] : [],
           gutters: this.props.lineNumbers ? ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'] : [],
           // match-highlighter, matchesonscrollbar, annotatescrollbar options
           // match-highlighter, matchesonscrollbar, annotatescrollbar options
-          highlightSelectionMatches: {annotateScrollbar: true},
+          highlightSelectionMatches: { annotateScrollbar: true },
           // markdown mode options
           // markdown mode options
           highlightFormatting: true,
           highlightFormatting: true,
           // continuelist, indentlist
           // continuelist, indentlist
           extraKeys: {
           extraKeys: {
-            'Enter': this.handleEnterKey,
+            Enter: this.handleEnterKey,
             'Ctrl-Enter': this.handleCtrlEnterKey,
             'Ctrl-Enter': this.handleCtrlEnterKey,
             'Cmd-Enter': this.handleCtrlEnterKey,
             'Cmd-Enter': this.handleCtrlEnterKey,
-            'Tab': 'indentMore',
+            Tab: 'indentMore',
             'Shift-Tab': 'indentLess',
             'Shift-Tab': 'indentLess',
             'Ctrl-Q': (cm) => { cm.foldCode(cm.getCursor()) },
             'Ctrl-Q': (cm) => { cm.foldCode(cm.getCursor()) },
-          }
+          },
         }}
         }}
-        onCursor={this.cursorHandler}
-        onScroll={(editor, data) => {
+          onCursor={this.cursorHandler}
+          onScroll={(editor, data) => {
           if (this.props.onScroll != null) {
           if (this.props.onScroll != null) {
             // add line data
             // add line data
             const line = editor.lineAtHeight(data.top, 'local');
             const line = editor.lineAtHeight(data.top, 'local');
@@ -739,23 +789,27 @@ export default class CodeMirrorEditor extends AbstractEditor {
             this.props.onScroll(data);
             this.props.onScroll(data);
           }
           }
         }}
         }}
-        onChange={this.changeHandler}
-        onDragEnter={(editor, event) => {
+          onChange={this.changeHandler}
+          onDragEnter={(editor, event) => {
           if (this.props.onDragEnter != null) {
           if (this.props.onDragEnter != null) {
             this.props.onDragEnter(event);
             this.props.onDragEnter(event);
           }
           }
         }}
         }}
-      />
+        />
 
 
-      { this.renderLoadingKeymapOverlay() }
+        { this.renderLoadingKeymapOverlay() }
 
 
-      <div className="overlay overlay-gfm-cheatsheet mt-1 p-3 pt-3">
-        { this.state.isSimpleCheatsheetShown && this.renderSimpleCheatsheet() }
-        { this.state.isCheatsheetModalButtonShown && this.renderCheatsheetModalButton() }
-      </div>
+        <div className="overlay overlay-gfm-cheatsheet mt-1 p-3 pt-3">
+          { this.state.isSimpleCheatsheetShown && this.renderSimpleCheatsheet() }
+          { this.state.isCheatsheetModalButtonShown && this.renderCheatsheetModalButton() }
+        </div>
 
 
-      <HandsontableModal ref='handsontableModal' onSave={ table => mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }/>
-    </React.Fragment>;
+        <HandsontableModal
+          ref={(c) => { this.handsontableModal = c }}
+          onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }}
+        />
+      </React.Fragment>
+    );
   }
   }
 
 
 }
 }

+ 17 - 17
src/client/js/components/PageEditor/EmojiAutoCompleteHelper.js

@@ -14,8 +14,7 @@ class EmojiAutoCompleteHelper {
   }
   }
 
 
   initEmojiImageMap() {
   initEmojiImageMap() {
-    for (let unicode in this.emojiStrategy) {
-      const data = this.emojiStrategy[unicode];
+    for (const data of Object.values(this.emojiStrategy)) {
       const shortname = data.shortname;
       const shortname = data.shortname;
       // add image tag
       // add image tag
       this.emojiShortnameImageMap[shortname] = emojione.shortnameToImage(shortname);
       this.emojiShortnameImageMap[shortname] = emojione.shortnameToImage(shortname);
@@ -62,7 +61,7 @@ class EmojiAutoCompleteHelper {
       // closeOnUnfocus: false,  // for debug
       // closeOnUnfocus: false,  // for debug
       hint: () => {
       hint: () => {
         const matched = editor.getDoc().getRange(sc.from(), sc.to());
         const matched = editor.getDoc().getRange(sc.from(), sc.to());
-        const term = matched.replace(':', '');  // remove ':' in the head
+        const term = matched.replace(':', ''); // remove ':' in the head
 
 
         // get a list of shortnames
         // get a list of shortnames
         const shortnames = this.searchEmojiShortnames(term);
         const shortnames = this.searchEmojiShortnames(term);
@@ -87,10 +86,9 @@ class EmojiAutoCompleteHelper {
         text: shortname,
         text: shortname,
         className: 'crowi-emoji-autocomplete',
         className: 'crowi-emoji-autocomplete',
         render: (element) => {
         render: (element) => {
-          element.innerHTML =
-            `<div class="img-container">${this.emojiShortnameImageMap[shortname]}</div>` +
-            `<span class="shortname-container">${shortname}</span>`;
-        }
+          element.innerHTML = `<div class="img-container">${this.emojiShortnameImageMap[shortname]}</div>`
+            + `<span class="shortname-container">${shortname}</span>`;
+        },
       };
       };
     });
     });
   }
   }
@@ -103,16 +101,18 @@ class EmojiAutoCompleteHelper {
   searchEmojiShortnames(term) {
   searchEmojiShortnames(term) {
     const maxLength = 12;
     const maxLength = 12;
 
 
-    let results1 = [], results2 = [], results3 = [], results4 = [];
-    const countLen1 = () => { results1.length };
-    const countLen2 = () => { countLen1() + results2.length };
-    const countLen3 = () => { countLen2() + results3.length };
-    const countLen4 = () => { countLen3() + results4.length };
+    const results1 = [];
+    const results2 = [];
+    const results3 = [];
+    const results4 = [];
+    const countLen1 = () => { return results1.length };
+    const countLen2 = () => { return countLen1() + results2.length };
+    const countLen3 = () => { return countLen2() + results3.length };
+    const countLen4 = () => { return countLen3() + results4.length };
+
     // TODO performance tune
     // TODO performance tune
     // when total length of all results is less than `maxLength`
     // when total length of all results is less than `maxLength`
-    for (let unicode in this.emojiStrategy) {
-      const data = this.emojiStrategy[unicode];
-
+    for (const data of Object.values(this.emojiStrategy)) {
       if (maxLength <= countLen1()) { break }
       if (maxLength <= countLen1()) { break }
       // prefix match to shortname
       // prefix match to shortname
       else if (data.shortname.indexOf(`:${term}`) > -1) {
       else if (data.shortname.indexOf(`:${term}`) > -1) {
@@ -127,13 +127,13 @@ class EmojiAutoCompleteHelper {
       }
       }
       else if (maxLength <= countLen3()) { continue }
       else if (maxLength <= countLen3()) { continue }
       // partial match to elements of aliases
       // partial match to elements of aliases
-      else if ((data.aliases != null) && data.aliases.find(elem => elem.indexOf(term) > -1)) {
+      else if ((data.aliases != null) && data.aliases.find((elem) => { return elem.indexOf(term) > -1 })) {
         results3.push(data.shortname);
         results3.push(data.shortname);
         continue;
         continue;
       }
       }
       else if (maxLength <= countLen4()) { continue }
       else if (maxLength <= countLen4()) { continue }
       // partial match to elements of keywords
       // partial match to elements of keywords
-      else if ((data.keywords != null) && data.keywords.find(elem => elem.indexOf(term) > -1)) {
+      else if ((data.keywords != null) && data.keywords.find((elem) => { return elem.indexOf(term) > -1 })) {
         results4.push(data.shortname);
         results4.push(data.shortname);
       }
       }
     }
     }

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

@@ -88,7 +88,7 @@ class MarkdownListUtil {
     // not listful data
     // not listful data
     else {
     else {
       // append `indentAndMark` at the beginning of all lines (except the first line)
       // append `indentAndMark` at the beginning of all lines (except the first line)
-      const replacedText = text.replace(/(\r\n|\r|\n)/g, '$1' + indentAndMark);
+      const replacedText = text.replace(/(\r\n|\r|\n)/g, `$1${indentAndMark}`);
       // append `indentAndMark` to the first line
       // append `indentAndMark` to the first line
       adjusted = indentAndMark + replacedText;
       adjusted = indentAndMark + replacedText;
     }
     }

+ 3 - 6
src/client/js/components/PageEditor/MarkdownTableInterceptor.js

@@ -8,10 +8,6 @@ import MarkdownTable from '../../models/MarkdownTable';
  */
  */
 export default class MarkdownTableInterceptor extends BasicInterceptor {
 export default class MarkdownTableInterceptor extends BasicInterceptor {
 
 
-  constructor() {
-    super();
-  }
-
   /**
   /**
    * @inheritdoc
    * @inheritdoc
    */
    */
@@ -32,8 +28,8 @@ export default class MarkdownTableInterceptor extends BasicInterceptor {
    * @inheritdoc
    * @inheritdoc
    */
    */
   process(contextName, ...args) {
   process(contextName, ...args) {
-    const context = Object.assign(args[0]);   // clone
-    const editor = context.editor;            // AbstractEditor instance
+    const context = Object.assign(args[0]); // clone
+    const editor = context.editor; // AbstractEditor instance
 
 
     // do nothing if editor is not a CodeMirrorEditor
     // do nothing if editor is not a CodeMirrorEditor
     if (editor == null || editor.getCodeMirror() == null) {
     if (editor == null || editor.getCodeMirror() == null) {
@@ -67,4 +63,5 @@ export default class MarkdownTableInterceptor extends BasicInterceptor {
     // resolve
     // resolve
     return Promise.resolve(context);
     return Promise.resolve(context);
   }
   }
+
 }
 }

+ 13 - 13
src/client/js/components/PageEditor/MarkdownTableUtil.js

@@ -9,7 +9,7 @@ class MarkdownTableUtil {
     // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
     // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
     // https://regex101.com/r/7BN2fR/7
     // https://regex101.com/r/7BN2fR/7
     this.tableAlignmentLineRE = /^[-:|][-:|\s]*$/;
     this.tableAlignmentLineRE = /^[-:|][-:|\s]*$/;
-    this.tableAlignmentLineNegRE = /^[^-:]*$/;  // it is need to check to ignore empty row which is matched above RE
+    this.tableAlignmentLineNegRE = /^[^-:]*$/; // it is need to check to ignore empty row which is matched above RE
     this.linePartOfTableRE = /^\|[^\r\n]*|[^\r\n]*\|$|([^|\r\n]+\|[^|\r\n]*)+/; // own idea
     this.linePartOfTableRE = /^\|[^\r\n]*|[^\r\n]*\|$|([^|\r\n]+\|[^|\r\n]*)+/; // own idea
 
 
     this.getBot = this.getBot.bind(this);
     this.getBot = this.getBot.bind(this);
@@ -29,7 +29,7 @@ class MarkdownTableUtil {
   getBot(editor) {
   getBot(editor) {
     const curPos = editor.getCursor();
     const curPos = editor.getCursor();
     if (!this.isInTable(editor)) {
     if (!this.isInTable(editor)) {
-      return { line: curPos.line, ch: curPos.ch};
+      return { line: curPos.line, ch: curPos.ch };
     }
     }
 
 
     const firstLine = editor.getDoc().firstLine();
     const firstLine = editor.getDoc().firstLine();
@@ -51,7 +51,7 @@ class MarkdownTableUtil {
   getEot(editor) {
   getEot(editor) {
     const curPos = editor.getCursor();
     const curPos = editor.getCursor();
     if (!this.isInTable(editor)) {
     if (!this.isInTable(editor)) {
-      return { line: curPos.line, ch: curPos.ch};
+      return { line: curPos.line, ch: curPos.ch };
     }
     }
 
 
     const lastLine = editor.getDoc().lastLine();
     const lastLine = editor.getDoc().lastLine();
@@ -109,7 +109,7 @@ class MarkdownTableUtil {
    */
    */
   isEndOfLine(editor) {
   isEndOfLine(editor) {
     const curPos = editor.getCursor();
     const curPos = editor.getCursor();
-    return (curPos.ch == editor.getDoc().getLine(curPos.line).length);
+    return (curPos.ch === editor.getDoc().getLine(curPos.line).length);
   }
   }
 
 
   /**
   /**
@@ -127,8 +127,8 @@ class MarkdownTableUtil {
    */
    */
   addRowToMarkdownTable(mdtable) {
   addRowToMarkdownTable(mdtable) {
     const numCol = mdtable.table.length > 0 ? mdtable.table[0].length : 1;
     const numCol = mdtable.table.length > 0 ? mdtable.table[0].length : 1;
-    let newRow = [];
-    (new Array(numCol)).forEach(() => newRow.push('')); // create cols
+    const newRow = [];
+    (new Array(numCol)).forEach(() => { return newRow.push('') }); // create cols
     mdtable.table.push(newRow);
     mdtable.table.push(newRow);
   }
   }
 
 
@@ -137,15 +137,14 @@ class MarkdownTableUtil {
    * (The merged markdown table options are used for the first markdown table.)
    * (The merged markdown table options are used for the first markdown table.)
    * @param {Array} array of markdown table
    * @param {Array} array of markdown table
    */
    */
-  mergeMarkdownTable(mdtable_list) {
-    if (mdtable_list == undefined
-        || !(mdtable_list instanceof Array)) {
+  mergeMarkdownTable(mdtableList) {
+    if (mdtableList == null || !(mdtableList instanceof Array)) {
       return undefined;
       return undefined;
     }
     }
 
 
     let newTable = [];
     let newTable = [];
-    const options = mdtable_list[0].options; // use option of first markdown-table
-    mdtable_list.forEach((mdtable) => {
+    const options = mdtableList[0].options; // use option of first markdown-table
+    mdtableList.forEach((mdtable) => {
       newTable = newTable.concat(mdtable.table);
       newTable = newTable.concat(mdtable.table);
     });
     });
     return (new MarkdownTable(newTable, options));
     return (new MarkdownTable(newTable, options));
@@ -176,15 +175,16 @@ class MarkdownTableUtil {
 
 
     let newMarkdown = '';
     let newMarkdown = '';
     if (markdownBeforeTable.length > 0) {
     if (markdownBeforeTable.length > 0) {
-      newMarkdown += markdownBeforeTable.join('\n') + '\n';
+      newMarkdown += `${markdownBeforeTable.join('\n')}\n`;
     }
     }
     newMarkdown += table;
     newMarkdown += table;
     if (markdownAfterTable.length > 0) {
     if (markdownAfterTable.length > 0) {
-      newMarkdown += '\n' + markdownAfterTable.join('\n');
+      newMarkdown += `\n${markdownAfterTable.join('\n')}`;
     }
     }
 
 
     return newMarkdown;
     return newMarkdown;
   }
   }
+
 }
 }
 
 
 // singleton pattern
 // singleton pattern

+ 68 - 42
src/client/js/components/PageEditor/OptionsSelector.js

@@ -9,6 +9,28 @@ import ControlLabel from 'react-bootstrap/es/ControlLabel';
 import Dropdown from 'react-bootstrap/es/Dropdown';
 import Dropdown from 'react-bootstrap/es/Dropdown';
 import MenuItem from 'react-bootstrap/es/MenuItem';
 import MenuItem from 'react-bootstrap/es/MenuItem';
 
 
+export class EditorOptions {
+
+  constructor(props) {
+    this.theme = 'elegant';
+    this.keymapMode = 'default';
+    this.styleActiveLine = false;
+
+    Object.assign(this, props);
+  }
+
+}
+
+export class PreviewOptions {
+
+  constructor(props) {
+    this.renderMathJaxInRealtime = false;
+
+    Object.assign(this, props);
+  }
+
+}
+
 class OptionsSelector extends React.Component {
 class OptionsSelector extends React.Component {
 
 
   constructor(props) {
   constructor(props) {
@@ -25,7 +47,7 @@ class OptionsSelector extends React.Component {
     };
     };
 
 
     this.availableThemes = [
     this.availableThemes = [
-      'eclipse', 'elegant', 'neo', 'mdn-like', 'material', 'dracula', 'monokai', 'twilight'
+      'eclipse', 'elegant', 'neo', 'mdn-like', 'material', 'dracula', 'monokai', 'twilight',
     ];
     ];
     this.keymapModes = {
     this.keymapModes = {
       default: 'Default',
       default: 'Default',
@@ -52,8 +74,8 @@ class OptionsSelector extends React.Component {
 
 
   onChangeTheme() {
   onChangeTheme() {
     const newValue = this.themeSelectorInputEl.value;
     const newValue = this.themeSelectorInputEl.value;
-    const newOpts = Object.assign(this.state.editorOptions, {theme: newValue});
-    this.setState({editorOptions: newOpts});
+    const newOpts = Object.assign(this.state.editorOptions, { theme: newValue });
+    this.setState({ editorOptions: newOpts });
 
 
     // dispatch event
     // dispatch event
     this.dispatchOnChange();
     this.dispatchOnChange();
@@ -61,8 +83,8 @@ class OptionsSelector extends React.Component {
 
 
   onChangeKeymapMode() {
   onChangeKeymapMode() {
     const newValue = this.keymapModeSelectorInputEl.value;
     const newValue = this.keymapModeSelectorInputEl.value;
-    const newOpts = Object.assign(this.state.editorOptions, {keymapMode: newValue});
-    this.setState({editorOptions: newOpts});
+    const newOpts = Object.assign(this.state.editorOptions, { keymapMode: newValue });
+    this.setState({ editorOptions: newOpts });
 
 
     // dispatch event
     // dispatch event
     this.dispatchOnChange();
     this.dispatchOnChange();
@@ -73,8 +95,8 @@ class OptionsSelector extends React.Component {
     this._cddForceOpen = true;
     this._cddForceOpen = true;
 
 
     const newValue = !this.state.editorOptions.styleActiveLine;
     const newValue = !this.state.editorOptions.styleActiveLine;
-    const newOpts = Object.assign(this.state.editorOptions, {styleActiveLine: newValue});
-    this.setState({editorOptions: newOpts});
+    const newOpts = Object.assign(this.state.editorOptions, { styleActiveLine: newValue });
+    this.setState({ editorOptions: newOpts });
 
 
     // dispatch event
     // dispatch event
     this.dispatchOnChange();
     this.dispatchOnChange();
@@ -85,8 +107,8 @@ class OptionsSelector extends React.Component {
     this._cddForceOpen = true;
     this._cddForceOpen = true;
 
 
     const newValue = !this.state.previewOptions.renderMathJaxInRealtime;
     const newValue = !this.state.previewOptions.renderMathJaxInRealtime;
-    const newOpts = Object.assign(this.state.previewOptions, {renderMathJaxInRealtime: newValue});
-    this.setState({previewOptions: newOpts});
+    const newOpts = Object.assign(this.state.previewOptions, { renderMathJaxInRealtime: newValue });
+    this.setState({ previewOptions: newOpts });
 
 
     // dispatch event
     // dispatch event
     this.dispatchOnChange();
     this.dispatchOnChange();
@@ -122,9 +144,15 @@ class OptionsSelector extends React.Component {
     return (
     return (
       <FormGroup controlId="formControlsSelect" className="my-0">
       <FormGroup controlId="formControlsSelect" className="my-0">
         <ControlLabel>Theme:</ControlLabel>
         <ControlLabel>Theme:</ControlLabel>
-        <FormControl componentClass="select" placeholder="select" bsClass={bsClassName} className="btn-group-sm selectpicker"
-            onChange={this.onChangeTheme}
-            inputRef={ el => this.themeSelectorInputEl=el }>
+        <FormControl
+          componentClass="select"
+          placeholder="select"
+          bsClass={bsClassName}
+          className="btn-group-sm selectpicker"
+          onChange={this.onChangeTheme}
+          // eslint-disable-next-line no-return-assign
+          inputRef={(el) => { return this.themeSelectorInputEl = el }}
+        >
 
 
           {optionElems}
           {optionElems}
 
 
@@ -135,13 +163,14 @@ class OptionsSelector extends React.Component {
 
 
   renderKeymapModeSelector() {
   renderKeymapModeSelector() {
     const optionElems = [];
     const optionElems = [];
-    for (let mode in this.keymapModes) {
+    // eslint-disable-next-line guard-for-in, no-restricted-syntax
+    for (const mode in this.keymapModes) {
       const label = this.keymapModes[mode];
       const label = this.keymapModes[mode];
       const dataContent = (mode === 'default')
       const dataContent = (mode === 'default')
         ? label
         ? label
         : `<img src='/images/icons/${mode}.png' width='16px' class='m-r-5'></img> ${label}`;
         : `<img src='/images/icons/${mode}.png' width='16px' class='m-r-5'></img> ${label}`;
       optionElems.push(
       optionElems.push(
-        <option key={mode} value={mode} data-content={dataContent}>{label}</option>
+        <option key={mode} value={mode} data-content={dataContent}>{label}</option>,
       );
       );
     }
     }
 
 
@@ -150,9 +179,15 @@ class OptionsSelector extends React.Component {
     return (
     return (
       <FormGroup controlId="formControlsSelect" className="my-0">
       <FormGroup controlId="formControlsSelect" className="my-0">
         <ControlLabel>Keymap:</ControlLabel>
         <ControlLabel>Keymap:</ControlLabel>
-        <FormControl componentClass="select" placeholder="select" bsClass={bsClassName} className="btn-group-sm selectpicker"
-            onChange={this.onChangeKeymapMode}
-            inputRef={ el => this.keymapModeSelectorInputEl=el }>
+        <FormControl
+          componentClass="select"
+          placeholder="select"
+          bsClass={bsClassName}
+          className="btn-group-sm selectpicker"
+          onChange={this.onChangeKeymapMode}
+          // eslint-disable-next-line no-return-assign
+          inputRef={(el) => { return this.keymapModeSelectorInputEl = el }}
+        >
 
 
           {optionElems}
           {optionElems}
 
 
@@ -165,8 +200,13 @@ class OptionsSelector extends React.Component {
     return (
     return (
       <FormGroup controlId="formControlsSelect" className="my-0">
       <FormGroup controlId="formControlsSelect" className="my-0">
 
 
-        <Dropdown dropup id="configurationDropdown" className="configuration-dropdown"
-            open={this.state.isCddMenuOpened} onToggle={this.onToggleConfigurationDropdown}>
+        <Dropdown
+          dropup
+          id="configurationDropdown"
+          className="configuration-dropdown"
+          open={this.state.isCddMenuOpened}
+          onToggle={this.onToggleConfigurationDropdown}
+        >
 
 
           <Dropdown.Toggle bsSize="sm">
           <Dropdown.Toggle bsSize="sm">
             <i className="icon-settings"></i>
             <i className="icon-settings"></i>
@@ -219,7 +259,7 @@ class OptionsSelector extends React.Component {
 
 
     return (
     return (
       <MenuItem onClick={this.onClickRenderMathJaxInRealtime}>
       <MenuItem onClick={this.onClickRenderMathJaxInRealtime}>
-        <span className="icon-container"><img src="/images/icons/fx.svg" width="14px"></img></span>
+        <span className="icon-container"><img src="/images/icons/fx.svg" width="14px" alt="fx"></img></span>
         <span className="menuitem-label">MathJax Rendering</span>
         <span className="menuitem-label">MathJax Rendering</span>
         <i className={iconClassName}></i>
         <i className={iconClassName}></i>
       </MenuItem>
       </MenuItem>
@@ -227,34 +267,20 @@ class OptionsSelector extends React.Component {
   }
   }
 
 
   render() {
   render() {
-    return <div className="d-flex flex-row">
-      <span className="m-l-5">{this.renderThemeSelector()}</span>
-      <span className="m-l-5">{this.renderKeymapModeSelector()}</span>
-      <span className="m-l-5">{this.renderConfigurationDropdown()}</span>
-    </div>;
+    return (
+      <div className="d-flex flex-row">
+        <span className="m-l-5">{this.renderThemeSelector()}</span>
+        <span className="m-l-5">{this.renderKeymapModeSelector()}</span>
+        <span className="m-l-5">{this.renderConfigurationDropdown()}</span>
+      </div>
+    );
   }
   }
-}
-
-export class EditorOptions {
-  constructor(props) {
-    this.theme = 'elegant';
-    this.keymapMode = 'default';
-    this.styleActiveLine = false;
 
 
-    Object.assign(this, props);
-  }
 }
 }
 
 
-export class PreviewOptions {
-  constructor(props) {
-    this.renderMathJaxInRealtime = false;
-
-    Object.assign(this, props);
-  }
-}
 
 
 OptionsSelector.propTypes = {
 OptionsSelector.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   crowi: PropTypes.object.isRequired,
   editorOptions: PropTypes.instanceOf(EditorOptions).isRequired,
   editorOptions: PropTypes.instanceOf(EditorOptions).isRequired,
   previewOptions: PropTypes.instanceOf(PreviewOptions).isRequired,
   previewOptions: PropTypes.instanceOf(PreviewOptions).isRequired,

+ 3 - 1
src/client/js/components/PageEditor/PasteHelper.js

@@ -17,7 +17,7 @@ class PasteHelper {
     // get data in clipboard
     // get data in clipboard
     const text = event.clipboardData.getData('text/plain');
     const text = event.clipboardData.getData('text/plain');
 
 
-    if (text.length == 0) {
+    if (text.length === 0) {
       return;
       return;
     }
     }
 
 
@@ -35,6 +35,7 @@ class PasteHelper {
   fileAccepted(file, accept) {
   fileAccepted(file, accept) {
     return file.type === 'application/x-moz-file' || accepts(file, accept);
     return file.type === 'application/x-moz-file' || accepts(file, accept);
   }
   }
+
   /**
   /**
    * transplanted from react-dropzone
    * transplanted from react-dropzone
    * @see https://github.com/react-dropzone/react-dropzone/blob/master/src/utils/index.js
    * @see https://github.com/react-dropzone/react-dropzone/blob/master/src/utils/index.js
@@ -46,6 +47,7 @@ class PasteHelper {
   fileMatchSize(file, maxSize, minSize) {
   fileMatchSize(file, maxSize, minSize) {
     return file.size <= maxSize && file.size >= minSize;
     return file.size <= maxSize && file.size >= minSize;
   }
   }
+
 }
 }
 
 
 // singleton pattern
 // singleton pattern

+ 3 - 6
src/client/js/components/PageEditor/PreventMarkdownListInterceptor.js

@@ -3,10 +3,6 @@ import mlu from './MarkdownListUtil';
 
 
 export default class PreventMarkdownListInterceptor extends BasicInterceptor {
 export default class PreventMarkdownListInterceptor extends BasicInterceptor {
 
 
-  constructor() {
-    super();
-  }
-
   /**
   /**
    * @inheritdoc
    * @inheritdoc
    */
    */
@@ -27,8 +23,8 @@ export default class PreventMarkdownListInterceptor extends BasicInterceptor {
    * @inheritdoc
    * @inheritdoc
    */
    */
   process(contextName, ...args) {
   process(contextName, ...args) {
-    const context = Object.assign(args[0]);   // clone
-    const editor = context.editor;            // AbstractEditor instance
+    const context = Object.assign(args[0]); // clone
+    const editor = context.editor; // AbstractEditor instance
 
 
     // get strings from current position to EOL(end of line) before break the line
     // get strings from current position to EOL(end of line) before break the line
     const strToEol = editor.getStrToEol();
     const strToEol = editor.getStrToEol();
@@ -43,4 +39,5 @@ export default class PreventMarkdownListInterceptor extends BasicInterceptor {
     // resolve
     // resolve
     return Promise.resolve(context);
     return Promise.resolve(context);
   }
   }
+
 }
 }

+ 8 - 9
src/client/js/components/PageEditor/Preview.js

@@ -10,24 +10,22 @@ import { PreviewOptions } from './OptionsSelector';
  */
  */
 export default class Preview extends React.Component {
 export default class Preview extends React.Component {
 
 
-  constructor(props) {
-    super(props);
-  }
-
   render() {
   render() {
     const renderMathJaxInRealtime = this.props.previewOptions.renderMathJaxInRealtime;
     const renderMathJaxInRealtime = this.props.previewOptions.renderMathJaxInRealtime;
 
 
     return (
     return (
-      <div className="page-editor-preview-body"
-          ref={(elm) => {
+      <div
+        className="page-editor-preview-body"
+        ref={(elm) => {
             this.previewElement = elm;
             this.previewElement = elm;
             this.props.inputRef(elm);
             this.props.inputRef(elm);
           }}
           }}
-          onScroll={(event) => {
+        onScroll={(event) => {
             if (this.props.onScroll != null) {
             if (this.props.onScroll != null) {
               this.props.onScroll(event.target.scrollTop);
               this.props.onScroll(event.target.scrollTop);
             }
             }
-          }}>
+          }}
+      >
 
 
         <RevisionBody
         <RevisionBody
           {...this.props}
           {...this.props}
@@ -36,11 +34,12 @@ export default class Preview extends React.Component {
       </div>
       </div>
     );
     );
   }
   }
+
 }
 }
 
 
 Preview.propTypes = {
 Preview.propTypes = {
   html: PropTypes.string,
   html: PropTypes.string,
-  inputRef: PropTypes.func.isRequired,  // for getting div element
+  inputRef: PropTypes.func.isRequired, // for getting div element
   isMathJaxEnabled: PropTypes.bool,
   isMathJaxEnabled: PropTypes.bool,
   renderMathJaxOnInit: PropTypes.bool,
   renderMathJaxOnInit: PropTypes.bool,
   previewOptions: PropTypes.instanceOf(PreviewOptions),
   previewOptions: PropTypes.instanceOf(PreviewOptions),

+ 38 - 36
src/client/js/components/PageEditor/ScrollSyncHelper.js

@@ -5,11 +5,8 @@
 class ScrollSyncHelper {
 class ScrollSyncHelper {
 
 
   /**
   /**
-	 * @typedef {{ element: Element, line: number }} CodeLineElement
-	 */
-
-  constructor() {
-  }
+   * @typedef {{ element: Element, line: number }} CodeLineElement
+   */
 
 
   getCodeLineElements(parentElement) {
   getCodeLineElements(parentElement) {
     /** @type {CodeLineElement[]} */
     /** @type {CodeLineElement[]} */
@@ -17,26 +14,27 @@ class ScrollSyncHelper {
     if (!elements) {
     if (!elements) {
       elements = Array.prototype.map.call(
       elements = Array.prototype.map.call(
         parentElement.getElementsByClassName('code-line'),
         parentElement.getElementsByClassName('code-line'),
-        element => {
+        (element) => {
           const line = +element.getAttribute('data-line');
           const line = +element.getAttribute('data-line');
           return { element, line };
           return { element, line };
-        })
-        .filter(x => !isNaN(x.line));
+        },
+      )
+        .filter((x) => { return !Number.isNaN(x.line) });
     }
     }
     return elements;
     return elements;
   }
   }
 
 
   /**
   /**
-	 * Find the html elements that map to a specific target line in the editor.
-	 *
-	 * If an exact match, returns a single element. If the line is between elements,
-	 * returns the element prior to and the element after the given line.
-	 *
+   * Find the html elements that map to a specific target line in the editor.
+   *
+   * If an exact match, returns a single element. If the line is between elements,
+   * returns the element prior to and the element after the given line.
+   *
    * @param {Element} element
    * @param {Element} element
-	 * @param {number} targetLine
-	 *
-	 * @returns {{ previous: CodeLineElement, next?: CodeLineElement }}
-	 */
+   * @param {number} targetLine
+   *
+   * @returns {{ previous: CodeLineElement, next?: CodeLineElement }}
+   */
   getElementsForSourceLine(element, targetLine) {
   getElementsForSourceLine(element, targetLine) {
     const lines = this.getCodeLineElements(element);
     const lines = this.getCodeLineElements(element);
     let previous = lines[0] || null;
     let previous = lines[0] || null;
@@ -44,7 +42,7 @@ class ScrollSyncHelper {
       if (entry.line === targetLine) {
       if (entry.line === targetLine) {
         return { previous: entry, next: null };
         return { previous: entry, next: null };
       }
       }
-      else if (entry.line > targetLine) {
+      if (entry.line > targetLine) {
         return { previous, next: entry };
         return { previous, next: entry };
       }
       }
       previous = entry;
       previous = entry;
@@ -53,13 +51,13 @@ class ScrollSyncHelper {
   }
   }
 
 
   /**
   /**
-	 * Find the html elements that are at a specific pixel offset on the page.
-	 *
+   * Find the html elements that are at a specific pixel offset on the page.
+   *
    * @param {Element} parentElement
    * @param {Element} parentElement
-	 * @param {number} offset
+   * @param {number} offset
    *
    *
-	 * @returns {{ previous: CodeLineElement, next?: CodeLineElement }}
-	 */
+   * @returns {{ previous: CodeLineElement, next?: CodeLineElement }}
+   */
   getLineElementsAtPageOffset(parentElement, offset) {
   getLineElementsAtPageOffset(parentElement, offset) {
     const lines = this.getCodeLineElements(parentElement);
     const lines = this.getCodeLineElements(parentElement);
 
 
@@ -82,7 +80,7 @@ class ScrollSyncHelper {
     if (hi >= 1 && hiElement.element.getBoundingClientRect().top > position) {
     if (hi >= 1 && hiElement.element.getBoundingClientRect().top > position) {
       const loElement = lines[lo];
       const loElement = lines[lo];
       const bounds = loElement.element.getBoundingClientRect();
       const bounds = loElement.element.getBoundingClientRect();
-      let previous = { element: loElement.element, line: loElement.line };
+      const previous = { element: loElement.element, line: loElement.line };
       if (bounds.height > 0) {
       if (bounds.height > 0) {
         previous.line += (position - bounds.top) / (bounds.height);
         previous.line += (position - bounds.top) / (bounds.height);
       }
       }
@@ -99,12 +97,14 @@ class ScrollSyncHelper {
     const { previous, next } = this.getLineElementsAtPageOffset(parentElement, offset);
     const { previous, next } = this.getLineElementsAtPageOffset(parentElement, offset);
     if (previous) {
     if (previous) {
       if (next) {
       if (next) {
-        const betweenProgress = (offset - parentElement.scrollTop - previous.element.getBoundingClientRect().top) / (next.element.getBoundingClientRect().top - previous.element.getBoundingClientRect().top);
+        const betweenProgress = (
+          offset - parentElement.scrollTop - previous.element.getBoundingClientRect().top)
+          / (next.element.getBoundingClientRect().top - previous.element.getBoundingClientRect().top);
         return previous.line + betweenProgress * (next.line - previous.line);
         return previous.line + betweenProgress * (next.line - previous.line);
       }
       }
-      else {
-        return previous.line;
-      }
+
+      return previous.line;
+
     }
     }
     return null;
     return null;
   }
   }
@@ -123,11 +123,11 @@ class ScrollSyncHelper {
   }
   }
 
 
   /**
   /**
-	 * Attempt to scroll preview element for a source line in the editor.
-	 *
+   * Attempt to scroll preview element for a source line in the editor.
+   *
    * @param {Element} previewElement
    * @param {Element} previewElement
-	 * @param {number} line
-	 */
+   * @param {number} line
+   */
   scrollPreview(previewElement, line) {
   scrollPreview(previewElement, line) {
     const { previous, next } = this.getElementsForSourceLine(previewElement, line);
     const { previous, next } = this.getElementsForSourceLine(previewElement, line);
     if (previous) {
     if (previous) {
@@ -149,12 +149,13 @@ class ScrollSyncHelper {
   }
   }
 
 
   /**
   /**
-	 * Attempt to reveal the element that is overflowing from previewElement.
-	 *
+   * Attempt to reveal the element that is overflowing from previewElement.
+   *
    * @param {Element} previewElement
    * @param {Element} previewElement
-	 * @param {number} line
-	 */
+   * @param {number} line
+   */
   scrollPreviewToRevealOverflowing(previewElement, line) {
   scrollPreviewToRevealOverflowing(previewElement, line) {
+    // eslint-disable-next-line no-unused-vars
     const { previous, next } = this.getElementsForSourceLine(previewElement, line);
     const { previous, next } = this.getElementsForSourceLine(previewElement, line);
     if (previous) {
     if (previous) {
       const parentElementOffset = this.getParentElementOffset(previewElement);
       const parentElementOffset = this.getParentElementOffset(previewElement);
@@ -191,6 +192,7 @@ class ScrollSyncHelper {
     line = Math.floor(line);
     line = Math.floor(line);
     editor.setScrollTopByLine(line);
     editor.setScrollTopByLine(line);
   }
   }
+
 }
 }
 
 
 // singleton pattern
 // singleton pattern

+ 10 - 8
src/client/js/components/PageEditor/SimpleCheatsheet.js

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
 import { translate } from 'react-i18next';
 import { translate } from 'react-i18next';
 
 
 class SimpleCheatsheet extends React.Component {
 class SimpleCheatsheet extends React.Component {
+
   render() {
   render() {
     const { t } = this.props;
     const { t } = this.props;
 
 
@@ -12,8 +13,8 @@ class SimpleCheatsheet extends React.Component {
           <div className="row">
           <div className="row">
             <div className="col-xs-6">
             <div className="col-xs-6">
               <p>
               <p>
-                # {t('sandbox.header_x', {index: '1'})}<br />
-                ## {t('sandbox.header_x', {index: '2'})}
+                # {t('sandbox.header_x', { index: '1' })}<br />
+                ## {t('sandbox.header_x', { index: '2' })}
               </p>
               </p>
               <p><i>*{t('sandbox.italics')}*</i>&nbsp;&nbsp;<b>**{t('sandbox.bold')}**</b></p>
               <p><i>*{t('sandbox.italics')}*</i>&nbsp;&nbsp;<b>**{t('sandbox.bold')}**</b></p>
               <p>
               <p>
@@ -28,11 +29,11 @@ class SimpleCheatsheet extends React.Component {
             </div>
             </div>
             <div className="col-xs-6">
             <div className="col-xs-6">
               <p>
               <p>
-                - {t('sandbox.unordered_list_x', {index: '1'})}<br />
-                &nbsp;&nbsp;&nbsp;- {t('sandbox.unordered_list_x', {index: '1.1'})}<br />
-                - {t('sandbox.unordered_list_x', {index: '2'})}<br />
-                1. {t('sandbox.ordered_list_x', {index: '1'})}<br />
-                1. {t('sandbox.ordered_list_x', {index: '2'})}
+                - {t('sandbox.unordered_list_x', { index: '1' })}<br />
+                &nbsp;&nbsp;&nbsp;- {t('sandbox.unordered_list_x', { index: '1.1' })}<br />
+                - {t('sandbox.unordered_list_x', { index: '2' })}<br />
+                1. {t('sandbox.ordered_list_x', { index: '1' })}<br />
+                1. {t('sandbox.ordered_list_x', { index: '2' })}
               </p>
               </p>
               <hr />
               <hr />
               <p>[ ][ ] {t('sandbox.block_detail')}</p>
               <p>[ ][ ] {t('sandbox.block_detail')}</p>
@@ -42,10 +43,11 @@ class SimpleCheatsheet extends React.Component {
       </div>
       </div>
     );
     );
   }
   }
+
 }
 }
 
 
 SimpleCheatsheet.propTypes = {
 SimpleCheatsheet.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
 };
 };
 
 
 export default translate()(SimpleCheatsheet);
 export default translate()(SimpleCheatsheet);

+ 23 - 18
src/client/js/components/PageEditor/TextAreaEditor.js

@@ -79,18 +79,20 @@ export default class TextAreaEditor extends AbstractEditor {
    * @inheritDoc
    * @inheritDoc
    */
    */
   setCaretLine(line) {
   setCaretLine(line) {
-    if (isNaN(line)) {
+    if (Number.isNaN(line)) {
       return;
       return;
     }
     }
 
 
     // scroll to bottom
     // scroll to bottom
     this.textarea.scrollTop = this.textarea.scrollHeight;
     this.textarea.scrollTop = this.textarea.scrollHeight;
 
 
-    const lines = this.textarea.value.split('\n').slice(0, line+1);
+    const lines = this.textarea.value.split('\n').slice(0, line + 1);
+    /* eslint-disable no-param-reassign, no-return-assign */
     const pos = lines
     const pos = lines
-        .map(lineStr => lineStr.length + 1) // correct length+1 of each lines
-        .reduce((a, x) => a += x, 0)        // sum
-        - 1;                                // -1
+      .map((lineStr) => { return lineStr.length + 1 }) // correct length+1 of each lines
+      .reduce((a, x) => { return a += x }, 0) //          sum
+        - 1; //                                           -1
+    /* eslint-enable no-param-reassign, no-return-assign */
 
 
     this.textarea.setSelectionRange(pos, pos);
     this.textarea.setSelectionRange(pos, pos);
   }
   }
@@ -149,13 +151,13 @@ export default class TextAreaEditor extends AbstractEditor {
 
 
   getBolPos() {
   getBolPos() {
     const currentPos = this.textarea.selectionStart;
     const currentPos = this.textarea.selectionStart;
-    return this.textarea.value.lastIndexOf('\n', currentPos-1) + 1;
+    return this.textarea.value.lastIndexOf('\n', currentPos - 1) + 1;
   }
   }
 
 
   getEolPos() {
   getEolPos() {
     const currentPos = this.textarea.selectionStart;
     const currentPos = this.textarea.selectionStart;
     const pos = this.textarea.value.indexOf('\n', currentPos);
     const pos = this.textarea.value.indexOf('\n', currentPos);
-    if (pos < 0) {  // not found but EOF
+    if (pos < 0) { // not found but EOF
       return this.textarea.value.length;
       return this.textarea.value.length;
     }
     }
     return pos;
     return pos;
@@ -197,7 +199,7 @@ export default class TextAreaEditor extends AbstractEditor {
     }
     }
 
 
     const context = {
     const context = {
-      handlers: [],  // list of handlers which process enter key
+      handlers: [], // list of handlers which process enter key
       editor: this,
       editor: this,
     };
     };
 
 
@@ -205,7 +207,7 @@ export default class TextAreaEditor extends AbstractEditor {
     interceptorManager.process('preHandleEnter', context)
     interceptorManager.process('preHandleEnter', context)
       .then(() => {
       .then(() => {
         event.preventDefault();
         event.preventDefault();
-        if (context.handlers.length == 0) {
+        if (context.handlers.length === 0) {
           mlu.newlineAndIndentContinueMarkdownList(this);
           mlu.newlineAndIndentContinueMarkdownList(this);
         }
         }
       });
       });
@@ -240,21 +242,24 @@ export default class TextAreaEditor extends AbstractEditor {
   }
   }
 
 
   render() {
   render() {
-    return <React.Fragment>
-      <FormControl
-        componentClass="textarea" className="textarea-editor"
-        inputRef={ref => { this.textarea = ref }}
-        defaultValue={this.state.value}
-        onChange={(e) => {
+    return (
+      <React.Fragment>
+        <FormControl
+          componentClass="textarea"
+          className="textarea-editor"
+          inputRef={(ref) => { this.textarea = ref }}
+          defaultValue={this.state.value}
+          onChange={(e) => {
           if (this.props.onChange != null) {
           if (this.props.onChange != null) {
             this.props.onChange(e.target.value);
             this.props.onChange(e.target.value);
           }
           }
-        }} />
-    </React.Fragment>;
+        }}
+        />
+      </React.Fragment>
+    );
   }
   }
 
 
 }
 }
 
 
 TextAreaEditor.propTypes = Object.assign({
 TextAreaEditor.propTypes = Object.assign({
 }, AbstractEditor.propTypes);
 }, AbstractEditor.propTypes);
-

+ 8 - 5
src/client/js/components/PageHistory/RevisionDiff.js

@@ -7,9 +7,9 @@ import { Diff2Html } from 'diff2html';
 export default class RevisionDiff extends React.Component {
 export default class RevisionDiff extends React.Component {
 
 
   render() {
   render() {
-    const currentRevision = this.props.currentRevision,
-      previousRevision = this.props.previousRevision,
-      revisionDiffOpened = this.props.revisionDiffOpened;
+    const currentRevision = this.props.currentRevision;
+    const previousRevision = this.props.previousRevision;
+    const revisionDiffOpened = this.props.revisionDiffOpened;
 
 
 
 
     let diffViewHTML = '';
     let diffViewHTML = '';
@@ -18,6 +18,8 @@ export default class RevisionDiff extends React.Component {
       && revisionDiffOpened) {
       && revisionDiffOpened) {
 
 
       let previousText = previousRevision.body;
       let previousText = previousRevision.body;
+      // comparing ObjectId
+      // eslint-disable-next-line eqeqeq
       if (currentRevision._id == previousRevision._id) {
       if (currentRevision._id == previousRevision._id) {
         previousText = '';
         previousText = '';
       }
       }
@@ -25,15 +27,16 @@ export default class RevisionDiff extends React.Component {
       const patch = createPatch(
       const patch = createPatch(
         currentRevision.path,
         currentRevision.path,
         previousText,
         previousText,
-        currentRevision.body
+        currentRevision.body,
       );
       );
 
 
       diffViewHTML = Diff2Html.getPrettyHtml(patch);
       diffViewHTML = Diff2Html.getPrettyHtml(patch);
     }
     }
 
 
-    const diffView = {__html: diffViewHTML};
+    const diffView = { __html: diffViewHTML };
     return <div className="revision-history-diff" dangerouslySetInnerHTML={diffView} />;
     return <div className="revision-history-diff" dangerouslySetInnerHTML={diffView} />;
   }
   }
+
 }
 }
 
 
 RevisionDiff.propTypes = {
 RevisionDiff.propTypes = {

+ 3 - 3
src/client/js/components/PageList/ListView.js

@@ -7,17 +7,18 @@ export default class ListView extends React.Component {
 
 
   render() {
   render() {
     const listView = this.props.pages.map((page) => {
     const listView = this.props.pages.map((page) => {
-      return <Page page={page} key={'page-list:list-view:' + page._id} />;
+      return <Page page={page} key={`page-list:list-view:${page._id}`} />;
     });
     });
 
 
     return (
     return (
       <div className="page-list">
       <div className="page-list">
         <ul className="page-list-ul page-list-ul-flat">
         <ul className="page-list-ul page-list-ul-flat">
-        {listView}
+          {listView}
         </ul>
         </ul>
       </div>
       </div>
     );
     );
   }
   }
+
 }
 }
 
 
 ListView.propTypes = {
 ListView.propTypes = {
@@ -25,5 +26,4 @@ ListView.propTypes = {
 };
 };
 
 
 ListView.defaultProps = {
 ListView.defaultProps = {
-  pages: [],
 };
 };

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

@@ -15,7 +15,7 @@ export default class Page extends React.Component {
     }
     }
 
 
     const styleFlex = {
     const styleFlex = {
-      flex: 1
+      flex: 1,
     };
     };
 
 
     return (
     return (
@@ -26,20 +26,19 @@ export default class Page extends React.Component {
         </a>
         </a>
         <PageListMeta page={page} />
         <PageListMeta page={page} />
         <div style={styleFlex}></div>
         <div style={styleFlex}></div>
-        {this.props.children}
       </li>
       </li>
     );
     );
   }
   }
+
 }
 }
 
 
 Page.propTypes = {
 Page.propTypes = {
   page: PropTypes.object.isRequired,
   page: PropTypes.object.isRequired,
   linkTo: PropTypes.string,
   linkTo: PropTypes.string,
+  excludePathString: PropTypes.string,
 };
 };
 
 
 Page.defaultProps = {
 Page.defaultProps = {
-  page: {},
   linkTo: '',
   linkTo: '',
   excludePathString: '',
   excludePathString: '',
 };
 };
-

+ 2 - 3
src/client/js/components/PageList/PageListMeta.js

@@ -39,7 +39,7 @@ export default class PageListMeta extends React.Component {
     }
     }
 
 
     let locked;
     let locked;
-    if (page.grant != 1) {
+    if (page.grant !== 1) {
       locked = <span><i className="icon-lock" /></span>;
       locked = <span><i className="icon-lock" /></span>;
     }
     }
 
 
@@ -53,6 +53,7 @@ export default class PageListMeta extends React.Component {
       </span>
       </span>
     );
     );
   }
   }
+
 }
 }
 
 
 PageListMeta.propTypes = {
 PageListMeta.propTypes = {
@@ -60,6 +61,4 @@ PageListMeta.propTypes = {
 };
 };
 
 
 PageListMeta.defaultProps = {
 PageListMeta.defaultProps = {
-  page: {},
 };
 };
-

+ 4 - 6
src/client/js/components/PageList/PagePath.js

@@ -6,7 +6,7 @@ import escapeStringRegexp from 'escape-string-regexp';
 export default class PagePath extends React.Component {
 export default class PagePath extends React.Component {
 
 
   getShortPath(path) {
   getShortPath(path) {
-    let name = path.replace(/(\/)$/, '');
+    const name = path.replace(/(\/)$/, '');
 
 
     // /.../hoge/YYYY/MM/DD 形式のページ
     // /.../hoge/YYYY/MM/DD 形式のページ
     if (name.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) {
     if (name.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) {
@@ -34,7 +34,7 @@ export default class PagePath extends React.Component {
     const shortPath = this.getShortPath(pagePath);
     const shortPath = this.getShortPath(pagePath);
 
 
     const shortPathEscaped = escapeStringRegexp(shortPath);
     const shortPathEscaped = escapeStringRegexp(shortPath);
-    const pathPrefix = pagePath.replace(new RegExp(shortPathEscaped + '(/)?$'), '');
+    const pathPrefix = pagePath.replace(new RegExp(`${shortPathEscaped}(/)?$`), '');
 
 
     let classNames = ['page-path'];
     let classNames = ['page-path'];
     classNames = classNames.concat(this.props.additionalClassNames);
     classNames = classNames.concat(this.props.additionalClassNames);
@@ -42,11 +42,10 @@ export default class PagePath extends React.Component {
     if (isShortPathOnly) {
     if (isShortPathOnly) {
       return <span className={classNames.join(' ')}>{shortPath}</span>;
       return <span className={classNames.join(' ')}>{shortPath}</span>;
     }
     }
-    else {
-      return <span className={classNames.join(' ')}>{pathPrefix}<strong>{shortPath}</strong></span>;
-    }
 
 
+    return <span className={classNames.join(' ')}>{pathPrefix}<strong>{shortPath}</strong></span>;
   }
   }
+
 }
 }
 
 
 PagePath.propTypes = {
 PagePath.propTypes = {
@@ -57,7 +56,6 @@ PagePath.propTypes = {
 };
 };
 
 
 PagePath.defaultProps = {
 PagePath.defaultProps = {
-  page: {},
   additionalClassNames: [],
   additionalClassNames: [],
   excludePathString: '',
   excludePathString: '',
 };
 };

+ 4 - 8
src/client/js/components/SearchPage/DeletePageListModal.js

@@ -12,15 +12,11 @@ export default class DeletePageListModal extends React.Component {
    */
    */
   static get OMIT_BODY_THRES() { return 400 }
   static get OMIT_BODY_THRES() { return 400 }
 
 
-  constructor(props) {
-    super(props);
-  }
-
   componentWillMount() {
   componentWillMount() {
   }
   }
 
 
   render() {
   render() {
-    if (this.props.pages === undefined || this.props.pages.length == 0) {
+    if (this.props.pages == null || this.props.pages.length === 0) {
       return <div></div>;
       return <div></div>;
     }
     }
 
 
@@ -44,7 +40,7 @@ export default class DeletePageListModal extends React.Component {
           <div className="d-flex justify-content-between">
           <div className="d-flex justify-content-between">
             <span className="text-danger">{this.props.errorMessage}</span>
             <span className="text-danger">{this.props.errorMessage}</span>
             <span className="d-flex align-items-center">
             <span className="d-flex align-items-center">
-              <Checkbox className="text-danger" onClick={this.props.toggleDeleteCompletely} inline={true}>Delete completely</Checkbox>
+              <Checkbox className="text-danger" onClick={this.props.toggleDeleteCompletely} inline>Delete completely</Checkbox>
               <span className="m-l-10">
               <span className="m-l-10">
                 <Button onClick={this.props.confirmedToDelete}><i className="icon-trash"></i>Delete</Button>
                 <Button onClick={this.props.confirmedToDelete}><i className="icon-trash"></i>Delete</Button>
               </span>
               </span>
@@ -61,7 +57,7 @@ DeletePageListModal.propTypes = {
   isShown: PropTypes.bool.isRequired,
   isShown: PropTypes.bool.isRequired,
   pages: PropTypes.array,
   pages: PropTypes.array,
   errorMessage: PropTypes.string,
   errorMessage: PropTypes.string,
-  cancel: PropTypes.func.isRequired,                 // for cancel evnet handling
-  confirmedToDelete: PropTypes.func.isRequired,      // for confirmed event handling
+  cancel: PropTypes.func.isRequired, //                 for cancel evnet handling
+  confirmedToDelete: PropTypes.func.isRequired, //      for confirmed event handling
   toggleDeleteCompletely: PropTypes.func.isRequired, // for delete completely check event handling
   toggleDeleteCompletely: PropTypes.func.isRequired, // for delete completely check event handling
 };
 };

+ 23 - 19
src/client/js/components/SearchPage/SearchPageForm.js

@@ -24,35 +24,39 @@ export default class SearchPageForm extends React.Component {
 
 
   search() {
   search() {
     const keyword = this.state.keyword;
     const keyword = this.state.keyword;
-    this.props.onSearchFormChanged({keyword: keyword});
-    this.setState({searchedKeyword: keyword});
+    this.props.onSearchFormChanged({ keyword });
+    this.setState({ searchedKeyword: keyword });
   }
   }
 
 
   onInputChange(input) { // for only submitting with button
   onInputChange(input) { // for only submitting with button
-    this.setState({keyword: input});
+    this.setState({ keyword: input });
   }
   }
 
 
   render() {
   render() {
-    return <FormGroup>
-      <InputGroup>
-        <SearchForm t={this.props.t}
-          crowi={this.props.crowi}
-          onSubmit={this.search}
-          keyword={this.state.searchedKeyword}
-          onInputChange={this.onInputChange}
-        />
-        <InputGroup.Button className="">
-          <Button onClick={this.search}>
-            <i className="icon-magnifier"></i>
-          </Button >
-        </InputGroup.Button>
-      </InputGroup>
-    </FormGroup>;
+    return (
+      <FormGroup>
+        <InputGroup>
+          <SearchForm
+            t={this.props.t}
+            crowi={this.props.crowi}
+            onSubmit={this.search}
+            keyword={this.state.searchedKeyword}
+            onInputChange={this.onInputChange}
+          />
+          <InputGroup.Button className="">
+            <Button onClick={this.search}>
+              <i className="icon-magnifier"></i>
+            </Button>
+          </InputGroup.Button>
+        </InputGroup>
+      </FormGroup>
+    );
   }
   }
+
 }
 }
 
 
 SearchPageForm.propTypes = {
 SearchPageForm.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   crowi: PropTypes.object.isRequired,
   keyword: PropTypes.string,
   keyword: PropTypes.string,
   onSearchFormChanged: PropTypes.func.isRequired,
   onSearchFormChanged: PropTypes.func.isRequired,

+ 88 - 70
src/client/js/components/SearchPage/SearchResult.js

@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/label-has-for */
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import * as toastr from 'toastr';
 import * as toastr from 'toastr';
@@ -8,6 +10,7 @@ import DeletePageListModal from './DeletePageListModal';
 
 
 // Search.SearchResult
 // Search.SearchResult
 export default class SearchResult extends React.Component {
 export default class SearchResult extends React.Component {
+
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
     this.state = {
     this.state = {
@@ -50,8 +53,8 @@ export default class SearchResult extends React.Component {
     else {
     else {
       this.state.selectedPages.add(page);
       this.state.selectedPages.add(page);
     }
     }
-    this.setState({isDeleteConfirmModalShown: false});
-    this.setState({selectedPages: this.state.selectedPages});
+    this.setState({ isDeleteConfirmModalShown: false });
+    this.setState({ selectedPages: this.state.selectedPages });
   }
   }
 
 
   /**
   /**
@@ -61,7 +64,7 @@ export default class SearchResult extends React.Component {
    * @memberof SearchResult
    * @memberof SearchResult
    */
    */
   isAllSelected() {
   isAllSelected() {
-    return this.state.selectedPages.size == this.props.pages.length;
+    return this.state.selectedPages.size === this.props.pages.length;
   }
   }
 
 
   /**
   /**
@@ -77,10 +80,12 @@ export default class SearchResult extends React.Component {
       this.state.selectedPages.clear();
       this.state.selectedPages.clear();
       this.props.pages.map((page) => {
       this.props.pages.map((page) => {
         this.state.selectedPages.add(page);
         this.state.selectedPages.add(page);
+        return;
       });
       });
     }
     }
-    this.setState({selectedPages: this.state.selectedPages});
+    this.setState({ selectedPages: this.state.selectedPages });
   }
   }
+
   /**
   /**
    * change deletion mode
    * change deletion mode
    *
    *
@@ -88,7 +93,7 @@ export default class SearchResult extends React.Component {
    */
    */
   handleDeletionModeChange() {
   handleDeletionModeChange() {
     this.state.selectedPages.clear();
     this.state.selectedPages.clear();
-    this.setState({deletionMode: !this.state.deletionMode});
+    this.setState({ deletionMode: !this.state.deletionMode });
   }
   }
 
 
   /**
   /**
@@ -98,7 +103,7 @@ export default class SearchResult extends React.Component {
    */
    */
   toggleDeleteCompletely() {
   toggleDeleteCompletely() {
     // request で completely が undefined でないと指定アリと見なされるため
     // request で completely が undefined でないと指定アリと見なされるため
-    this.setState({isDeleteCompletely: this.state.isDeleteCompletely? undefined : true});
+    this.setState({ isDeleteCompletely: this.state.isDeleteCompletely ? undefined : true });
   }
   }
 
 
   /**
   /**
@@ -107,41 +112,41 @@ export default class SearchResult extends React.Component {
    * @memberof SearchResult
    * @memberof SearchResult
    */
    */
   deleteSelectedPages() {
   deleteSelectedPages() {
-    let deleteCompletely = this.state.isDeleteCompletely;
+    const deleteCompletely = this.state.isDeleteCompletely;
     Promise.all(Array.from(this.state.selectedPages).map((page) => {
     Promise.all(Array.from(this.state.selectedPages).map((page) => {
       return new Promise((resolve, reject) => {
       return new Promise((resolve, reject) => {
         const pageId = page._id;
         const pageId = page._id;
         const revisionId = page.revision._id;
         const revisionId = page.revision._id;
-        this.props.crowi.apiPost('/pages.remove', {page_id: pageId, revision_id: revisionId, completely: deleteCompletely})
-          .then(res => {
+        this.props.crowi.apiPost('/pages.remove', { page_id: pageId, revision_id: revisionId, completely: deleteCompletely })
+          .then((res) => {
             if (res.ok) {
             if (res.ok) {
               this.state.selectedPages.delete(page);
               this.state.selectedPages.delete(page);
               return resolve();
               return resolve();
             }
             }
-            else {
-              return reject();
-            }
+
+            return reject();
+
           })
           })
-          .catch(err => {
-            console.log(err.message);   // eslint-disable-line no-console
-            this.setState({errorMessageForDeleting: err.message});
+          .catch((err) => {
+            console.log(err.message); // eslint-disable-line no-console
+            this.setState({ errorMessageForDeleting: err.message });
             return reject();
             return reject();
           });
           });
       });
       });
     }))
     }))
-    .then(() => {
-      window.location.reload();
-    })
-    .catch(err => {
-      toastr.error(err, 'Error occured', {
-        closeButton: true,
-        progressBar: true,
-        newestOnTop: false,
-        showDuration: '100',
-        hideDuration: '100',
-        timeOut: '3000',
+      .then(() => {
+        window.location.reload();
+      })
+      .catch((err) => {
+        toastr.error(err, 'Error occured', {
+          closeButton: true,
+          progressBar: true,
+          newestOnTop: false,
+          showDuration: '100',
+          hideDuration: '100',
+          timeOut: '3000',
+        });
       });
       });
-    });
   }
   }
 
 
   /**
   /**
@@ -150,7 +155,7 @@ export default class SearchResult extends React.Component {
    * @memberof SearchResult
    * @memberof SearchResult
    */
    */
   showDeleteConfirmModal() {
   showDeleteConfirmModal() {
-    this.setState({isDeleteConfirmModalShown: true});
+    this.setState({ isDeleteConfirmModalShown: true });
   }
   }
 
 
   /**
   /**
@@ -168,8 +173,8 @@ export default class SearchResult extends React.Component {
   render() {
   render() {
     const excludePathString = this.props.tree;
     const excludePathString = this.props.tree;
 
 
-    //console.log(this.props.searchError);
-    //console.log(this.isError());
+    // console.log(this.props.searchError);
+    // console.log(this.isError());
     if (this.isError()) {
     if (this.isError()) {
       return (
       return (
         <div className="content-main">
         <div className="content-main">
@@ -189,7 +194,7 @@ export default class SearchResult extends React.Component {
       }
       }
       return (
       return (
         <div className="content-main">
         <div className="content-main">
-            <i className="icon-fw icon-info" /> No page found with &quot;{this.props.searchingKeyword}&quot;{under}
+          <i className="icon-fw icon-info" /> No page found with &quot;{this.props.searchingKeyword}&quot;{under}
         </div>
         </div>
       );
       );
 
 
@@ -199,48 +204,63 @@ export default class SearchResult extends React.Component {
     let allSelectCheck = '';
     let allSelectCheck = '';
 
 
     if (this.state.deletionMode) {
     if (this.state.deletionMode) {
-      deletionModeButtons =
-      <div className="btn-group">
-        <button type="button" className="btn btn-rounded btn-default btn-xs" onClick={() => this.handleDeletionModeChange()}>
-          <i className="icon-ban"/> Cancel
-        </button>
-        <button type="button" className="btn btn-rounded btn-danger btn-xs" onClick={() => this.showDeleteConfirmModal()} disabled={this.state.selectedPages.size == 0}>
-          <i className="icon-trash"/> Delete
-        </button>
-      </div>;
-      allSelectCheck =
-      <div>
-        <label>
-          <input
-            type="checkbox"
-            onClick={() => this.handleAllSelect()}
-            checked={this.isAllSelected()} />
+      deletionModeButtons = (
+        <div className="btn-group">
+          <button type="button" className="btn btn-rounded btn-default btn-xs" onClick={() => { return this.handleDeletionModeChange() }}>
+            <i className="icon-ban" /> Cancel
+          </button>
+          <button
+            type="button"
+            className="btn btn-rounded btn-danger btn-xs"
+            onClick={() => { return this.showDeleteConfirmModal() }}
+            disabled={this.state.selectedPages.size === 0}
+          >
+            <i className="icon-trash" /> Delete
+          </button>
+        </div>
+      );
+      allSelectCheck = (
+        <div>
+          <label>
+            <input
+              type="checkbox"
+              onClick={() => { return this.handleAllSelect() }}
+              checked={this.isAllSelected()}
+            />
             &nbsp;Check All
             &nbsp;Check All
-        </label>
-      </div>;
+          </label>
+        </div>
+      );
     }
     }
     else {
     else {
-      deletionModeButtons =
-      <div className="btn-group">
-        <button type="button" className="btn btn-default btn-rounded btn-xs" onClick={() => this.handleDeletionModeChange()}>
-          <i className="ti-check-box"/> DeletionMode
-        </button>
-      </div>;
+      deletionModeButtons = (
+        <div className="btn-group">
+          <button type="button" className="btn btn-default btn-rounded btn-xs" onClick={() => { return this.handleDeletionModeChange() }}>
+            <i className="ti-check-box" /> DeletionMode
+          </button>
+        </div>
+      );
     }
     }
 
 
     const listView = this.props.pages.map((page) => {
     const listView = this.props.pages.map((page) => {
-      const pageId = '#' + page._id;
+      const pageId = `#${page._id}`;
       return (
       return (
-        <Page page={page}
+        <Page
+          page={page}
           linkTo={pageId}
           linkTo={pageId}
           key={page._id}
           key={page._id}
           excludePathString={excludePathString}
           excludePathString={excludePathString}
-          >
-          { this.state.deletionMode &&
-            <input type="checkbox" className="search-result-list-delete-checkbox"
+        >
+          { this.state.deletionMode
+            && (
+            <input
+              type="checkbox"
+              className="search-result-list-delete-checkbox"
               value={pageId}
               value={pageId}
               checked={this.state.selectedPages.has(page)}
               checked={this.state.selectedPages.has(page)}
-              onClick={() => this.toggleCheckbox(page)} />
+              onClick={() => { return this.toggleCheckbox(page) }}
+            />
+)
             }
             }
           <div className="page-list-option">
           <div className="page-list-option">
             <a href={page.path}><i className="icon-login" /></a>
             <a href={page.path}><i className="icon-login" /></a>
@@ -251,7 +271,7 @@ export default class SearchResult extends React.Component {
 
 
     // TODO あとでなんとかする
     // TODO あとでなんとかする
     setTimeout(() => {
     setTimeout(() => {
-      $('#search-result-list > nav').affix({ offset: { top: 50 }});
+      $('#search-result-list > nav').affix({ offset: { top: 50 } });
     }, 1200);
     }, 1200);
 
 
     /*
     /*
@@ -280,10 +300,11 @@ export default class SearchResult extends React.Component {
           </div>
           </div>
           <div className="col-md-8 search-result-content" id="search-result-content">
           <div className="col-md-8 search-result-content" id="search-result-content">
             <SearchResultList
             <SearchResultList
-              crowi={this.props.crowi} crowiRenderer={this.props.crowiRenderer}
+              crowi={this.props.crowi}
+              crowiRenderer={this.props.crowiRenderer}
               pages={this.props.pages}
               pages={this.props.pages}
               searchingKeyword={this.props.searchingKeyword}
               searchingKeyword={this.props.searchingKeyword}
-              />
+            />
           </div>
           </div>
         </div>
         </div>
         <DeletePageListModal
         <DeletePageListModal
@@ -295,9 +316,10 @@ export default class SearchResult extends React.Component {
           toggleDeleteCompletely={this.toggleDeleteCompletely}
           toggleDeleteCompletely={this.toggleDeleteCompletely}
         />
         />
 
 
-      </div>//content-main
+      </div>// content-main
     );
     );
   }
   }
+
 }
 }
 
 
 SearchResult.propTypes = {
 SearchResult.propTypes = {
@@ -307,12 +329,8 @@ SearchResult.propTypes = {
   pages: PropTypes.array.isRequired,
   pages: PropTypes.array.isRequired,
   searchingKeyword: PropTypes.string.isRequired,
   searchingKeyword: PropTypes.string.isRequired,
   searchResultMeta: PropTypes.object.isRequired,
   searchResultMeta: PropTypes.object.isRequired,
-  searchError: PropTypes.object
+  searchError: PropTypes.object,
 };
 };
 SearchResult.defaultProps = {
 SearchResult.defaultProps = {
-  tree: '',
-  pages: [],
-  searchingKeyword: '',
-  searchResultMeta: {},
   searchError: null,
   searchError: null,
 };
 };

+ 3 - 4
src/client/js/components/SearchPage/SearchResultList.js

@@ -10,7 +10,7 @@ export default class SearchResultList extends React.Component {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
-    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiRenderer, {mode: 'searchresult'});
+    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiRenderer, { mode: 'searchresult' });
   }
   }
 
 
   render() {
   render() {
@@ -32,10 +32,11 @@ export default class SearchResultList extends React.Component {
 
 
     return (
     return (
       <div>
       <div>
-      {resultList}
+        {resultList}
       </div>
       </div>
     );
     );
   }
   }
+
 }
 }
 
 
 SearchResultList.propTypes = {
 SearchResultList.propTypes = {
@@ -46,6 +47,4 @@ SearchResultList.propTypes = {
 };
 };
 
 
 SearchResultList.defaultProps = {
 SearchResultList.defaultProps = {
-  pages: [],
-  searchingKeyword: '',
 };
 };

+ 6 - 7
src/client/js/components/User/User.js

@@ -7,7 +7,7 @@ export default class User extends React.Component {
 
 
   render() {
   render() {
     const user = this.props.user;
     const user = this.props.user;
-    const userLink = '/user/' + user.username;
+    const userLink = `/user/${user.username}`;
 
 
     const username = this.props.username;
     const username = this.props.username;
     const name = this.props.name;
     const name = this.props.name;
@@ -17,16 +17,17 @@ export default class User extends React.Component {
         <a href={userLink}>
         <a href={userLink}>
           <UserPicture user={user} />
           <UserPicture user={user} />
 
 
-          {username &&
-              <span className="user-component-username">@{user.username}</span>
+          {username
+              && <span className="user-component-username">@{user.username}</span>
           }
           }
-          {name &&
-              <span className="user-component-name">({user.name})</span>
+          {name
+              && <span className="user-component-name">({user.name})</span>
           }
           }
         </a>
         </a>
       </span>
       </span>
     );
     );
   }
   }
+
 }
 }
 
 
 User.propTypes = {
 User.propTypes = {
@@ -36,6 +37,4 @@ User.propTypes = {
 };
 };
 
 
 User.defaultProps = {
 User.defaultProps = {
-  name: false,
-  username: false,
 };
 };

+ 9 - 9
src/client/js/components/User/UserPicture.js

@@ -11,15 +11,15 @@ export default class UserPicture extends React.Component {
       return this.generateGravatarSrc(user);
       return this.generateGravatarSrc(user);
     }
     }
     // uploaded image
     // uploaded image
-    else if (user.image != null) {
+    if (user.image != null) {
       return user.image;
       return user.image;
     }
     }
-    else if (user.imageAttachment != null) {
+    if (user.imageAttachment != null) {
       return user.imageAttachment.filePathProxied;
       return user.imageAttachment.filePathProxied;
     }
     }
-    else {
-      return '/images/icons/user.svg';
-    }
+
+    return '/images/icons/user.svg';
+
   }
   }
 
 
   generateGravatarSrc(user) {
   generateGravatarSrc(user) {
@@ -29,10 +29,10 @@ export default class UserPicture extends React.Component {
   }
   }
 
 
   getClassName() {
   getClassName() {
-    let className = ['img-circle', 'picture'];
+    const className = ['img-circle', 'picture'];
     // size
     // size
     if (this.props.size) {
     if (this.props.size) {
-      className.push('picture-' + this.props.size);
+      className.push(`picture-${this.props.size}`);
     }
     }
 
 
     return className.join(' ');
     return className.join(' ');
@@ -46,9 +46,10 @@ export default class UserPicture extends React.Component {
         src={this.getUserPicture(user)}
         src={this.getUserPicture(user)}
         alt={user.username}
         alt={user.username}
         className={this.getClassName()}
         className={this.getClassName()}
-        />
+      />
     );
     );
   }
   }
+
 }
 }
 
 
 UserPicture.propTypes = {
 UserPicture.propTypes = {
@@ -57,6 +58,5 @@ UserPicture.propTypes = {
 };
 };
 
 
 UserPicture.defaultProps = {
 UserPicture.defaultProps = {
-  user: {},
   size: null,
   size: null,
 };
 };

+ 20 - 19
src/client/js/hackmd-agent.js

@@ -16,7 +16,7 @@ import { debounce } from 'throttle-debounce';
 
 
 /* eslint-disable no-console  */
 /* eslint-disable no-console  */
 
 
-const allowedOrigin = '{{origin}}';         // will be replaced by swig
+const allowedOrigin = '{{origin}}'; // will be replaced by swig
 
 
 
 
 /**
 /**
@@ -47,14 +47,14 @@ function setValueToCodemirrorOnInit(newValue) {
     setValueToCodemirror(newValue);
     setValueToCodemirror(newValue);
     return;
     return;
   }
   }
-  else {
-    const intervalId = setInterval(() => {
-      if (window.cmClient != null) {
-        clearInterval(intervalId);
-        setValueToCodemirror(newValue);
-      }
-    }, 250);
-  }
+
+  const intervalId = setInterval(() => {
+    if (window.cmClient != null) {
+      clearInterval(intervalId);
+      setValueToCodemirror(newValue);
+    }
+  }, 250);
+
 }
 }
 
 
 /**
 /**
@@ -86,7 +86,7 @@ function addEventListenersToCodemirror() {
     return;
     return;
   }
   }
 
 
-  //// change event
+  // == change event
   editor.on('change', (cm, change) => {
   editor.on('change', (cm, change) => {
     if (change.origin === 'ignoreHistory') {
     if (change.origin === 'ignoreHistory') {
       // do nothing because this operation triggered by other user
       // do nothing because this operation triggered by other user
@@ -95,7 +95,7 @@ function addEventListenersToCodemirror() {
     debouncedPostParentToNotifyBodyChanges(cm.doc.getValue());
     debouncedPostParentToNotifyBodyChanges(cm.doc.getValue());
   });
   });
 
 
-  //// save event
+  // == save event
   // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
   // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
   codemirror.commands.save = function(cm) {
   codemirror.commands.save = function(cm) {
     postParentToSaveWithShortcut(cm.doc.getValue());
     postParentToSaveWithShortcut(cm.doc.getValue());
@@ -117,14 +117,16 @@ function connectToParentWithPenpal() {
       },
       },
       setValueOnInit(newValue) {
       setValueOnInit(newValue) {
         setValueToCodemirrorOnInit(newValue);
         setValueToCodemirrorOnInit(newValue);
-      }
-    }
-  });
-  connection.promise.then(parent => {
-    window.growi = parent;
-  }).catch(err => {
-    console.log(err);
+      },
+    },
   });
   });
+  connection.promise
+    .then((parent) => {
+      window.growi = parent;
+    })
+    .catch((err) => {
+      console.log(err);
+    });
 }
 }
 
 
 /**
 /**
@@ -148,4 +150,3 @@ function connectToParentWithPenpal() {
 
 
   console.log('[HackMD] GROWI agent for HackMD has successfully loaded.');
   console.log('[HackMD] GROWI agent for HackMD has successfully loaded.');
 }());
 }());
-

+ 1 - 1
src/client/js/hackmd-styles.js

@@ -12,7 +12,7 @@
 
 
 /* eslint-disable no-console  */
 /* eslint-disable no-console  */
 
 
-const styles = '{{styles}}';         // will be replaced by swig
+const styles = '{{styles}}'; // will be replaced by swig
 
 
 /**
 /**
  * Insert link tag to load style file
  * Insert link tag to load style file

+ 2 - 2
src/client/js/i18n.js

@@ -39,7 +39,7 @@ export default (userlang) => {
         withRef: true,
         withRef: true,
         bindI18n: 'languageChanged loaded',
         bindI18n: 'languageChanged loaded',
         bindStore: 'added removed',
         bindStore: 'added removed',
-        nsMode: 'default'
-      }
+        nsMode: 'default',
+      },
     });
     });
 };
 };

+ 1 - 1
src/client/js/installer.js

@@ -19,6 +19,6 @@ if (installerFormElem) {
     <I18nextProvider i18n={i18n}>
     <I18nextProvider i18n={i18n}>
       <InstallerForm userName={userName} name={name} email={email} csrf={csrf} />
       <InstallerForm userName={userName} name={name} email={email} csrf={csrf} />
     </I18nextProvider>,
     </I18nextProvider>,
-    installerFormElem
+    installerFormElem,
   );
   );
 }
 }

+ 1 - 1
src/client/js/plugin.js

@@ -39,4 +39,4 @@ export default class CrowiPlugin {
 
 
 }
 }
 
 
-window.crowiPlugin = new CrowiPlugin();     // FIXME
+window.crowiPlugin = new CrowiPlugin(); // FIXME

+ 2 - 0
src/client/styles/scss/_attachments.scss

@@ -1,11 +1,13 @@
 .page-attachments-row {
 .page-attachments-row {
   border-top: solid 1px transparent;
   border-top: solid 1px transparent;
 }
 }
+
 .page-attachments {
 .page-attachments {
   li.attachment {
   li.attachment {
     list-style: none;
     list-style: none;
   }
   }
 }
 }
+
 .page-attachments,
 .page-attachments,
 .page-meta {
 .page-meta {
   font-size: 0.95em;
   font-size: 0.95em;

+ 1 - 0
src/client/styles/scss/_editor-attachment.scss

@@ -110,6 +110,7 @@
     }
     }
   }
   }
 }
 }
+
 .comment-form {
 .comment-form {
   @media (max-width: $screen-sm) {
   @media (max-width: $screen-sm) {
     .desc-long {
     .desc-long {

+ 1 - 1
src/client/styles/scss/_editor-overlay.scss

@@ -47,7 +47,7 @@
       }
       }
     }
     }
 
 
-    a.gfm-cheatsheet-modal-link {
+    .gfm-cheatsheet-modal-link {
       color: $text-muted;
       color: $text-muted;
       pointer-events: all;
       pointer-events: all;
       cursor: pointer;
       cursor: pointer;

+ 1 - 0
src/client/styles/scss/_hljs.scss

@@ -30,6 +30,7 @@ pre.hljs {
   user-select: none;
   user-select: none;
   border-right: 1px solid #ccc;
   border-right: 1px solid #ccc;
 }
 }
+
 .hljs-ln td.hljs-ln-code {
 .hljs-ln td.hljs-ln-code {
   padding-left: 10px;
   padding-left: 10px;
 }
 }

+ 1 - 0
src/client/styles/scss/_layout.scss

@@ -107,6 +107,7 @@
 // fix tab width to 95 pixels
 // fix tab width to 95 pixels
 // see also '_on-edit.scss'
 // see also '_on-edit.scss'
 $nav-main-left-tab-width: 95px;
 $nav-main-left-tab-width: 95px;
+
 .nav.nav-tabs {
 .nav.nav-tabs {
   .nav-main-left-tab {
   .nav-main-left-tab {
     width: $nav-main-left-tab-width;
     width: $nav-main-left-tab-width;

+ 1 - 0
src/client/styles/scss/_layout_crowi_sidebar.scss

@@ -182,6 +182,7 @@ body:not(.aside-hidden) #toggle-sidebar {
     display: block;
     display: block;
   }
   }
 }
 }
+
 .aside-hidden {
 .aside-hidden {
   #toggle-sidebar {
   #toggle-sidebar {
     right: 0;
     right: 0;

+ 1 - 0
src/client/styles/scss/_layout_kibela.scss

@@ -187,6 +187,7 @@ body.kibela {
     }
     }
   }
   }
 }
 }
+
 .on-edit {
 .on-edit {
   .kibela-block {
   .kibela-block {
     border: 0px;
     border: 0px;

+ 1 - 0
src/client/styles/scss/_on-edit.scss

@@ -293,6 +293,7 @@ body.on-edit {
 .nav-tabs-create-portal {
 .nav-tabs-create-portal {
   display: none;
   display: none;
 }
 }
+
 .on-edit .nav-tabs-create-portal {
 .on-edit .nav-tabs-create-portal {
   display: block;
   display: block;
 }
 }

+ 1 - 0
src/client/styles/scss/_page.scss

@@ -201,6 +201,7 @@
     box-shadow: 0 0 20px rgba(0, 0, 0, 0.8);
     box-shadow: 0 0 20px rgba(0, 0, 0, 0.8);
   }
   }
 }
 }
+
 .overlay-on {
 .overlay-on {
   #wrapper {
   #wrapper {
     filter: blur(5px);
     filter: blur(5px);

+ 2 - 0
src/client/styles/scss/_page_list.scss

@@ -53,10 +53,12 @@
   font-weight: bold;
   font-weight: bold;
   color: #e80000;
   color: #e80000;
 }
 }
+
 .popular-page-mid {
 .popular-page-mid {
   font-weight: bold;
   font-weight: bold;
   color: #e47800;
   color: #e47800;
 }
 }
+
 .popular-page-low {
 .popular-page-low {
   color: #ab7c7c;
   color: #ab7c7c;
 }
 }

+ 3 - 0
src/client/styles/scss/_search.scss

@@ -1,5 +1,6 @@
 // import react-bootstrap-typeahead
 // import react-bootstrap-typeahead
 @import '~react-bootstrap-typeahead/css/Typeahead';
 @import '~react-bootstrap-typeahead/css/Typeahead';
+
 .search-listpage-icon {
 .search-listpage-icon {
   font-size: 16px;
   font-size: 16px;
   color: #999;
   color: #999;
@@ -102,6 +103,7 @@
     }
     }
   }
   }
 }
 }
+
 .search-sidebar {
 .search-sidebar {
   .search-form,
   .search-form,
   .form-group,
   .form-group,
@@ -121,6 +123,7 @@
     //                                  since upgrade react-bootstrap-typeahead to v3.3.2 -- 2019.02.05 Yuki Takei
     //                                  since upgrade react-bootstrap-typeahead to v3.3.2 -- 2019.02.05 Yuki Takei
   }
   }
 }
 }
+
 #search-top,
 #search-top,
 #renamePage,
 #renamePage,
 #duplicatePage,
 #duplicatePage,

+ 1 - 0
src/client/styles/scss/style-app.scss

@@ -90,6 +90,7 @@
     content: '⌘';
     content: '⌘';
   }
   }
 }
 }
+
 .cmd-key.win {
 .cmd-key.win {
   &:after {
   &:after {
     content: 'Ctrl';
     content: 'Ctrl';

+ 2 - 0
src/client/styles/scss/theme/_override-agileadmin.scss

@@ -38,6 +38,7 @@
     text-transform: none;
     text-transform: none;
   }
   }
 }
 }
+
 .logo {
 .logo {
   .logo-mark {
   .logo-mark {
     svg {
     svg {
@@ -185,6 +186,7 @@ button.btn.btn-link:hover {
     padding: 7px 15px;
     padding: 7px 15px;
   }
   }
 }
 }
+
 .panel.panel-primary .panel-heading {
 .panel.panel-primary .panel-heading {
 }
 }
 
 

+ 2 - 0
src/lib/models/cdn-resource.js

@@ -2,11 +2,13 @@
  * Value Object
  * Value Object
  */
  */
 class CdnResource {
 class CdnResource {
+
   constructor(name, url, outDir) {
   constructor(name, url, outDir) {
     this.name = name;
     this.name = name;
     this.url = url;
     this.url = url;
     this.outDir = outDir;
     this.outDir = outDir;
   }
   }
+
 }
 }
 
 
 module.exports = CdnResource;
 module.exports = CdnResource;

+ 2 - 0
src/lib/service/cdn-resources-downloader.js

@@ -11,6 +11,7 @@ const CdnResource = require('../models/cdn-resource');
 
 
 
 
 class CdnResourcesDownloader {
 class CdnResourcesDownloader {
+
   constructor() {
   constructor() {
     this.logger = require('@alias/logger')('growi:service:CdnResourcesDownloader');
     this.logger = require('@alias/logger')('growi:service:CdnResourcesDownloader');
   }
   }
@@ -144,6 +145,7 @@ class CdnResourcesDownloader {
 
 
     return streamToPromise(stream);
     return streamToPromise(stream);
   }
   }
+
 }
 }
 
 
 module.exports = CdnResourcesDownloader;
 module.exports = CdnResourcesDownloader;

+ 2 - 0
src/lib/service/cdn-resources-service.js

@@ -10,6 +10,7 @@ const cdnLocalStyleWebRoot = '/styles/cdn';
 
 
 
 
 class CdnResourcesService {
 class CdnResourcesService {
+
   constructor() {
   constructor() {
     this.logger = require('@alias/logger')('growi:service:CdnResourcesService');
     this.logger = require('@alias/logger')('growi:service:CdnResourcesService');
 
 
@@ -161,6 +162,7 @@ class CdnResourcesService {
 
 
     return this.generateStyleTag(manifest, this.noCdn);
     return this.generateStyleTag(manifest, this.noCdn);
   }
   }
+
 }
 }
 
 
 module.exports = CdnResourcesService;
 module.exports = CdnResourcesService;

+ 7 - 5
src/lib/service/interceptor-manager.js

@@ -4,6 +4,7 @@ const logger = require('@alias/logger')('growi:InterceptorManager');
  * the manager class of Interceptor
  * the manager class of Interceptor
  */
  */
 class InterceptorManager {
 class InterceptorManager {
+
   constructor() {
   constructor() {
     this.interceptorAndOrders = []; /* [
     this.interceptorAndOrders = []; /* [
                                           {interceptor: instanceA, order: 200 },
                                           {interceptor: instanceA, order: 200 },
@@ -76,11 +77,11 @@ class InterceptorManager {
         return this.doProcess(interceptor, contextName, ...args);
         return this.doProcess(interceptor, contextName, ...args);
       })
       })
       // sequential
       // sequential
-      .concat([
-        sequentials.reduce((prevPromise, nextInterceptor) => {
-          return prevPromise.then((...results) => { return this.doProcess(nextInterceptor, contextName, ...results) });
-        }, Promise.resolve(...args)/* initial Promise */),
-      ]),
+        .concat([
+          sequentials.reduce((prevPromise, nextInterceptor) => {
+            return prevPromise.then((...results) => { return this.doProcess(nextInterceptor, contextName, ...results) });
+          }, Promise.resolve(...args)/* initial Promise */),
+        ]),
     ).then(() => {
     ).then(() => {
       logger.debug(`end processing the context '${contextName}'`);
       logger.debug(`end processing the context '${contextName}'`);
     });
     });
@@ -98,6 +99,7 @@ class InterceptorManager {
         return Promise.resolve(...args);
         return Promise.resolve(...args);
       });
       });
   }
   }
+
 }
 }
 
 
 module.exports = InterceptorManager;
 module.exports = InterceptorManager;

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

@@ -1,4 +1,5 @@
 class Xss {
 class Xss {
+
   constructor(xssOption) {
   constructor(xssOption) {
     const xss = require('xss');
     const xss = require('xss');
 
 
@@ -29,6 +30,7 @@ class Xss {
   process(document) {
   process(document) {
     return this.myxss.process(document);
     return this.myxss.process(document);
   }
   }
+
 }
 }
 
 
 module.exports = Xss;
 module.exports = Xss;

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

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

+ 2 - 0
src/server/crowi/dev.js

@@ -7,6 +7,7 @@ const onHeaders = require('on-headers');
 
 
 
 
 class CrowiDev {
 class CrowiDev {
+
   /**
   /**
    * Creates an instance of CrowiDev.
    * Creates an instance of CrowiDev.
    * @param {Crowi} crowi
    * @param {Crowi} crowi
@@ -137,6 +138,7 @@ class CrowiDev {
       }
       }
     }
     }
   }
   }
+
 }
 }
 
 
 module.exports = CrowiDev;
 module.exports = CrowiDev;

+ 2 - 0
src/server/models/GlobalNotificationSetting/index.js

@@ -39,6 +39,7 @@ const generatePathsToMatch = (originalPath) => {
  * @class GlobalNotificationSetting
  * @class GlobalNotificationSetting
  */
  */
 class GlobalNotificationSetting {
 class GlobalNotificationSetting {
+
   constructor(crowi) {
   constructor(crowi) {
     this.crowi = crowi;
     this.crowi = crowi;
   }
   }
@@ -95,6 +96,7 @@ class GlobalNotificationSetting {
 
 
     return settings;
     return settings;
   }
   }
+
 }
 }
 
 
 
 

+ 4 - 0
src/server/models/external-account.js

@@ -29,11 +29,13 @@ schema.plugin(uniqueValidator);
  * @class DuplicatedUsernameException
  * @class DuplicatedUsernameException
  */
  */
 class DuplicatedUsernameException {
 class DuplicatedUsernameException {
+
   constructor(message, user) {
   constructor(message, user) {
     this.name = this.constructor.name;
     this.name = this.constructor.name;
     this.message = message;
     this.message = message;
     this.user = user;
     this.user = user;
   }
   }
+
 }
 }
 
 
 /**
 /**
@@ -42,6 +44,7 @@ class DuplicatedUsernameException {
  * @class ExternalAccount
  * @class ExternalAccount
  */
  */
 class ExternalAccount {
 class ExternalAccount {
+
   /**
   /**
    * limit items num for pagination
    * limit items num for pagination
    *
    *
@@ -171,6 +174,7 @@ class ExternalAccount {
         debug('Error on pagination:', err);
         debug('Error on pagination:', err);
       });
       });
   }
   }
+
 }
 }
 
 
 module.exports = function(crowi) {
 module.exports = function(crowi) {

+ 2 - 0
src/server/models/page-group-relation.js

@@ -28,6 +28,7 @@ schema.plugin(mongoosePaginate);
  * @class PageGroupRelation
  * @class PageGroupRelation
  */
  */
 class PageGroupRelation {
 class PageGroupRelation {
+
   /**
   /**
    * limit items num for pagination
    * limit items num for pagination
    *
    *
@@ -249,6 +250,7 @@ class PageGroupRelation {
         }
         }
       });
       });
   }
   }
+
 }
 }
 
 
 module.exports = function(crowi) {
 module.exports = function(crowi) {

+ 2 - 0
src/server/models/page.js

@@ -117,6 +117,7 @@ const populateDataToShowRevision = (page, userPublicFields, imagePopulation) =>
 
 
 
 
 class PageQueryBuilder {
 class PageQueryBuilder {
+
   constructor(query) {
   constructor(query) {
     this.query = query;
     this.query = query;
   }
   }
@@ -261,6 +262,7 @@ class PageQueryBuilder {
     this.query = populateDataToShowRevision(this.query, userPublicFields, imagePopulation);
     this.query = populateDataToShowRevision(this.query, userPublicFields, imagePopulation);
     return this;
     return this;
   }
   }
+
 }
 }
 
 
 module.exports = function(crowi) {
 module.exports = function(crowi) {

+ 2 - 0
src/server/models/user-group-relation.js

@@ -21,6 +21,7 @@ schema.plugin(mongoosePaginate);
  * @class UserGroupRelation
  * @class UserGroupRelation
  */
  */
 class UserGroupRelation {
 class UserGroupRelation {
+
   /**
   /**
    * limit items num for pagination
    * limit items num for pagination
    *
    *
@@ -280,6 +281,7 @@ class UserGroupRelation {
         }
         }
       });
       });
   }
   }
+
 }
 }
 
 
 module.exports = function(crowi) {
 module.exports = function(crowi) {

+ 2 - 0
src/server/models/user-group.js

@@ -14,6 +14,7 @@ const schema = new mongoose.Schema({
 schema.plugin(mongoosePaginate);
 schema.plugin(mongoosePaginate);
 
 
 class UserGroup {
 class UserGroup {
+
   /**
   /**
    * public fields for UserGroup model
    * public fields for UserGroup model
    *
    *
@@ -127,6 +128,7 @@ class UserGroup {
     this.name = name;
     this.name = name;
     return this.save();
     return this.save();
   }
   }
+
 }
 }
 
 
 
 

+ 2 - 0
src/server/models/user.js

@@ -822,9 +822,11 @@ module.exports = function(crowi) {
   };
   };
 
 
   class UserUpperLimitException {
   class UserUpperLimitException {
+
     constructor() {
     constructor() {
       this.name = this.constructor.name;
       this.name = this.constructor.name;
     }
     }
+
   }
   }
 
 
   userSchema.statics.STATUS_REGISTERED = STATUS_REGISTERED;
   userSchema.statics.STATUS_REGISTERED = STATUS_REGISTERED;

+ 2 - 0
src/server/plugins/plugin-utils-v2.js

@@ -1,6 +1,7 @@
 const path = require('path');
 const path = require('path');
 
 
 class PluginUtilsV2 {
 class PluginUtilsV2 {
+
   /**
   /**
    * return a definition objects that has following structure:
    * return a definition objects that has following structure:
    *
    *
@@ -33,6 +34,7 @@ class PluginUtilsV2 {
       entries,
       entries,
     };
     };
   }
   }
+
 }
 }
 
 
 module.exports = PluginUtilsV2;
 module.exports = PluginUtilsV2;

+ 2 - 0
src/server/plugins/plugin-utils.js

@@ -7,6 +7,7 @@ const PluginUtilsV2 = require('./plugin-utils-v2');
 const pluginUtilsV2 = new PluginUtilsV2();
 const pluginUtilsV2 = new PluginUtilsV2();
 
 
 class PluginUtils {
 class PluginUtils {
+
   /**
   /**
    * return a definition objects that has following structure:
    * return a definition objects that has following structure:
    *
    *
@@ -88,6 +89,7 @@ class PluginUtils {
     const plugins = this.listPlugins(rootDir);
     const plugins = this.listPlugins(rootDir);
     return Object.keys(plugins);
     return Object.keys(plugins);
   }
   }
+
 }
 }
 
 
 module.exports = PluginUtils;
 module.exports = PluginUtils;

+ 2 - 0
src/server/plugins/plugin.service.js

@@ -2,6 +2,7 @@ const logger = require('@alias/logger')('growi:plugins:PluginService');
 const PluginUtils = require('./plugin-utils');
 const PluginUtils = require('./plugin-utils');
 
 
 class PluginService {
 class PluginService {
+
   constructor(crowi, app) {
   constructor(crowi, app) {
     this.crowi = crowi;
     this.crowi = crowi;
     this.app = app;
     this.app = app;
@@ -44,6 +45,7 @@ class PluginService {
         });
         });
     }
     }
   }
   }
+
 }
 }
 
 
 module.exports = PluginService;
 module.exports = PluginService;

+ 2 - 0
src/server/service/config-loader.js

@@ -185,6 +185,7 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
 };
 };
 
 
 class ConfigLoader {
 class ConfigLoader {
+
   constructor(configModel) {
   constructor(configModel) {
     this.configModel = configModel;
     this.configModel = configModel;
   }
   }
@@ -256,6 +257,7 @@ class ConfigLoader {
 
 
     return config;
     return config;
   }
   }
+
 }
 }
 
 
 module.exports = ConfigLoader;
 module.exports = ConfigLoader;

+ 2 - 0
src/server/service/config-manager.js

@@ -15,6 +15,7 @@ const KEYS_FOR_SAML_USE_ONLY_ENV_OPTION = [
 ];
 ];
 
 
 class ConfigManager {
 class ConfigManager {
+
   constructor(configModel) {
   constructor(configModel) {
     this.configModel = configModel;
     this.configModel = configModel;
     this.configLoader = new ConfigLoader(this.configModel);
     this.configLoader = new ConfigLoader(this.configModel);
@@ -219,6 +220,7 @@ class ConfigManager {
   convertInsertValue(value) {
   convertInsertValue(value) {
     return JSON.stringify(value === '' ? null : value);
     return JSON.stringify(value === '' ? null : value);
   }
   }
+
 }
 }
 
 
 module.exports = ConfigManager;
 module.exports = ConfigManager;

+ 2 - 0
src/server/service/file-uploader/index.js

@@ -8,6 +8,7 @@ const envToModuleMappings = {
 };
 };
 
 
 class FileUploaderFactory {
 class FileUploaderFactory {
+
   getUploader(crowi) {
   getUploader(crowi) {
     if (this.uploader == null) {
     if (this.uploader == null) {
       const method = envToModuleMappings[process.env.FILE_UPLOAD] || 'aws';
       const method = envToModuleMappings[process.env.FILE_UPLOAD] || 'aws';
@@ -17,6 +18,7 @@ class FileUploaderFactory {
 
 
     return this.uploader;
     return this.uploader;
   }
   }
+
 }
 }
 
 
 const factory = new FileUploaderFactory();
 const factory = new FileUploaderFactory();

+ 2 - 0
src/server/service/global-notification.js

@@ -4,6 +4,7 @@ const path = require('path');
  * the service class of GlobalNotificationSetting
  * the service class of GlobalNotificationSetting
  */
  */
 class GlobalNotificationService {
 class GlobalNotificationService {
+
   constructor(crowi) {
   constructor(crowi) {
     this.crowi = crowi;
     this.crowi = crowi;
     this.config = crowi.getConfig();
     this.config = crowi.getConfig();
@@ -184,6 +185,7 @@ class GlobalNotificationService {
 
 
     this.sendNotification(notifications, option);
     this.sendNotification(notifications, option);
   }
   }
+
 }
 }
 
 
 module.exports = GlobalNotificationService;
 module.exports = GlobalNotificationService;

+ 2 - 0
src/server/service/passport.js

@@ -12,6 +12,7 @@ const SamlStrategy = require('passport-saml').Strategy;
  * the service class of Passport
  * the service class of Passport
  */
  */
 class PassportService {
 class PassportService {
+
   // see '/lib/form/login.js'
   // see '/lib/form/login.js'
   static get USERNAME_FIELD() { return 'loginForm[username]' }
   static get USERNAME_FIELD() { return 'loginForm[username]' }
 
 
@@ -539,6 +540,7 @@ class PassportService {
     const key = `security:passport-${providerType}:isSameEmailTreatedAsIdenticalUser`;
     const key = `security:passport-${providerType}:isSameEmailTreatedAsIdenticalUser`;
     return this.crowi.configManager.getConfig('crowi', key);
     return this.crowi.configManager.getConfig('crowi', key);
   }
   }
+
 }
 }
 
 
 module.exports = PassportService;
 module.exports = PassportService;

+ 2 - 0
src/server/service/rest-qiita-API.js

@@ -16,6 +16,7 @@ function getAxios(team, token) {
  */
  */
 
 
 class RestQiitaAPIService {
 class RestQiitaAPIService {
+
   constructor(crowi) {
   constructor(crowi) {
     this.crowi = crowi;
     this.crowi = crowi;
     this.config = crowi.getConfig();
     this.config = crowi.getConfig();
@@ -79,6 +80,7 @@ class RestQiitaAPIService {
       return { pages, total };
       return { pages, total };
     }
     }
   }
   }
+
 }
 }
 
 
 module.exports = RestQiitaAPIService;
 module.exports = RestQiitaAPIService;

+ 5 - 54
wercker.yml

@@ -22,6 +22,11 @@ test:
         echo -n "npm " && npm -v
         echo -n "npm " && npm -v
         yarn list --depth=0
         yarn list --depth=0
 
 
+    - script:
+      name: npm run lint
+      code: |
+        npm run lint
+
     - script:
     - script:
       name: npm test
       name: npm test
       code: |
       code: |
@@ -36,36 +41,6 @@ test:
       username: wercker
       username: wercker
       notify_on: "failed"
       notify_on: "failed"
 
 
-lint:
-  steps:
-    - script:
-      name: set yarn cache-folder
-      code: yarn config set cache-folder $WERCKER_CACHE_DIR/yarn
-
-    - script:
-      name: install dependencies
-      code: |
-        yarn
-
-    - script:
-      name: print dependencies
-      code: |
-        echo -n "node " && node -v
-        echo -n "npm " && npm -v
-        yarn list --depth=0
-
-    - script:
-      name: npm run lint
-      code: |
-        npm run lint
-
-  after-steps:
-    - slack-notifier:
-      url: $SLACK_WEBHOOK_URL
-      channel: ci
-      username: wercker
-      notify_on: "failed"
-
 
 
 build-prod:
 build-prod:
   steps:
   steps:
@@ -73,23 +48,11 @@ build-prod:
       name: set yarn cache-folder
       name: set yarn cache-folder
       code: yarn config set cache-folder $WERCKER_CACHE_DIR/yarn
       code: yarn config set cache-folder $WERCKER_CACHE_DIR/yarn
 
 
-    - script:
-      name: install dependencies
-      code: |
-        yarn
-
     - script:
     - script:
       name: install plugins
       name: install plugins
       code: |
       code: |
         yarn add growi-plugin-lsx growi-plugin-pukiwiki-like-linker
         yarn add growi-plugin-lsx growi-plugin-pukiwiki-like-linker
 
 
-    - script:
-      name: print dependencies
-      code: |
-        echo -n "node " && node -v
-        echo -n "npm " && npm -v
-        yarn list --depth=0
-
     - script:
     - script:
       name: npm run build:prod:analyze
       name: npm run build:prod:analyze
       code: |
       code: |
@@ -121,23 +84,11 @@ build-dev:
       name: set yarn cache-folder
       name: set yarn cache-folder
       code: yarn config set cache-folder $WERCKER_CACHE_DIR/yarn
       code: yarn config set cache-folder $WERCKER_CACHE_DIR/yarn
 
 
-    - script:
-      name: install dependencies
-      code: |
-        yarn
-
     - script:
     - script:
       name: install plugins
       name: install plugins
       code: |
       code: |
         yarn add growi-plugin-lsx growi-plugin-pukiwiki-like-linker
         yarn add growi-plugin-lsx growi-plugin-pukiwiki-like-linker
 
 
-    - script:
-      name: print dependencies
-      code: |
-        echo -n "node " && node -v
-        echo -n "npm " && npm -v
-        yarn list --depth=0
-
     - script:
     - script:
       name: npm run build:dev
       name: npm run build:dev
       code: |
       code: |