Yuki Takei 7 лет назад
Родитель
Сommit
d8ce7132fd
80 измененных файлов с 1463 добавлено и 1172 удалено
  1. 115 86
      src/client/js/app.js
  2. 6 7
      src/client/js/components/Admin/CustomCssEditor.js
  3. 6 7
      src/client/js/components/Admin/CustomHeaderEditor.js
  4. 6 7
      src/client/js/components/Admin/CustomScriptEditor.js
  5. 0 2
      src/client/js/components/Common/UserDate.js
  6. 14 7
      src/client/js/components/CopyButton.js
  7. 3 3
      src/client/js/components/Page/PagePath.js
  8. 5 5
      src/client/js/components/Page/RevisionBody.js
  9. 23 21
      src/client/js/components/Page/RevisionPath.js
  10. 9 5
      src/client/js/components/Page/RevisionUrl.js
  11. 11 11
      src/client/js/components/PageAttachment.js
  12. 12 6
      src/client/js/components/PageAttachment/Attachment.js
  13. 15 7
      src/client/js/components/PageAttachment/DeleteAttachmentModal.js
  14. 5 7
      src/client/js/components/PageAttachment/PageAttachmentList.js
  15. 23 22
      src/client/js/components/PageComment/Comment.js
  16. 6 5
      src/client/js/components/PageComment/CommentPreview.js
  17. 15 9
      src/client/js/components/PageComment/DeleteCommentModal.js
  18. 38 30
      src/client/js/components/PageComments.js
  19. 30 25
      src/client/js/components/PageEditor.js
  20. 0 2
      src/client/js/components/PageEditor/AbstractEditor.js
  21. 123 25
      src/client/js/components/PageEditor/Cheatsheet.js
  22. 171 110
      src/client/js/components/PageEditor/CodeMirrorEditor.js
  23. 10 14
      src/client/js/components/PageEditor/EmojiAutoCompleteHelper.js
  24. 1 4
      src/client/js/components/PageEditor/MarkdownListUtil.js
  25. 2 3
      src/client/js/components/PageEditor/MarkdownTableInterceptor.js
  26. 7 8
      src/client/js/components/PageEditor/MarkdownTableUtil.js
  27. 47 31
      src/client/js/components/PageEditor/OptionsSelector.js
  28. 1 1
      src/client/js/components/PageEditor/PasteHelper.js
  29. 2 3
      src/client/js/components/PageEditor/PreventMarkdownListInterceptor.js
  30. 7 6
      src/client/js/components/PageEditor/Preview.js
  31. 8 9
      src/client/js/components/PageEditor/ScrollSyncHelper.js
  32. 52 13
      src/client/js/components/PageEditor/SimpleCheatsheet.js
  33. 19 18
      src/client/js/components/PageEditor/TextAreaEditor.js
  34. 18 19
      src/client/js/components/PageHistory.js
  35. 9 7
      src/client/js/components/PageHistory/RevisionDiff.js
  36. 2 3
      src/client/js/components/PageList/ListView.js
  37. 2 4
      src/client/js/components/PageList/Page.js
  38. 12 4
      src/client/js/components/PageList/PageListMeta.js
  39. 8 6
      src/client/js/components/PageList/PagePath.js
  40. 5 8
      src/client/js/components/ReactUtils.js
  41. 22 8
      src/client/js/components/SearchForm.js
  42. 21 17
      src/client/js/components/SearchPage.js
  43. 8 7
      src/client/js/components/SearchPage/DeletePageListModal.js
  44. 22 20
      src/client/js/components/SearchPage/SearchPageForm.js
  45. 88 58
      src/client/js/components/SearchPage/SearchResult.js
  46. 2 3
      src/client/js/components/SearchPage/SearchResultList.js
  47. 14 17
      src/client/js/components/SearchTypeahead.js
  48. 16 6
      src/client/js/components/User/User.js
  49. 7 9
      src/client/js/components/User/UserPicture.js
  50. 7 9
      src/client/js/hackmd-agent.js
  51. 1 1
      src/client/js/hackmd-styles.js
  52. 2 2
      src/client/js/i18n.js
  53. 1 1
      src/client/js/installer.js
  54. 14 17
      src/client/js/legacy/crowi-admin.js
  55. 3 3
      src/client/js/legacy/crowi-presentation.js
  56. 118 114
      src/client/js/legacy/crowi.js
  57. 12 13
      src/client/js/models/MarkdownTable.js
  58. 1 4
      src/client/js/plugin.js
  59. 18 21
      src/client/js/util/Crowi.js
  60. 10 11
      src/client/js/util/GrowiRenderer.js
  61. 12 14
      src/client/js/util/PostProcessor/CrowiTemplate.js
  62. 0 1
      src/client/js/util/PreProcessor/CsvToTable.js
  63. 1 3
      src/client/js/util/PreProcessor/Linker.js
  64. 2 5
      src/client/js/util/PreProcessor/XssFilter.js
  65. 1 3
      src/client/js/util/codemirror/update-display-util.ext.js
  66. 8 10
      src/client/js/util/interceptor/detach-code-blocks.js
  67. 0 1
      src/client/js/util/markdown-it/blockdiag.js
  68. 2 4
      src/client/js/util/markdown-it/emoji.js
  69. 0 2
      src/client/js/util/markdown-it/footernote.js
  70. 2 4
      src/client/js/util/markdown-it/header-line-number.js
  71. 0 1
      src/client/js/util/markdown-it/header-with-edit-link.js
  72. 2 4
      src/client/js/util/markdown-it/header.js
  73. 1 3
      src/client/js/util/markdown-it/mathjax.js
  74. 0 1
      src/client/js/util/markdown-it/plantuml.js
  75. 1 2
      src/client/js/util/markdown-it/table-with-handsontable-button.js
  76. 0 2
      src/client/js/util/markdown-it/table.js
  77. 0 2
      src/client/js/util/markdown-it/task-lists.js
  78. 0 2
      src/client/js/util/markdown-it/toc-and-anchor.js
  79. 38 34
      src/client/js/util/reveal/plugins/growi-renderer.js
  80. 158 163
      src/client/js/util/reveal/plugins/markdown.js

+ 115 - 86
src/client/js/app.js

@@ -3,43 +3,45 @@ import ReactDOM from 'react-dom';
 import { I18nextProvider } from 'react-i18next';
 import * as toastr from 'toastr';
 
-import i18nFactory from './i18n';
-
 import loggerFactory from '@alias/logger';
 import Xss from '@commons/service/xss';
+import * as entities from 'entities';
+import i18nFactory from './i18n';
+
 
 import Crowi from './util/Crowi';
 // import CrowiRenderer from './util/CrowiRenderer';
 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 { EditorOptions, PreviewOptions } from './components/PageEditor/OptionsSelector';
+import HeaderSearchBox from './components/HeaderSearchBox';
+import SearchPage from './components/SearchPage';
+import PageEditor from './components/PageEditor';
+import OptionsSelector from './components/PageEditor/OptionsSelector'; // eslint-disable-line import/no-duplicates
+import { EditorOptions, PreviewOptions } from './components/PageEditor/OptionsSelector'; // eslint-disable-line import/no-duplicates
 import SavePageControls from './components/SavePageControls';
 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 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';
+// 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 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 CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import AdminRebuildSearch from './components/Admin/AdminRebuildSearch';
 
-import * as entities from 'entities';
+/* global location */
+/* eslint no-restricted-globals: ['error', 'locaion'] */
 
 const logger = loggerFactory('growi:app');
 
@@ -65,8 +67,8 @@ let pagePath;
 let pageContent = '';
 let markdown = '';
 let slackChannels;
-let currentPageTags = '';
-let newPageTags = '';
+// const currentPageTags = '';
+const newPageTags = '';
 if (mainContent !== null) {
   pageId = mainContent.getAttribute('data-page-id') || null;
   pageRevisionId = mainContent.getAttribute('data-page-revision-id');
@@ -100,8 +102,8 @@ const socketClientId = crowi.getSocketClientId();
 
 const crowiRenderer = new GrowiRenderer(crowi, null, {
   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;
 
@@ -116,14 +118,14 @@ if (isEnabledPlugins) {
  * get new tags from page tag form
  * @param {String} tags new tags [TODO] String -> Array
  */
-const getNewPageTags = function(tags) {
-  newPageTags = tags;
-};
+// const getNewPageTags = function(tags) {
+//   newPageTags = tags;
+// };
 
 /**
  * component store
  */
-let componentInstances = {};
+const componentInstances = {};
 
 /**
  * save success handler when reloading is not needed
@@ -207,7 +209,7 @@ const saveWithShortcut = function(markdown) {
     revisionId = componentInstances.pageEditorByHackmd.getRevisionIdHackmdSynced();
   }
 
-  let promise = undefined;
+  let promise;
   if (pageId == null) {
     promise = crowi.createPage(pagePath, markdown, options);
   }
@@ -241,7 +243,7 @@ const saveWithSubmitButton = function(submitOpts) {
   // set 'submitOpts.overwriteScopesOfDescendants' to options
   options.overwriteScopesOfDescendants = submitOpts ? !!submitOpts.overwriteScopesOfDescendants : false;
 
-  let promise = undefined;
+  let promise;
   if (editorMode === 'hackmd') {
     // get markdown
     promise = componentInstances.pageEditorByHackmd.getMarkdown();
@@ -256,12 +258,12 @@ const saveWithSubmitButton = function(submitOpts) {
   }
   // create or update
   if (pageId == null) {
-    promise = promise.then(markdown => {
+    promise = promise.then((markdown) => {
       return crowi.createPage(pagePath, markdown, options);
     });
   }
   else {
-    promise = promise.then(markdown => {
+    promise = promise.then((markdown) => {
       return crowi.updatePage(pageId, revisionId, markdown, options);
     });
   }
@@ -290,11 +292,11 @@ const componentMappings = {
   'search-sidebar': <HeaderSearchBox crowi={crowi} />,
   '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-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} />,
   'duplicate-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
 
@@ -305,7 +307,7 @@ if (pageId) {
   componentMappings['page-attachment'] = <PageAttachment pageId={pageId} markdown={markdown} crowi={crowi} />;
 }
 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['page-tag'] = <PageTagForm pageTags={currentPageTags} submitTags={getNewPageTags} />; [pagetag]
   componentMappings['revision-url'] = <RevisionUrl pageId={pageId} pagePath={pagePath} />;
@@ -319,8 +321,8 @@ Object.keys(componentMappings).forEach((key) => {
 });
 
 // set page if exists
-if (componentInstances['page'] != null) {
-  crowi.setPage(componentInstances['page']);
+if (componentInstances.page != null) {
+  crowi.setPage(componentInstances.page);
 }
 
 // render LikeButton
@@ -329,7 +331,7 @@ if (likeButtonElem) {
   const isLiked = likeButtonElem.dataset.liked === 'true';
   ReactDOM.render(
     <LikeButton crowi={crowi} pageId={pageId} isLiked={isLiked} />,
-    likeButtonElem
+    likeButtonElem,
   );
 }
 
@@ -340,7 +342,7 @@ if (seenUserListElem) {
   const userIds = userIdsStr.split(',');
   ReactDOM.render(
     <UserPictureList crowi={crowi} userIds={userIds} />,
-    seenUserListElem
+    seenUserListElem,
   );
 }
 // render UserPictureList for liker-list
@@ -350,7 +352,7 @@ if (likerListElem) {
   const userIds = userIdsStr.split(',');
   ReactDOM.render(
     <UserPictureList crowi={crowi} userIds={userIds} />,
-    likerListElem
+    likerListElem,
   );
 }
 
@@ -363,16 +365,23 @@ if (savePageControlsElem) {
   const grantGroupName = savePageControlsElem.dataset.grantGroupName;
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
-      <SavePageControls crowi={crowi} onSubmit={saveWithSubmitButton}
-          ref={(elem) => {
+      <SavePageControls
+        crowi={crowi}
+        onSubmit={saveWithSubmitButton}
+        ref={(elem) => {
             if (savePageControls == null) {
               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>,
-    savePageControlsElem
+    savePageControlsElem,
   );
   componentInstances.savePageControls = savePageControls;
 }
@@ -380,13 +389,11 @@ if (savePageControlsElem) {
 const recentCreatedControlsElem = document.getElementById('user-created-list');
 if (recentCreatedControlsElem) {
   let limit = crowi.getConfig().recentCreatedLimit;
-  if (null == limit) {
+  if (limit == null) {
     limit = 10;
   }
   ReactDOM.render(
-    <RecentCreated  crowi={crowi} pageId={pageId} limit={limit} >
-
-    </RecentCreated>, document.getElementById('user-created-list')
+    <RecentCreated crowi={crowi} pageId={pageId} limit={limit} />, document.getElementById('user-created-list'),
   );
 }
 
@@ -398,12 +405,17 @@ let pageEditorByHackmd = null;
 const pageEditorWithHackmdElem = document.getElementById('page-editor-with-hackmd');
 if (pageEditorWithHackmdElem) {
   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;
 }
@@ -419,12 +431,18 @@ const previewOptions = new PreviewOptions(crowi.previewOptions);
 const pageEditorElem = document.getElementById('page-editor');
 if (pageEditorElem) {
   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;
   // set refs for setCaretLine/forceToFocus when tab is changed
@@ -441,15 +459,18 @@ if (writeCommentElem) {
     }
   };
   ReactDOM.render(
-    <CommentForm crowi={crowi}
+    <CommentForm
+      crowi={crowi}
       crowiOriginRenderer={crowiRenderer}
       pageId={pageId}
       pagePath={pagePath}
       revisionId={pageRevisionId}
       onPostComplete={postCompleteHandler}
       editorOptions={editorOptions}
-      slackChannels = {slackChannels}/>,
-    writeCommentElem);
+      slackChannels={slackChannels}
+    />,
+    writeCommentElem,
+  );
 }
 
 // render OptionsSelector
@@ -457,8 +478,10 @@ const pageEditorOptionsSelectorElem = document.getElementById('page-editor-optio
 if (pageEditorOptionsSelectorElem) {
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
-      <OptionsSelector crowi={crowi}
-        editorOptions={editorOptions} previewOptions={previewOptions}
+      <OptionsSelector
+        crowi={crowi}
+        editorOptions={editorOptions}
+        previewOptions={previewOptions}
         onChange={(newEditorOptions, newPreviewOptions) => { // set onChange event handler
           // set options
           pageEditor.setEditorOptions(newEditorOptions);
@@ -466,9 +489,10 @@ if (pageEditorOptionsSelectorElem) {
           // save
           crowi.saveEditorOptions(newEditorOptions);
           crowi.savePreviewOptions(newPreviewOptions);
-        }} />
+        }}
+      />
     </I18nextProvider>,
-    pageEditorOptionsSelectorElem
+    pageEditorOptionsSelectorElem,
   );
 }
 
@@ -478,15 +502,19 @@ const pageStatusAlertElem = document.getElementById('page-status-alert');
 if (pageStatusAlertElem) {
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
-      <PageStatusAlert crowi={crowi}
-          ref={(elem) => {
+      <PageStatusAlert
+        crowi={crowi}
+        ref={(elem) => {
             if (pageStatusAlert == null) {
               pageStatusAlert = elem.getWrappedInstance();
             }
           }}
-          revisionId={pageRevisionId} revisionIdHackmdSynced={pageRevisionIdHackmdSynced} hasDraftOnHackmd={hasDraftOnHackmd} />
+        revisionId={pageRevisionId}
+        revisionIdHackmdSynced={pageRevisionIdHackmdSynced}
+        hasDraftOnHackmd={hasDraftOnHackmd}
+      />
     </I18nextProvider>,
-    pageStatusAlertElem
+    pageStatusAlertElem,
   );
   componentInstances.pageStatusAlert = pageStatusAlert;
 }
@@ -499,7 +527,7 @@ if (customCssEditorElem != null) {
 
   ReactDOM.render(
     <CustomCssEditor inputElem={customCssInputElem} />,
-    customCssEditorElem
+    customCssEditorElem,
   );
 }
 const customScriptEditorElem = document.getElementById('custom-script-editor');
@@ -509,7 +537,7 @@ if (customScriptEditorElem != null) {
 
   ReactDOM.render(
     <CustomScriptEditor inputElem={customScriptInputElem} />,
-    customScriptEditorElem
+    customScriptEditorElem,
   );
 }
 const customHeaderEditorElem = document.getElementById('custom-header-editor');
@@ -519,14 +547,14 @@ if (customHeaderEditorElem != null) {
 
   ReactDOM.render(
     <CustomHeaderEditor inputElem={customHeaderInputElem} />,
-    customHeaderEditorElem
+    customHeaderEditorElem,
   );
 }
 const adminRebuildSearchElem = document.getElementById('admin-rebuild-search');
 if (adminRebuildSearchElem != null) {
   ReactDOM.render(
     <AdminRebuildSearch crowi={crowi} />,
-    adminRebuildSearchElem
+    adminRebuildSearchElem,
   );
 }
 
@@ -540,7 +568,7 @@ function updatePageStatusAlert(page, user) {
     pageStatusAlert.setLastUpdateUsername(user.name);
   }
 }
-socket.on('page:create', function(data) {
+socket.on('page:create', (data) => {
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
@@ -549,11 +577,11 @@ socket.on('page:create', function(data) {
   logger.debug({ obj: data }, `websocket on 'page:create'`); // eslint-disable-line quotes
 
   // update PageStatusAlert
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     updatePageStatusAlert(data.page, data.user);
   }
 });
-socket.on('page:update', function(data) {
+socket.on('page:update', (data) => {
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
@@ -561,7 +589,7 @@ socket.on('page:update', function(data) {
 
   logger.debug({ obj: data }, `websocket on 'page:update'`); // eslint-disable-line quotes
 
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     // update PageStatusAlert
     updatePageStatusAlert(data.page, data.user);
     // update PageEditorByHackmd
@@ -573,7 +601,7 @@ socket.on('page:update', function(data) {
     }
   }
 });
-socket.on('page:delete', function(data) {
+socket.on('page:delete', (data) => {
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
@@ -582,11 +610,11 @@ socket.on('page:delete', function(data) {
   logger.debug({ obj: data }, `websocket on 'page:delete'`); // eslint-disable-line quotes
 
   // update PageStatusAlert
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     updatePageStatusAlert(data.page, data.user);
   }
 });
-socket.on('page:editingWithHackmd', function(data) {
+socket.on('page:editingWithHackmd', (data) => {
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
@@ -594,7 +622,7 @@ socket.on('page:editingWithHackmd', function(data) {
 
   logger.debug({ obj: data }, `websocket on 'page:editingWithHackmd'`); // eslint-disable-line quotes
 
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     // update PageStatusAlert
     const pageStatusAlert = componentInstances.pageStatusAlert;
     if (pageStatusAlert != null) {
@@ -609,9 +637,10 @@ socket.on('page:editingWithHackmd', function(data) {
 });
 
 // うわーもうー (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(
     <I18nextProvider i18n={i18n}>
       <PageHistory pageId={pageId} crowi={crowi} />
-    </I18nextProvider>, document.getElementById('revision-history'));
+    </I18nextProvider>, document.getElementById('revision-history'),
+  );
 });

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

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

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

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

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

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

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

@@ -9,7 +9,6 @@ import dateFnsFormat from 'date-fns/format';
  * display date depends on user timezone of user settings
  */
 export default class UserDate extends React.Component {
-
   render() {
     const dt = dateFnsFormat(this.props.dateTime, this.props.format);
 
@@ -32,4 +31,3 @@ UserDate.defaultProps = {
   format: 'YYYY/MM/DD HH:mm:ss',
   className: '',
 };
-

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

@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
 import ClipboardButton from 'react-clipboard.js';
 
 export default class CopyButton extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -23,7 +22,7 @@ export default class CopyButton extends React.Component {
 
   render() {
     const containerStyle = {
-      lineHeight: 0
+      lineHeight: 0,
     };
     const style = Object.assign({
       padding: '0 2px',
@@ -34,12 +33,20 @@ export default class CopyButton extends React.Component {
 
     return (
       <span className="btn-copy-container" style={containerStyle}>
-        <ClipboardButton className={this.props.buttonClassName}
-            button-id={this.props.buttonId} button-data-toggle="tooltip" button-data-container="body" button-title="copied!" button-data-placement="bottom" button-data-trigger="manual"
-            button-style={style}
-            data-clipboard-text={text} onSuccess={this.showToolTip}>
+        <ClipboardButton
+          className={this.props.buttonClassName}
+          button-id={this.props.buttonId}
+          button-data-toggle="tooltip"
+          button-data-container="body"
+          button-title="copied!"
+          button-data-placement="bottom"
+          button-data-trigger="manual"
+          button-style={style}
+          data-clipboard-text={text}
+          onSuccess={this.showToolTip}
+        >
 
-          <i className={this.props.iconClassName}></i>
+          <i className={this.props.iconClassName} />
         </ClipboardButton>
       </span>
     );

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

@@ -2,7 +2,6 @@ import React from 'react';
 import PropTypes from 'prop-types';
 
 export default class PagePath extends React.Component {
-
   linkPath(path) {
     return path;
   }
@@ -10,11 +9,12 @@ export default class PagePath extends React.Component {
   render() {
     const page = this.props.page;
     const shortPath = this.getShortPath(page.path);
-    const pathPrefix = page.path.replace(new RegExp(shortPath + '(/)?$'), '');
+    const pathPrefix = page.path.replace(new RegExp(`${shortPath}(/)?$`), '');
 
     return (
       <span className="page-path">
-        {pathPrefix}<strong>{shortPath}</strong>
+        {pathPrefix}
+        <strong>{shortPath}</strong>
       </span>
     );
   }

+ 5 - 5
src/client/js/components/Page/RevisionBody.js

@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
 import { debounce } from 'throttle-debounce';
 
 export default class RevisionBody extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -39,7 +38,7 @@ export default class RevisionBody extends React.Component {
   }
 
   generateInnerHtml(html) {
-    return {__html: html};
+    return { __html: html };
   }
 
   render() {
@@ -52,15 +51,16 @@ export default class RevisionBody extends React.Component {
             this.props.inputRef(elm);
           }
         }}
-        className={`wiki ${additionalClassName}`} dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}>
-      </div>
+        className={`wiki ${additionalClassName}`}
+        dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}
+      />
     );
   }
 }
 
 RevisionBody.propTypes = {
   html: PropTypes.string,
-  inputRef: PropTypes.func,  // for getting div element
+  inputRef: PropTypes.func, // for getting div element
   isMathJaxEnabled: PropTypes.bool,
   renderMathJaxOnInit: PropTypes.bool,
   renderMathJaxInRealtime: PropTypes.bool,

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

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

+ 9 - 5
src/client/js/components/Page/RevisionUrl.js

@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
 import CopyButton from '../CopyButton';
 
 export default class RevisionUrl extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -14,7 +13,7 @@ export default class RevisionUrl extends React.Component {
 
   render() {
     const buttonStyle = {
-      fontSize: '1em'
+      fontSize: '1em',
     };
 
     const pagePath = this.xss.process(this.props.pagePath);
@@ -22,13 +21,18 @@ export default class RevisionUrl extends React.Component {
     const url = (this.props.pageId == null)
       ? decodeURIComponent(location.href)
       : `${location.origin}/${this.props.pageId}`;
-    const copiedText = pagePath + '\n' + url;
+    const copiedText = `${pagePath}\n${url}`;
 
     return (
       <span>
         {url}
-        <CopyButton buttonId="btnCopyRevisionUrl" text={copiedText}
-            buttonClassName="btn btn-default btn-copy-link" buttonStyle={buttonStyle} iconClassName="ti-clipboard" />
+        <CopyButton
+          buttonId="btnCopyRevisionUrl"
+          text={copiedText}
+          buttonClassName="btn btn-default btn-copy-link"
+          buttonStyle={buttonStyle}
+          iconClassName="ti-clipboard"
+        />
       </span>
     );
   }

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

@@ -24,21 +24,21 @@ export default class PageAttachment extends React.Component {
     const pageId = this.props.pageId;
 
     if (!pageId) {
-      return ;
+      return;
     }
 
-    this.props.crowi.apiGet('/attachments.list', {page_id: pageId })
-      .then(res => {
+    this.props.crowi.apiGet('/attachments.list', { page_id: pageId })
+      .then((res) => {
         const attachments = res.attachments;
-        let inUse = {};
+        const inUse = {};
 
         for (const attachment of attachments) {
           inUse[attachment._id] = this.checkIfFileInUse(attachment);
         }
 
         this.setState({
-          attachments: attachments,
-          inUse: inUse,
+          attachments,
+          inUse,
         });
       });
   }
@@ -62,8 +62,8 @@ export default class PageAttachment extends React.Component {
       deleting: true,
     });
 
-    this.props.crowi.apiPost('/attachments.remove', {attachment_id: attachmentId})
-      .then(res => {
+    this.props.crowi.apiPost('/attachments.remove', { attachment_id: attachmentId })
+      .then((res) => {
         this.setState({
           attachments: this.state.attachments.filter((at) => {
             return at._id != attachmentId;
@@ -71,7 +71,7 @@ export default class PageAttachment extends React.Component {
           attachmentToDelete: null,
           deleting: false,
         });
-      }).catch(err => {
+      }).catch((err) => {
         this.setState({
           deleteError: 'Something went wrong.',
           deleting: false,
@@ -87,10 +87,10 @@ export default class PageAttachment extends React.Component {
     let deleteAttachmentModal = '';
     if (this.isUserLoggedIn()) {
       const attachmentToDelete = this.state.attachmentToDelete;
-      let deleteModalClose = () => {
+      const deleteModalClose = () => {
         this.setState({ attachmentToDelete: null, deleteError: '' });
       };
-      let showModal = attachmentToDelete !== null;
+      const showModal = attachmentToDelete !== null;
 
       let deleteInUse = null;
       if (attachmentToDelete !== null) {

+ 12 - 6
src/client/js/components/PageAttachment/Attachment.js

@@ -36,22 +36,28 @@ export default class Attachment extends React.Component {
     const btnDownload = (this.props.isUserLoggedIn)
       ? (
         <a className="attachment-download" href={attachment.downloadPathProxied}>
-          <i className="icon-cloud-download"></i>
-        </a>)
+          <i className="icon-cloud-download" />
+        </a>
+      )
       : '';
 
     const btnTrash = (this.props.isUserLoggedIn)
       ? (
         <a className="text-danger attachment-delete" onClick={this._onAttachmentDeleteClicked}>
-          <i className="icon-trash"></i>
-        </a>)
+          <i className="icon-trash" />
+        </a>
+      )
       : '';
 
     return (
-      <li className='attachment'>
+      <li className="attachment">
         <User user={attachment.creator} />
 
-        <a href={attachment.filePathProxied}><i className={formatIcon}></i> {attachment.originalName}</a>
+        <a href={attachment.filePathProxied}>
+          <i className={formatIcon} />
+          {' '}
+          {attachment.originalName}
+        </a>
 
         {fileType}
 

+ 15 - 7
src/client/js/components/PageAttachment/DeleteAttachmentModal.js

@@ -32,10 +32,14 @@ export default class DeleteAttachmentModal extends React.Component {
     return (
       <div className="attachment-delete-image">
         <p>
-          <i className={this.iconNameByFormat(attachment.fileFormat)}></i> {attachment.originalName}
+          <i className={this.iconNameByFormat(attachment.fileFormat)} />
+          {' '}
+          {attachment.originalName}
         </p>
         <p>
-          uploaded by <User user={attachment.creator} username />
+          uploaded by
+          {' '}
+          <User user={attachment.creator} username />
         </p>
         {content}
       </div>
@@ -57,13 +61,13 @@ export default class DeleteAttachmentModal extends React.Component {
 
     let deletingIndicator = '';
     if (this.props.deleting) {
-      deletingIndicator = <div className="speeding-wheel-sm"></div>;
+      deletingIndicator = <div className="speeding-wheel-sm" />;
     }
     if (this.props.deleteError) {
       deletingIndicator = <span>{this.props.deleteError}</span>;
     }
 
-    let renderAttachment = this.renderByFileFormat(attachment);
+    const renderAttachment = this.renderByFileFormat(attachment);
 
     return (
       <Modal {...props} className="attachment-delete-modal" bsSize="large" aria-labelledby="contained-modal-title-lg">
@@ -77,11 +81,15 @@ export default class DeleteAttachmentModal extends React.Component {
           <div className="mr-3 d-inline-block">
             {deletingIndicator}
           </div>
-          <Button onClick={this._onDeleteConfirm} bsStyle="danger"
-            disabled={this.props.deleting}>Delete!</Button>
+          <Button
+            onClick={this._onDeleteConfirm}
+            bsStyle="danger"
+            disabled={this.props.deleting}
+          >
+Delete!
+          </Button>
         </Modal.Footer>
       </Modal>
     );
   }
 }
-

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

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

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

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

+ 6 - 5
src/client/js/components/PageComment/CommentPreview.js

@@ -7,18 +7,19 @@ import RevisionBody from '../Page/RevisionBody';
  * Wrapper component for Page/RevisionBody
  */
 export default class CommentPreview extends React.Component {
-
   constructor(props) {
     super(props);
   }
 
   render() {
     return (
-      <div className="page-comment-preview-body"
-          ref={(elm) => {
+      <div
+        className="page-comment-preview-body"
+        ref={(elm) => {
             this.previewElement = elm;
             this.props.inputRef(elm);
-          }}>
+          }}
+      >
 
         <RevisionBody
           {...this.props}
@@ -31,5 +32,5 @@ export default class CommentPreview extends React.Component {
 
 CommentPreview.propTypes = {
   html: PropTypes.string,
-  inputRef: PropTypes.func.isRequired,  // for getting div element
+  inputRef: PropTypes.func.isRequired, // for getting div element
 };

+ 15 - 9
src/client/js/components/PageComment/DeleteCommentModal.js

@@ -10,7 +10,6 @@ import ReactUtils from '../ReactUtils';
 import UserPicture from '../User/UserPicture';
 
 export default class DeleteCommentModal extends React.Component {
-
   /*
    * the threshold for omitting body
    */
@@ -25,7 +24,7 @@ export default class DeleteCommentModal extends React.Component {
 
   render() {
     if (this.props.comment === undefined) {
-      return <div></div>;
+      return <div />;
     }
 
     const comment = this.props.comment;
@@ -34,7 +33,7 @@ export default class DeleteCommentModal extends React.Component {
     // generate body
     let commentBody = comment.comment;
     if (commentBody.length > DeleteCommentModal.OMIT_BODY_THRES) { // omit
-      commentBody = commentBody.substr(0, DeleteCommentModal.OMIT_BODY_THRES) + '...';
+      commentBody = `${commentBody.substr(0, DeleteCommentModal.OMIT_BODY_THRES)}...`;
     }
     commentBody = ReactUtils.nl2br(commentBody);
 
@@ -42,32 +41,39 @@ export default class DeleteCommentModal extends React.Component {
       <Modal show={this.props.isShown} onHide={this.props.cancel} className="page-comment-delete-modal">
         <Modal.Header closeButton>
           <Modal.Title>
-            <i className="icon-fw icon-fire text-danger"></i>
+            <i className="icon-fw icon-fire text-danger" />
             Delete comment?
           </Modal.Title>
         </Modal.Header>
         <Modal.Body>
-          <UserPicture user={comment.creator} size="xs" /> <strong>{comment.creator.username}</strong> wrote on {commentDate}:
+          <UserPicture user={comment.creator} size="xs" />
+          {' '}
+          <strong>{comment.creator.username}</strong>
+          {' '}
+wrote on
+          {' '}
+          {commentDate}
+:
           <p className="well well-sm comment-body m-t-5">{commentBody}</p>
         </Modal.Body>
         <Modal.Footer>
-          <span className="text-danger">{this.props.errorMessage}</span>&nbsp;
+          <span className="text-danger">{this.props.errorMessage}</span>
+&nbsp;
           <Button onClick={this.props.cancel} bsClass="btn btn-sm">Cancel</Button>
           <Button onClick={this.props.confirmedToDelete} bsClass="btn btn-sm btn-danger">
-            <i className="icon icon-fire"></i>
+            <i className="icon icon-fire" />
             Delete
           </Button>
         </Modal.Footer>
       </Modal>
     );
   }
-
 }
 
 DeleteCommentModal.propTypes = {
   isShown: PropTypes.bool.isRequired,
   comment: PropTypes.object,
   errorMessage: PropTypes.string,
-  cancel: PropTypes.func.isRequired,            // for cancel evnet handling
+  cancel: PropTypes.func.isRequired, // for cancel evnet handling
   confirmedToDelete: PropTypes.func.isRequired, // for confirmed event handling
 };

+ 38 - 30
src/client/js/components/PageComments.js

@@ -16,7 +16,6 @@ import DeleteCommentModal from './PageComment/DeleteCommentModal';
  * @extends {React.Component}
  */
 export default class PageComments extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -32,7 +31,7 @@ export default class PageComments extends React.Component {
       errorMessageForDeleting: undefined,
     };
 
-    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, {mode: 'comment'});
+    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, { mode: 'comment' });
 
     this.init = this.init.bind(this);
     this.confirmToDeleteComment = this.confirmToDeleteComment.bind(this);
@@ -51,8 +50,8 @@ export default class PageComments extends React.Component {
       return;
     }
 
-    const layoutType = this.props.crowi.getConfig()['layoutType'];
-    this.setState({isLayoutTypeGrowi: 'crowi-plus' === layoutType || 'growi' === layoutType});
+    const layoutType = this.props.crowi.getConfig().layoutType;
+    this.setState({ isLayoutTypeGrowi: layoutType === 'crowi-plus' || layoutType === 'growi' });
 
     this.retrieveData();
   }
@@ -62,35 +61,35 @@ export default class PageComments extends React.Component {
    */
   retrieveData() {
     // get data (desc order array)
-    this.props.crowi.apiGet('/comments.get', {page_id: this.props.pageId})
-      .then(res => {
+    this.props.crowi.apiGet('/comments.get', { page_id: this.props.pageId })
+      .then((res) => {
         if (res.ok) {
-          this.setState({comments: res.comments});
+          this.setState({ comments: res.comments });
         }
       });
   }
 
   confirmToDeleteComment(comment) {
-    this.setState({commentToDelete: comment});
+    this.setState({ commentToDelete: comment });
     this.showDeleteConfirmModal();
   }
 
   deleteComment() {
     const comment = this.state.commentToDelete;
 
-    this.props.crowi.apiPost('/comments.remove', {comment_id: comment._id})
-    .then(res => {
+    this.props.crowi.apiPost('/comments.remove', { comment_id: comment._id })
+    .then((res) => {
       if (res.ok) {
         this.findAndSplice(comment);
       }
       this.closeDeleteConfirmModal();
-    }).catch(err => {
-      this.setState({errorMessageForDeleting: err.message});
+    }).catch((err) => {
+      this.setState({ errorMessageForDeleting: err.message });
     });
   }
 
   findAndSplice(comment) {
-    let comments = this.state.comments;
+    const comments = this.state.comments;
 
     const index = comments.indexOf(comment);
     if (index < 0) {
@@ -98,11 +97,11 @@ export default class PageComments extends React.Component {
     }
     comments.splice(index, 1);
 
-    this.setState({comments});
+    this.setState({ comments });
   }
 
   showDeleteConfirmModal() {
-    this.setState({isDeleteConfirmModalShown: true});
+    this.setState({ isDeleteConfirmModalShown: true });
   }
 
   closeDeleteConfirmModal() {
@@ -123,25 +122,28 @@ export default class PageComments extends React.Component {
   generateCommentElements(comments) {
     return comments.map((comment) => {
       return (
-        <Comment key={comment._id} comment={comment}
+        <Comment
+          key={comment._id}
+          comment={comment}
           currentUserId={this.props.crowi.me}
           currentRevisionId={this.props.revisionId}
           deleteBtnClicked={this.confirmToDeleteComment}
           crowi={this.props.crowi}
-          crowiRenderer={this.growiRenderer} />
+          crowiRenderer={this.growiRenderer}
+        />
       );
     });
   }
 
   render() {
-    let currentComments = [];
-    let newerComments = [];
-    let olderComments = [];
+    const currentComments = [];
+    const newerComments = [];
+    const olderComments = [];
 
     let comments = this.state.comments;
     if (this.state.isLayoutTypeGrowi) {
       // replace with asc order array
-      comments = comments.slice().reverse();  // non-destructive reverse
+      comments = comments.slice().reverse(); // non-destructive reverse
     }
 
     // divide by revisionId and createdAt
@@ -151,7 +153,7 @@ export default class PageComments extends React.Component {
       if (comment.revision == revisionId) {
         currentComments.push(comment);
       }
-      else if (Date.parse(comment.createdAt)/1000 > revisionCreatedAt) {
+      else if (Date.parse(comment.createdAt) / 1000 > revisionCreatedAt) {
         newerComments.push(comment);
       }
       else {
@@ -182,23 +184,29 @@ export default class PageComments extends React.Component {
 
     // generate toggle elements
     const iconForNewer = (this.state.isLayoutTypeGrowi)
-      ? <i className="fa fa-angle-double-down"></i>
-      : <i className="fa fa-angle-double-up"></i>;
+      ? <i className="fa fa-angle-double-down" />
+      : <i className="fa fa-angle-double-up" />;
     const toggleNewer = (newerElements.length === 0)
-      ? <div></div>
+      ? <div />
       : (
         <a className="page-comments-list-toggle-newer text-center" data-toggle="collapse" href="#page-comments-list-newer">
-          {iconForNewer} Comments for Newer Revision {iconForNewer}
+          {iconForNewer}
+          {' '}
+Comments for Newer Revision
+          {iconForNewer}
         </a>
       );
     const iconForOlder = (this.state.isLayoutTypeGrowi)
-      ? <i className="fa fa-angle-double-up"></i>
-      : <i className="fa fa-angle-double-down"></i>;
+      ? <i className="fa fa-angle-double-up" />
+      : <i className="fa fa-angle-double-down" />;
     const toggleOlder = (olderElements.length === 0)
-      ? <div></div>
+      ? <div />
       : (
         <a className="page-comments-list-toggle-older text-center" data-toggle="collapse" href="#page-comments-list-older">
-          {iconForOlder} Comments for Older Revision {iconForOlder}
+          {iconForOlder}
+          {' '}
+Comments for Older Revision
+          {iconForOlder}
         </a>
       );
 

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

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

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

@@ -2,7 +2,6 @@ import React from 'react';
 import PropTypes from 'prop-types';
 
 export default class AbstractEditor extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -133,4 +132,3 @@ AbstractEditor.propTypes = {
 AbstractEditor.defaultProps = {
   isGfmMode: true,
 };
-

+ 123 - 25
src/client/js/components/PageEditor/Cheatsheet.js

@@ -11,29 +11,74 @@ class Cheatsheet extends React.Component {
         <div className="col-sm-6">
           <h4>{t('sandbox.header')}</h4>
           <ul className="hljs">
-            <li><code># </code>{t('sandbox.header_x', {index: '1'})}</li>
-            <li><code>## </code>{t('sandbox.header_x', {index: '2'})}</li>
-            <li><code>### </code>{t('sandbox.header_x', {index: '3'})}</li>
+            <li>
+              <code># </code>
+              {t('sandbox.header_x', { index: '1' })}
+            </li>
+            <li>
+              <code>## </code>
+              {t('sandbox.header_x', { index: '2' })}
+            </li>
+            <li>
+              <code>### </code>
+              {t('sandbox.header_x', { index: '3' })}
+            </li>
           </ul>
           <h4>{t('sandbox.block')}</h4>
-          <p className="mb-1"><code>[{t('sandbox.empty_line')}]</code>{t('sandbox.block_detail')}</p>
+          <p className="mb-1">
+            <code>
+[
+              {t('sandbox.empty_line')}
+]
+            </code>
+            {t('sandbox.block_detail')}
+          </p>
           <ul className="hljs">
             <li>text</li>
-            <li></li>
+            <li />
             <li>text</li>
           </ul>
           <h4>{t('sandbox.line_break')}</h4>
-          <p className="mb-1"><code>[ ][ ]</code> {t('sandbox.line_break_detail')}</p>
+          <p className="mb-1">
+            <code>[ ][ ]</code>
+            {' '}
+            {t('sandbox.line_break_detail')}
+          </p>
           <ul className="hljs">
             <li>text</li>
             <li>text</li>
           </ul>
           <h4>{t('sandbox.typography')}</h4>
           <ul className="hljs">
-            <li><i>*{t('sandbox.italics')}*</i></li>
-            <li><b>**{t('sandbox.bold')}**</b></li>
-            <li><i><b>***{t('sandbox.italic_bold')}***</b></i></li>
-            <li>~~{t('sandbox.strikethrough')}~~ => <s>{t('sandbox.strikethrough')}</s></li>
+            <li>
+              <i>
+*
+                {t('sandbox.italics')}
+*
+              </i>
+            </li>
+            <li>
+              <b>
+**
+                {t('sandbox.bold')}
+**
+              </b>
+            </li>
+            <li>
+              <i>
+                <b>
+***
+                  {t('sandbox.italic_bold')}
+***
+                </b>
+              </i>
+            </li>
+            <li>
+~~
+              {t('sandbox.strikethrough')}
+~~ =>
+              <s>{t('sandbox.strikethrough')}</s>
+            </li>
           </ul>
           <h4>{t('sandbox.link')}</h4>
           <ul className="hljs">
@@ -50,27 +95,69 @@ class Cheatsheet extends React.Component {
         <div className="col-sm-6">
           <h4>{t('sandbox.list')}</h4>
           <ul className="hljs">
-            <li>- {t('sandbox.unordered_list_x', {index: '1'})}</li>
-            <li>&nbsp;&nbsp;- {t('sandbox.unordered_list_x', {index: '1.1'})}</li>
-            <li>- {t('sandbox.unordered_list_x', {index: '2'})}</li>
+            <li>
+-
+              {t('sandbox.unordered_list_x', { index: '1' })}
+            </li>
+            <li>
+&nbsp;&nbsp;-
+              {t('sandbox.unordered_list_x', { index: '1.1' })}
+            </li>
+            <li>
+-
+              {t('sandbox.unordered_list_x', { index: '2' })}
+            </li>
           </ul>
           <ul className="hljs">
-            <li>1. {t('sandbox.ordered_list_x', {index: '1'})}</li>
-            <li>1. {t('sandbox.ordered_list_x', {index: '2'})}</li>
+            <li>
+1.
+              {t('sandbox.ordered_list_x', { index: '1' })}
+            </li>
+            <li>
+1.
+              {t('sandbox.ordered_list_x', { index: '2' })}
+            </li>
           </ul>
           <ul className="hljs">
-            <li>- [ ] {t('sandbox.task')}({t('sandbox.task_unchecked')})</li>
-            <li>- [x] {t('sandbox.task')}({t('sandbox.task_checked')})</li>
+            <li>
+- [ ]
+              {t('sandbox.task')}
+(
+              {t('sandbox.task_unchecked')}
+)
+            </li>
+            <li>
+- [x]
+              {t('sandbox.task')}
+(
+              {t('sandbox.task_checked')}
+)
+            </li>
           </ul>
           <h4>{t('sandbox.quote')}</h4>
           <ul className="hljs">
-            <li>> {t('sandbox.quote1')}</li>
-            <li>> {t('sandbox.quote2')}</li>
+            <li>
+>
+              {t('sandbox.quote1')}
+            </li>
+            <li>
+>
+              {t('sandbox.quote2')}
+            </li>
           </ul>
           <ul className="hljs">
-            <li>>> {t('sandbox.quote_nested')}</li>
-            <li>>>> {t('sandbox.quote_nested')}</li>
-            <li>>>>> {t('sandbox.quote_nested')}</li>
+            <li>
+>>
+              {t('sandbox.quote_nested')}
+            </li>
+            <li>
+>>>
+              {t('sandbox.quote_nested')}
+            </li>
+            <li>
+>>>>
+              {t('sandbox.quote_nested')}
+            </li>
           </ul>
           <h4>{t('sandbox.table')}</h4>
           <ul className="hljs text-center">
@@ -80,14 +167,25 @@ class Cheatsheet extends React.Component {
             <li>|col 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;col 2&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;col 3|</li>
           </ul>
           <h4>{t('sandbox.image')}</h4>
-          <p className="mb-1"><code> ![{t('sandbox.alt_text')}](URL)</code> {t('sandbox.insert_image')}</p>
+          <p className="mb-1">
+            <code>
+              {' '}
+![
+              {t('sandbox.alt_text')}
+](URL)
+            </code>
+            {' '}
+            {t('sandbox.insert_image')}
+          </p>
           <ul className="hljs">
             <li>![ex](https://example.com/image.png)</li>
           </ul>
 
           <hr />
           <a href="/Sandbox" className="btn btn-info btn-block" target="_blank">
-            <i className="icon-share-alt"/> {t('sandbox.open_sandbox')}
+            <i className="icon-share-alt" />
+            {' '}
+            {t('sandbox.open_sandbox')}
           </a>
         </div>
       </div>
@@ -96,7 +194,7 @@ class Cheatsheet extends React.Component {
 }
 
 Cheatsheet.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
 };
 
 export default translate()(Cheatsheet);

+ 171 - 110
src/client/js/components/PageEditor/CodeMirrorEditor.js

@@ -7,10 +7,27 @@ import Button from 'react-bootstrap/es/Button';
 import InterceptorManager from '@commons/service/interceptor-manager';
 
 import urljoin from 'url-join';
-const loadScript = require('simple-load-script');
-const loadCssSync = require('load-css-file');
 
 import * as codemirror from 'codemirror';
+
+
+import { UnControlled as ReactCodeMirror } from 'react-codemirror2';
+
+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 loadCssSync = require('load-css-file');
 // set save handler
 codemirror.commands.save = (instance) => {
   if (instance.codeMirrorEditor != null) {
@@ -19,9 +36,6 @@ codemirror.commands.save = (instance) => {
 };
 // set CodeMirror instance as 'CodeMirror' so that CDN addons can reference
 window.CodeMirror = require('codemirror');
-
-
-import { UnControlled as ReactCodeMirror } from 'react-codemirror2';
 require('codemirror/addon/display/placeholder');
 require('codemirror/addon/edit/matchbrackets');
 require('codemirror/addon/edit/matchtags');
@@ -42,21 +56,7 @@ require('codemirror/addon/display/placeholder');
 require('codemirror/mode/gfm/gfm');
 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 {
-
   constructor(props) {
     super(props);
     this.logger = require('@alias/logger')('growi:PageEditor:CodeMirrorEditor');
@@ -110,14 +110,14 @@ export default class CodeMirrorEditor extends AbstractEditor {
       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();
   }
 
   componentWillMount() {
     if (this.props.emojiStrategy != null) {
       this.emojiAutoCompleteHelper = new EmojiAutoCompleteHelper(this.props.emojiStrategy);
-      this.setState({isEnabledEmojiAutoComplete: true});
+      this.setState({ isEnabledEmojiAutoComplete: true });
     }
   }
 
@@ -192,7 +192,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
     const editor = this.getCodeMirror();
     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);
   }
 
@@ -206,7 +206,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
     const editor = this.getCodeMirror();
     // 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);
   }
 
@@ -331,8 +331,8 @@ export default class CodeMirrorEditor extends AbstractEditor {
    */
   loadKeymapMode(keymapMode) {
     const loadCss = this.loadCss;
-    let scriptList = [];
-    let cssList = [];
+    const scriptList = [];
+    const cssList = [];
 
     // add dependencies
     if (this.loadedKeymapSet.size == 0) {
@@ -394,7 +394,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
     }
 
     const context = {
-      handlers: [],  // list of handlers which process enter key
+      handlers: [], // list of handlers which process enter key
       editor: this,
     };
 
@@ -432,14 +432,12 @@ export default class CodeMirrorEditor extends AbstractEditor {
     if (mtu.isEndOfLine(editor) && mtu.linePartOfTableRE.test(strFromBol)) {
       if (!hasCustomClass) {
         additionalClassSet.add(autoformatTableClass);
-        this.setState({additionalClassSet});
+        this.setState({ additionalClassSet });
       }
     }
-    else {
-      if (hasCustomClass) {
-        additionalClassSet.delete(autoformatTableClass);
-        this.setState({additionalClassSet});
-      }
+    else if (hasCustomClass) {
+      additionalClassSet.delete(autoformatTableClass);
+      this.setState({ additionalClassSet });
     }
   }
 
@@ -474,7 +472,6 @@ export default class CodeMirrorEditor extends AbstractEditor {
     else if (types.includes('text/plain')) {
       pasteHelper.pasteText(this, event);
     }
-
   }
 
   /**
@@ -505,11 +502,15 @@ export default class CodeMirrorEditor extends AbstractEditor {
     };
 
     return this.state.isLoadingKeymap
-      ? <div className="overlay overlay-loading-keymap">
+      ? (
+        <div className="overlay overlay-loading-keymap">
           <span style={style} className="overlay-content">
-            <div className="speeding-wheel d-inline-block"></div> Loading Keymap ...
+            <div className="speeding-wheel d-inline-block" />
+            {' '}
+Loading Keymap ...
           </span>
         </div>
+      )
       : '';
   }
 
@@ -523,18 +524,21 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
   renderCheatsheetModalButton() {
     const showCheatsheetModal = () => {
-      this.setState({isCheatsheetModalShown: true});
+      this.setState({ isCheatsheetModalShown: true });
     };
 
     const hideCheatsheetModal = () => {
-      this.setState({isCheatsheetModalShown: false});
+      this.setState({ isCheatsheetModalShown: false });
     };
 
     return (
       <React.Fragment>
         <Modal className="modal-gfm-cheatsheet" show={this.state.isCheatsheetModalShown} onHide={() => { hideCheatsheetModal() }}>
           <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.Body className="pt-1">
             { this.renderCheatsheetModalBody() }
@@ -542,7 +546,9 @@ export default class CodeMirrorEditor extends AbstractEditor {
         </Modal>
 
         <a className="gfm-cheatsheet-modal-link text-muted small" onClick={() => { showCheatsheetModal() }}>
-          <i className="icon-question" /> Markdown
+          <i className="icon-question" />
+          {' '}
+Markdown
         </a>
       </React.Fragment>
     );
@@ -590,8 +596,8 @@ export default class CodeMirrorEditor extends AbstractEditor {
       for (let i = startLineNum; i <= endLineNum; 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.focus();
@@ -611,7 +617,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
     if (!line.startsWith('#')) {
       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();
   }
 
@@ -621,59 +627,113 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
   getNavbarItems() {
     // 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 [
-      <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" />
       </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" />
       </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" />
       </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" />
       </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" />
       </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" />
       </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} />
       </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} />
       </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" />
       </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" />
       </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" />
       </Button>,
-      <Button key='nav-item-table' bsSize="small" title={'Table'}
-              onClick={ this.showHandsonTableHandler }>
+      <Button
+        key="nav-item-table"
+        bsSize="small"
+        title="Table"
+        onClick={this.showHandsonTableHandler}
+      >
         <img src="/images/icons/editor/table.svg" width="14" height="14" />
-      </Button>
+      </Button>,
     ];
   }
 
@@ -688,50 +748,51 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
     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="cm"
+          className={additionalClasses}
+          placeholder="search"
+          editorDidMount={(editor) => {
           // add event handlers
           editor.on('paste', this.pasteHandler);
           editor.on('scrollCursorIntoView', this.scrollCursorIntoViewHandler);
         }}
-        value={this.state.value}
-        options={{
-          mode: mode,
+          value={this.state.value}
+          options={{
+          mode,
           theme: editorOptions.theme,
           styleActiveLine: editorOptions.styleActiveLine,
           lineNumbers: this.props.lineNumbers,
           tabSize: 4,
           indentUnit: 4,
           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,
-          placeholder: placeholder,
+          placeholder,
           matchBrackets: true,
-          matchTags: {bothTags: true},
+          matchTags: { bothTags: true },
           // folding
           foldGutter: this.props.lineNumbers,
           gutters: this.props.lineNumbers ? ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'] : [],
           // match-highlighter, matchesonscrollbar, annotatescrollbar options
-          highlightSelectionMatches: {annotateScrollbar: true},
+          highlightSelectionMatches: { annotateScrollbar: true },
           // markdown mode options
           highlightFormatting: true,
           // continuelist, indentlist
           extraKeys: {
-            'Enter': this.handleEnterKey,
+            Enter: this.handleEnterKey,
             'Ctrl-Enter': this.handleCtrlEnterKey,
             'Cmd-Enter': this.handleCtrlEnterKey,
-            'Tab': 'indentMore',
+            Tab: 'indentMore',
             'Shift-Tab': 'indentLess',
             'Ctrl-Q': (cm) => { cm.foldCode(cm.getCursor()) },
-          }
+          },
         }}
-        onCursor={this.cursorHandler}
-        onScroll={(editor, data) => {
+          onCursor={this.cursorHandler}
+          onScroll={(editor, data) => {
           if (this.props.onScroll != null) {
             // add line data
             const line = editor.lineAtHeight(data.top, 'local');
@@ -739,25 +800,25 @@ export default class CodeMirrorEditor extends AbstractEditor {
             this.props.onScroll(data);
           }
         }}
-        onChange={this.changeHandler}
-        onDragEnter={(editor, event) => {
+          onChange={this.changeHandler}
+          onDragEnter={(editor, event) => {
           if (this.props.onDragEnter != null) {
             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="handsontableModal" onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }} />
+      </React.Fragment>
+    );
   }
-
 }
 
 CodeMirrorEditor.propTypes = Object.assign({

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

@@ -1,7 +1,6 @@
 import UpdateDisplayUtil from '../../util/codemirror/update-display-util.ext';
 
 class EmojiAutoCompleteHelper {
-
   constructor(emojiStrategy) {
     this.emojiStrategy = emojiStrategy;
 
@@ -14,7 +13,7 @@ class EmojiAutoCompleteHelper {
   }
 
   initEmojiImageMap() {
-    for (let unicode in this.emojiStrategy) {
+    for (const unicode in this.emojiStrategy) {
       const data = this.emojiStrategy[unicode];
       const shortname = data.shortname;
       // add image tag
@@ -62,7 +61,7 @@ class EmojiAutoCompleteHelper {
       // closeOnUnfocus: false,  // for debug
       hint: () => {
         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
         const shortnames = this.searchEmojiShortnames(term);
@@ -87,10 +86,9 @@ class EmojiAutoCompleteHelper {
         text: shortname,
         className: 'crowi-emoji-autocomplete',
         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,15 @@ class EmojiAutoCompleteHelper {
   searchEmojiShortnames(term) {
     const maxLength = 12;
 
-    let results1 = [], results2 = [], results3 = [], results4 = [];
+    const results1 = []; const results2 = []; const results3 = []; const
+      results4 = [];
     const countLen1 = () => { results1.length };
     const countLen2 = () => { countLen1() + results2.length };
     const countLen3 = () => { countLen2() + results3.length };
     const countLen4 = () => { countLen3() + results4.length };
     // TODO performance tune
     // 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 this.emojiStrategy) {
       if (maxLength <= countLen1()) { break }
       // prefix match to shortname
       else if (data.shortname.indexOf(`:${term}`) > -1) {
@@ -127,13 +124,13 @@ class EmojiAutoCompleteHelper {
       }
       else if (maxLength <= countLen3()) { continue }
       // 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);
         continue;
       }
       else if (maxLength <= countLen4()) { continue }
       // 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);
       }
     }
@@ -143,7 +140,6 @@ class EmojiAutoCompleteHelper {
 
     return results;
   }
-
 }
 
 export default EmojiAutoCompleteHelper;

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

@@ -2,7 +2,6 @@
  * Utility for markdown list
  */
 class MarkdownListUtil {
-
   constructor() {
     // https://github.com/codemirror/CodeMirror/blob/c7853a989c77bb9f520c9c530cbe1497856e96fc/addon/edit/continuelist.js#L14
     // https://regex101.com/r/7BN2fR/5
@@ -88,7 +87,7 @@ class MarkdownListUtil {
     // not listful data
     else {
       // append `indentAndMark` at the beginning of all lines (except the first line)
-      const replacedText = text.replace(/(\r\n|\r|\n)/g, '$1' + indentAndMark);
+      const replacedText = text.replace(/(\r\n|\r|\n)/g, `$1${indentAndMark}`);
       // append `indentAndMark` to the first line
       adjusted = indentAndMark + replacedText;
     }
@@ -118,13 +117,11 @@ class MarkdownListUtil {
       // ensure to be true if it is 50% or more
       if (count >= lines.length / 2) {
         isListful = true;
-        return;
       }
     });
 
     return isListful;
   }
-
 }
 
 // singleton pattern

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

@@ -7,7 +7,6 @@ import MarkdownTable from '../../models/MarkdownTable';
  * Interceptor for markdown table
  */
 export default class MarkdownTableInterceptor extends BasicInterceptor {
-
   constructor() {
     super();
   }
@@ -32,8 +31,8 @@ export default class MarkdownTableInterceptor extends BasicInterceptor {
    * @inheritdoc
    */
   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
     if (editor == null || editor.getCodeMirror() == null) {

+ 7 - 8
src/client/js/components/PageEditor/MarkdownTableUtil.js

@@ -4,12 +4,11 @@ import MarkdownTable from '../../models/MarkdownTable';
  * Utility for markdown table
  */
 class MarkdownTableUtil {
-
   constructor() {
     // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
     // https://regex101.com/r/7BN2fR/7
     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.getBot = this.getBot.bind(this);
@@ -29,7 +28,7 @@ class MarkdownTableUtil {
   getBot(editor) {
     const curPos = editor.getCursor();
     if (!this.isInTable(editor)) {
-      return { line: curPos.line, ch: curPos.ch};
+      return { line: curPos.line, ch: curPos.ch };
     }
 
     const firstLine = editor.getDoc().firstLine();
@@ -51,7 +50,7 @@ class MarkdownTableUtil {
   getEot(editor) {
     const curPos = editor.getCursor();
     if (!this.isInTable(editor)) {
-      return { line: curPos.line, ch: curPos.ch};
+      return { line: curPos.line, ch: curPos.ch };
     }
 
     const lastLine = editor.getDoc().lastLine();
@@ -127,8 +126,8 @@ class MarkdownTableUtil {
    */
   addRowToMarkdownTable(mdtable) {
     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);
   }
 
@@ -176,11 +175,11 @@ class MarkdownTableUtil {
 
     let newMarkdown = '';
     if (markdownBeforeTable.length > 0) {
-      newMarkdown += markdownBeforeTable.join('\n') + '\n';
+      newMarkdown += `${markdownBeforeTable.join('\n')}\n`;
     }
     newMarkdown += table;
     if (markdownAfterTable.length > 0) {
-      newMarkdown += '\n' + markdownAfterTable.join('\n');
+      newMarkdown += `\n${markdownAfterTable.join('\n')}`;
     }
 
     return newMarkdown;

+ 47 - 31
src/client/js/components/PageEditor/OptionsSelector.js

@@ -10,7 +10,6 @@ import Dropdown from 'react-bootstrap/es/Dropdown';
 import MenuItem from 'react-bootstrap/es/MenuItem';
 
 class OptionsSelector extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -25,7 +24,7 @@ class OptionsSelector extends React.Component {
     };
 
     this.availableThemes = [
-      'eclipse', 'elegant', 'neo', 'mdn-like', 'material', 'dracula', 'monokai', 'twilight'
+      'eclipse', 'elegant', 'neo', 'mdn-like', 'material', 'dracula', 'monokai', 'twilight',
     ];
     this.keymapModes = {
       default: 'Default',
@@ -52,8 +51,8 @@ class OptionsSelector extends React.Component {
 
   onChangeTheme() {
     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
     this.dispatchOnChange();
@@ -61,8 +60,8 @@ class OptionsSelector extends React.Component {
 
   onChangeKeymapMode() {
     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
     this.dispatchOnChange();
@@ -73,8 +72,8 @@ class OptionsSelector extends React.Component {
     this._cddForceOpen = true;
 
     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
     this.dispatchOnChange();
@@ -85,8 +84,8 @@ class OptionsSelector extends React.Component {
     this._cddForceOpen = true;
 
     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
     this.dispatchOnChange();
@@ -122,9 +121,14 @@ class OptionsSelector extends React.Component {
     return (
       <FormGroup controlId="formControlsSelect" className="my-0">
         <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}
+          inputRef={(el) => { return this.themeSelectorInputEl = el }}
+        >
 
           {optionElems}
 
@@ -135,13 +139,13 @@ class OptionsSelector extends React.Component {
 
   renderKeymapModeSelector() {
     const optionElems = [];
-    for (let mode in this.keymapModes) {
+    for (const mode in this.keymapModes) {
       const label = this.keymapModes[mode];
       const dataContent = (mode === 'default')
         ? label
         : `<img src='/images/icons/${mode}.png' width='16px' class='m-r-5'></img> ${label}`;
       optionElems.push(
-        <option key={mode} value={mode} data-content={dataContent}>{label}</option>
+        <option key={mode} value={mode} data-content={dataContent}>{label}</option>,
       );
     }
 
@@ -150,9 +154,14 @@ class OptionsSelector extends React.Component {
     return (
       <FormGroup controlId="formControlsSelect" className="my-0">
         <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}
+          inputRef={(el) => { return this.keymapModeSelectorInputEl = el }}
+        >
 
           {optionElems}
 
@@ -165,11 +174,16 @@ class OptionsSelector extends React.Component {
     return (
       <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">
-            <i className="icon-settings"></i>
+            <i className="icon-settings" />
           </Dropdown.Toggle>
 
           <Dropdown.Menu>
@@ -196,9 +210,9 @@ class OptionsSelector extends React.Component {
 
     return (
       <MenuItem onClick={this.onClickStyleActiveLine}>
-        <span className="icon-container"></span>
+        <span className="icon-container" />
         <span className="menuitem-label">{ t('page_edit.Show active line') }</span>
-        <span className="icon-container"><i className={iconClassName}></i></span>
+        <span className="icon-container"><i className={iconClassName} /></span>
       </MenuItem>
     );
   }
@@ -219,19 +233,21 @@ class OptionsSelector extends React.Component {
 
     return (
       <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" /></span>
         <span className="menuitem-label">MathJax Rendering</span>
-        <i className={iconClassName}></i>
+        <i className={iconClassName} />
       </MenuItem>
     );
   }
 
   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>
+    );
   }
 }
 
@@ -254,7 +270,7 @@ export class PreviewOptions {
 }
 
 OptionsSelector.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   editorOptions: PropTypes.instanceOf(EditorOptions).isRequired,
   previewOptions: PropTypes.instanceOf(PreviewOptions).isRequired,

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

@@ -3,7 +3,6 @@ import accepts from 'attr-accept';
 import markdownListUtil from './MarkdownListUtil';
 
 class PasteHelper {
-
   constructor() {
     this.pasteText = this.pasteText.bind(this);
   }
@@ -35,6 +34,7 @@ class PasteHelper {
   fileAccepted(file, accept) {
     return file.type === 'application/x-moz-file' || accepts(file, accept);
   }
+
   /**
    * transplanted from react-dropzone
    * @see https://github.com/react-dropzone/react-dropzone/blob/master/src/utils/index.js

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

@@ -2,7 +2,6 @@ import { BasicInterceptor } from 'growi-pluginkit';
 import mlu from './MarkdownListUtil';
 
 export default class PreventMarkdownListInterceptor extends BasicInterceptor {
-
   constructor() {
     super();
   }
@@ -27,8 +26,8 @@ export default class PreventMarkdownListInterceptor extends BasicInterceptor {
    * @inheritdoc
    */
   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
     const strToEol = editor.getStrToEol();

+ 7 - 6
src/client/js/components/PageEditor/Preview.js

@@ -9,7 +9,6 @@ import { PreviewOptions } from './OptionsSelector';
  * Wrapper component for Page/RevisionBody
  */
 export default class Preview extends React.Component {
-
   constructor(props) {
     super(props);
   }
@@ -18,16 +17,18 @@ export default class Preview extends React.Component {
     const renderMathJaxInRealtime = this.props.previewOptions.renderMathJaxInRealtime;
 
     return (
-      <div className="page-editor-preview-body"
-          ref={(elm) => {
+      <div
+        className="page-editor-preview-body"
+        ref={(elm) => {
             this.previewElement = elm;
             this.props.inputRef(elm);
           }}
-          onScroll={(event) => {
+        onScroll={(event) => {
             if (this.props.onScroll != null) {
               this.props.onScroll(event.target.scrollTop);
             }
-          }}>
+          }}
+      >
 
         <RevisionBody
           {...this.props}
@@ -40,7 +41,7 @@ export default class Preview extends React.Component {
 
 Preview.propTypes = {
   html: PropTypes.string,
-  inputRef: PropTypes.func.isRequired,  // for getting div element
+  inputRef: PropTypes.func.isRequired, // for getting div element
   isMathJaxEnabled: PropTypes.bool,
   renderMathJaxOnInit: PropTypes.bool,
   previewOptions: PropTypes.instanceOf(PreviewOptions),

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

@@ -3,7 +3,6 @@
  * @see https://github.com/Microsoft/vscode/blob/0532a3429a18688a0c086a4212e7e5b4888b2a48/extensions/markdown/media/main.js
  */
 class ScrollSyncHelper {
-
   /**
 	 * @typedef {{ element: Element, line: number }} CodeLineElement
 	 */
@@ -17,11 +16,12 @@ class ScrollSyncHelper {
     if (!elements) {
       elements = Array.prototype.map.call(
         parentElement.getElementsByClassName('code-line'),
-        element => {
+        (element) => {
           const line = +element.getAttribute('data-line');
           return { element, line };
-        })
-        .filter(x => !isNaN(x.line));
+        },
+      )
+        .filter((x) => { return !isNaN(x.line) });
     }
     return elements;
   }
@@ -44,7 +44,7 @@ class ScrollSyncHelper {
       if (entry.line === targetLine) {
         return { previous: entry, next: null };
       }
-      else if (entry.line > targetLine) {
+      if (entry.line > targetLine) {
         return { previous, next: entry };
       }
       previous = entry;
@@ -82,7 +82,7 @@ class ScrollSyncHelper {
     if (hi >= 1 && hiElement.element.getBoundingClientRect().top > position) {
       const loElement = lines[lo];
       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) {
         previous.line += (position - bounds.top) / (bounds.height);
       }
@@ -102,9 +102,8 @@ class ScrollSyncHelper {
         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);
       }
-      else {
-        return previous.line;
-      }
+
+      return previous.line;
     }
     return null;
   }

+ 52 - 13
src/client/js/components/PageEditor/SimpleCheatsheet.js

@@ -12,30 +12,69 @@ class SimpleCheatsheet extends React.Component {
           <div className="row">
             <div className="col-xs-6">
               <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><i>*{t('sandbox.italics')}*</i>&nbsp;&nbsp;<b>**{t('sandbox.bold')}**</b></p>
               <p>
-                [{t('sandbox.link')}](http://..)<br />
+                <i>
+*
+                  {t('sandbox.italics')}
+*
+                </i>
+&nbsp;&nbsp;
+                <b>
+**
+                  {t('sandbox.bold')}
+**
+                </b>
+              </p>
+              <p>
+                [
+                {t('sandbox.link')}
+](http://..)
+                <br />
                 [/Page1/ChildPage1]
               </p>
               <p>
-                ```javascript:index.js<br />
-                writeCode();<br />
+                ```javascript:index.js
+                <br />
+                writeCode();
+                <br />
                 ```
               </p>
             </div>
             <div className="col-xs-6">
               <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>
               <hr />
-              <p>[ ][ ] {t('sandbox.block_detail')}</p>
+              <p>
+[ ][ ]
+                {t('sandbox.block_detail')}
+              </p>
             </div>
           </div>
         </div>
@@ -45,7 +84,7 @@ class SimpleCheatsheet extends React.Component {
 }
 
 SimpleCheatsheet.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
 };
 
 export default translate()(SimpleCheatsheet);

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

@@ -13,7 +13,6 @@ import mlu from './MarkdownListUtil';
 import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
 
 export default class TextAreaEditor extends AbstractEditor {
-
   constructor(props) {
     super(props);
     this.logger = require('@alias/logger')('growi:PageEditor:TextAreaEditor');
@@ -86,11 +85,11 @@ export default class TextAreaEditor extends AbstractEditor {
     // scroll to bottom
     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);
     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
 
     this.textarea.setSelectionRange(pos, pos);
   }
@@ -149,13 +148,13 @@ export default class TextAreaEditor extends AbstractEditor {
 
   getBolPos() {
     const currentPos = this.textarea.selectionStart;
-    return this.textarea.value.lastIndexOf('\n', currentPos-1) + 1;
+    return this.textarea.value.lastIndexOf('\n', currentPos - 1) + 1;
   }
 
   getEolPos() {
     const currentPos = this.textarea.selectionStart;
     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 pos;
@@ -197,7 +196,7 @@ export default class TextAreaEditor extends AbstractEditor {
     }
 
     const context = {
-      handlers: [],  // list of handlers which process enter key
+      handlers: [], // list of handlers which process enter key
       editor: this,
     };
 
@@ -240,21 +239,23 @@ export default class TextAreaEditor extends AbstractEditor {
   }
 
   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) {
             this.props.onChange(e.target.value);
           }
-        }} />
-    </React.Fragment>;
+        }}
+        />
+      </React.Fragment>
+    );
   }
-
 }
 
 TextAreaEditor.propTypes = Object.assign({
 }, AbstractEditor.propTypes);
-

+ 18 - 19
src/client/js/components/PageHistory.js

@@ -5,7 +5,6 @@ import { translate } from 'react-i18next';
 import PageRevisionList from './PageHistory/PageRevisionList';
 
 class PageHistory extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -22,14 +21,13 @@ class PageHistory extends React.Component {
     const pageId = this.props.pageId;
 
     if (!pageId) {
-      return ;
+      return;
     }
 
-    this.props.crowi.apiGet('/revisions.ids', {page_id: pageId})
-    .then(res => {
-
+    this.props.crowi.apiGet('/revisions.ids', { page_id: pageId })
+    .then((res) => {
       const rev = res.revisions;
-      let diffOpened = {};
+      const diffOpened = {};
       const lastId = rev.length - 1;
       res.revisions.map((revision, i) => {
         const user = this.props.crowi.findUserById(revision.author);
@@ -47,7 +45,7 @@ class PageHistory extends React.Component {
 
       this.setState({
         revisions: rev,
-        diffOpened: diffOpened,
+        diffOpened,
       });
 
       // load 0, and last default
@@ -60,14 +58,14 @@ class PageHistory extends React.Component {
       if (lastId !== 0 && lastId !== 1 && rev[lastId]) {
         this.fetchPageRevisionBody(rev[lastId]);
       }
-    }).catch(err => {
+    }).catch((err) => {
       // do nothing
     });
   }
 
   getPreviousRevision(currentRevision) {
     let cursor = null;
-    for (let revision of this.state.revisions) {
+    for (const revision of this.state.revisions) {
       if (cursor && cursor._id == currentRevision._id) {
         cursor = revision;
         break;
@@ -80,12 +78,14 @@ class PageHistory extends React.Component {
   }
 
   onDiffOpenClicked(revision) {
-    const diffOpened = this.state.diffOpened,
-      revisionId = revision._id;
+    const diffOpened = this.state.diffOpened;
+
+
+    const revisionId = revision._id;
 
     diffOpened[revisionId] = !(diffOpened[revisionId]);
     this.setState({
-      diffOpened
+      diffOpened,
     });
 
     this.fetchPageRevisionBody(revision);
@@ -94,13 +94,12 @@ class PageHistory extends React.Component {
 
   fetchPageRevisionBody(revision) {
     if (revision.body) {
-      return ;
+      return;
     }
 
     this.props.crowi.apiGet('/revisions.get',
-      { page_id: this.props.pageId, revision_id: revision._id}
-    )
-    .then(res => {
+      { page_id: this.props.pageId, revision_id: revision._id })
+    .then((res) => {
       if (res.ok) {
         this.setState({
           revisions: this.state.revisions.map((rev) => {
@@ -109,11 +108,11 @@ class PageHistory extends React.Component {
             }
 
             return rev;
-          })
+          }),
         });
       }
     })
-    .catch(err => {
+    .catch((err) => {
 
     });
   }
@@ -134,7 +133,7 @@ class PageHistory extends React.Component {
 }
 
 PageHistory.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   pageId: PropTypes.string,
   crowi: PropTypes.object.isRequired,
 };

+ 9 - 7
src/client/js/components/PageHistory/RevisionDiff.js

@@ -5,18 +5,20 @@ import { createPatch } from 'diff';
 import { Diff2Html } from 'diff2html';
 
 export default class RevisionDiff extends React.Component {
-
   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 = '';
     if (currentRevision.body
       && previousRevision.body
       && revisionDiffOpened) {
-
       let previousText = previousRevision.body;
       if (currentRevision._id == previousRevision._id) {
         previousText = '';
@@ -25,13 +27,13 @@ export default class RevisionDiff extends React.Component {
       const patch = createPatch(
         currentRevision.path,
         previousText,
-        currentRevision.body
+        currentRevision.body,
       );
 
       diffViewHTML = Diff2Html.getPrettyHtml(patch);
     }
 
-    const diffView = {__html: diffViewHTML};
+    const diffView = { __html: diffViewHTML };
     return <div className="revision-history-diff" dangerouslySetInnerHTML={diffView} />;
   }
 }

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

@@ -4,16 +4,15 @@ import PropTypes from 'prop-types';
 import Page from './Page';
 
 export default class ListView extends React.Component {
-
   render() {
     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 (
       <div className="page-list">
         <ul className="page-list-ul page-list-ul-flat">
-        {listView}
+          {listView}
         </ul>
       </div>
     );

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

@@ -6,7 +6,6 @@ import PageListMeta from './PageListMeta';
 import PagePath from './PagePath';
 
 export default class Page extends React.Component {
-
   render() {
     const page = this.props.page;
     let link = this.props.linkTo;
@@ -15,7 +14,7 @@ export default class Page extends React.Component {
     }
 
     const styleFlex = {
-      flex: 1
+      flex: 1,
     };
 
     return (
@@ -25,7 +24,7 @@ export default class Page extends React.Component {
           <PagePath page={page} excludePathString={this.props.excludePathString} />
         </a>
         <PageListMeta page={page} />
-        <div style={styleFlex}></div>
+        <div style={styleFlex} />
         {this.props.children}
       </li>
     );
@@ -42,4 +41,3 @@ Page.defaultProps = {
   linkTo: '',
   excludePathString: '',
 };
-

+ 12 - 4
src/client/js/components/PageList/PageListMeta.js

@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
 import templateChecker from '@commons/util/template-checker';
 
 export default class PageListMeta extends React.Component {
-
   isPortalPath(path) {
     if (path.match(/.*\/$/)) {
       return true;
@@ -30,12 +29,22 @@ export default class PageListMeta extends React.Component {
 
     let commentCount;
     if (page.commentCount > 0) {
-      commentCount = <span><i className="icon-bubble" />{page.commentCount}</span>;
+      commentCount = (
+        <span>
+          <i className="icon-bubble" />
+          {page.commentCount}
+        </span>
+      );
     }
 
     let likerCount;
     if (page.liker.length > 0) {
-      likerCount = <span><i className="icon-like" />{page.liker.length}</span>;
+      likerCount = (
+        <span>
+          <i className="icon-like" />
+          {page.liker.length}
+        </span>
+      );
     }
 
     let locked;
@@ -62,4 +71,3 @@ PageListMeta.propTypes = {
 PageListMeta.defaultProps = {
   page: {},
 };
-

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

@@ -4,9 +4,8 @@ import PropTypes from 'prop-types';
 import escapeStringRegexp from 'escape-string-regexp';
 
 export default class PagePath extends React.Component {
-
   getShortPath(path) {
-    let name = path.replace(/(\/)$/, '');
+    const name = path.replace(/(\/)$/, '');
 
     // /.../hoge/YYYY/MM/DD 形式のページ
     if (name.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) {
@@ -34,7 +33,7 @@ export default class PagePath extends React.Component {
     const shortPath = this.getShortPath(pagePath);
 
     const shortPathEscaped = escapeStringRegexp(shortPath);
-    const pathPrefix = pagePath.replace(new RegExp(shortPathEscaped + '(/)?$'), '');
+    const pathPrefix = pagePath.replace(new RegExp(`${shortPathEscaped}(/)?$`), '');
 
     let classNames = ['page-path'];
     classNames = classNames.concat(this.props.additionalClassNames);
@@ -42,10 +41,13 @@ export default class PagePath extends React.Component {
     if (isShortPathOnly) {
       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>
+    );
   }
 }
 

+ 5 - 8
src/client/js/components/ReactUtils.js

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

+ 22 - 8
src/client/js/components/SearchForm.js

@@ -4,7 +4,6 @@ import SearchTypeahead from './SearchTypeahead';
 
 // SearchTypeahead wrapper
 export default class SearchForm extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -29,7 +28,7 @@ export default class SearchForm extends React.Component {
   }
 
   onChange(selected) {
-    const page = selected[0];  // should be single page selected
+    const page = selected[0]; // should be single page selected
 
     // navigate to page
     if (page != null) {
@@ -43,20 +42,35 @@ export default class SearchForm extends React.Component {
     return (
       <table className="table m-1 search-help">
         <caption className="text-left text-primary p-2 mb-2">
-          <h5 className="m-1"><i className="icon-magnifier pr-2 mb-2"/>{ t('search_help.title') }</h5>
+          <h5 className="m-1">
+            <i className="icon-magnifier pr-2 mb-2" />
+            { t('search_help.title') }
+          </h5>
         </caption>
         <tbody>
           <tr>
             <th className="text-right pt-2">
-              <code>word1</code> <code>word2</code><br></br>
-              <small>({ t('search_help.and.syntax help') })</small>
+              <code>word1</code>
+              {' '}
+              <code>word2</code>
+              <br />
+              <small>
+(
+                { t('search_help.and.syntax help') }
+)
+              </small>
             </th>
             <td><h6 className="m-0 pt-1">{ t('search_help.and.desc', { word1: 'word1', word2: 'word2' }) }</h6></td>
           </tr>
           <tr>
             <th className="text-right pt-2">
-              <code>"This is GROWI"</code><br></br>
-              <small>({ t('search_help.phrase.syntax help') })</small>
+              <code>"This is GROWI"</code>
+              <br />
+              <small>
+(
+                { t('search_help.phrase.syntax help') }
+)
+              </small>
             </th>
             <td><h6 className="m-0 pt-1">{ t('search_help.phrase.desc', { phrase: 'This is GROWI' }) }</h6></td>
           </tr>
@@ -100,7 +114,7 @@ export default class SearchForm extends React.Component {
 }
 
 SearchForm.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   keyword: PropTypes.string,
   onSubmit: PropTypes.func.isRequired,

+ 21 - 17
src/client/js/components/SearchPage.js

@@ -7,13 +7,15 @@ import { translate } from 'react-i18next';
 import SearchPageForm from './SearchPage/SearchPageForm';
 import SearchResult from './SearchPage/SearchResult';
 
-class SearchPage extends React.Component {
+/* global location */
+/* eslint no-restricted-globals: ['error', 'locaion'] */
 
+class SearchPage extends React.Component {
   constructor(props) {
     super(props);
 
     this.state = {
-      location: location,
+      location,
       searchingKeyword: this.props.query.q || '',
       searchedKeyword: '',
       searchedPages: [],
@@ -27,17 +29,17 @@ class SearchPage extends React.Component {
 
   componentDidMount() {
     const keyword = this.state.searchingKeyword;
-    if (keyword !== '')  {
-      this.search({keyword});
+    if (keyword !== '') {
+      this.search({ keyword });
     }
   }
 
   static getQueryByLocation(location) {
-    let search = location.search || '';
-    let query = {};
+    const search = location.search || '';
+    const query = {};
 
-    search.replace(/^\?/, '').split('&').forEach(function(element) {
-      let queryParts = element.split('=');
+    search.replace(/^\?/, '').split('&').forEach((element) => {
+      const queryParts = element.split('=');
       query[queryParts[0]] = decodeURIComponent(queryParts[1]).replace(/\+/g, ' ');
     });
 
@@ -72,8 +74,8 @@ class SearchPage extends React.Component {
       searchingKeyword: keyword,
     });
 
-    this.props.crowi.apiGet('/search', {q: keyword})
-    .then(res => {
+    this.props.crowi.apiGet('/search', { q: keyword })
+    .then((res) => {
       this.changeURL(keyword);
 
       this.setState({
@@ -81,7 +83,7 @@ class SearchPage extends React.Component {
         searchedPages: res.data,
         searchResultMeta: res.meta,
       });
-    }).catch(err => {
+    }).catch((err) => {
       // TODO error
       this.setState({
         searchError: err,
@@ -93,31 +95,33 @@ class SearchPage extends React.Component {
     return (
       <div>
         <div className="search-page-input">
-          <SearchPageForm t={this.props.t}
+          <SearchPageForm
+            t={this.props.t}
             crowi={this.props.crowi}
             onSearchFormChanged={this.search}
             keyword={this.state.searchingKeyword}
-            />
+          />
         </div>
         <SearchResult
-          crowi={this.props.crowi} crowiRenderer={this.props.crowiRenderer}
+          crowi={this.props.crowi}
+          crowiRenderer={this.props.crowiRenderer}
           pages={this.state.searchedPages}
           searchingKeyword={this.state.searchingKeyword}
           searchResultMeta={this.state.searchResultMeta}
-          />
+        />
       </div>
     );
   }
 }
 
 SearchPage.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   crowiRenderer: PropTypes.object.isRequired,
   query: PropTypes.object,
 };
 SearchPage.defaultProps = {
-  //pollInterval: 1000,
+  // pollInterval: 1000,
   query: SearchPage.getQueryByLocation(location || {}),
   searchError: null,
 };

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

@@ -6,7 +6,6 @@ import Modal from 'react-bootstrap/es/Modal';
 import Checkbox from 'react-bootstrap/es/Checkbox';
 
 export default class DeletePageListModal extends React.Component {
-
   /*
    * the threshold for omitting body
    */
@@ -21,7 +20,7 @@ export default class DeletePageListModal extends React.Component {
 
   render() {
     if (this.props.pages === undefined || this.props.pages.length == 0) {
-      return <div></div>;
+      return <div />;
     }
 
     const listView = this.props.pages.map((page) => {
@@ -44,9 +43,12 @@ export default class DeletePageListModal extends React.Component {
           <div className="d-flex justify-content-between">
             <span className="text-danger">{this.props.errorMessage}</span>
             <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">
-                <Button onClick={this.props.confirmedToDelete}><i className="icon-trash"></i>Delete</Button>
+                <Button onClick={this.props.confirmedToDelete}>
+                  <i className="icon-trash" />
+Delete
+                </Button>
               </span>
             </span>
           </div>
@@ -54,14 +56,13 @@ export default class DeletePageListModal extends React.Component {
       </Modal>
     );
   }
-
 }
 
 DeletePageListModal.propTypes = {
   isShown: PropTypes.bool.isRequired,
   pages: PropTypes.array,
   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
 };

+ 22 - 20
src/client/js/components/SearchPage/SearchPageForm.js

@@ -9,7 +9,6 @@ import SearchForm from '../SearchForm';
 
 // Search.SearchForm
 export default class SearchPageForm extends React.Component {
-
   constructor(props) {
     super(props);
 
@@ -24,35 +23,38 @@ export default class SearchPageForm extends React.Component {
 
   search() {
     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
-    this.setState({keyword: input});
+    this.setState({ keyword: input });
   }
 
   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" />
+            </Button>
+          </InputGroup.Button>
+        </InputGroup>
+      </FormGroup>
+    );
   }
 }
 
 SearchPageForm.propTypes = {
-  t: PropTypes.func.isRequired,               // i18next
+  t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   keyword: PropTypes.string,
   onSearchFormChanged: PropTypes.func.isRequired,

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

@@ -50,8 +50,8 @@ export default class SearchResult extends React.Component {
     else {
       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 });
   }
 
   /**
@@ -79,8 +79,9 @@ export default class SearchResult extends React.Component {
         this.state.selectedPages.add(page);
       });
     }
-    this.setState({selectedPages: this.state.selectedPages});
+    this.setState({ selectedPages: this.state.selectedPages });
   }
+
   /**
    * change deletion mode
    *
@@ -88,7 +89,7 @@ export default class SearchResult extends React.Component {
    */
   handleDeletionModeChange() {
     this.state.selectedPages.clear();
-    this.setState({deletionMode: !this.state.deletionMode});
+    this.setState({ deletionMode: !this.state.deletionMode });
   }
 
   /**
@@ -98,7 +99,7 @@ export default class SearchResult extends React.Component {
    */
   toggleDeleteCompletely() {
     // request で completely が undefined でないと指定アリと見なされるため
-    this.setState({isDeleteCompletely: this.state.isDeleteCompletely? undefined : true});
+    this.setState({ isDeleteCompletely: this.state.isDeleteCompletely ? undefined : true });
   }
 
   /**
@@ -107,24 +108,23 @@ export default class SearchResult extends React.Component {
    * @memberof SearchResult
    */
   deleteSelectedPages() {
-    let deleteCompletely = this.state.isDeleteCompletely;
+    const deleteCompletely = this.state.isDeleteCompletely;
     Promise.all(Array.from(this.state.selectedPages).map((page) => {
       return new Promise((resolve, reject) => {
         const pageId = page._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) {
               this.state.selectedPages.delete(page);
               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();
           });
       });
@@ -132,7 +132,7 @@ export default class SearchResult extends React.Component {
     .then(() => {
       window.location.reload();
     })
-    .catch(err => {
+    .catch((err) => {
       toastr.error(err, 'Error occured', {
         closeButton: true,
         progressBar: true,
@@ -150,7 +150,7 @@ export default class SearchResult extends React.Component {
    * @memberof SearchResult
    */
   showDeleteConfirmModal() {
-    this.setState({isDeleteConfirmModalShown: true});
+    this.setState({ isDeleteConfirmModalShown: true });
   }
 
   /**
@@ -168,12 +168,14 @@ export default class SearchResult extends React.Component {
   render() {
     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()) {
       return (
         <div className="content-main">
-          <i className="searcing fa fa-warning"></i> Error on searching.
+          <i className="searcing fa fa-warning" />
+          {' '}
+Error on searching.
         </div>
       );
     }
@@ -189,58 +191,78 @@ export default class SearchResult extends React.Component {
       }
       return (
         <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>
       );
-
     }
 
     let deletionModeButtons = '';
     let allSelectCheck = '';
 
     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
-        </label>
-      </div>;
+          </label>
+        </div>
+      );
     }
     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 pageId = '#' + page._id;
+      const pageId = `#${page._id}`;
       return (
-        <Page page={page}
+        <Page
+          page={page}
           linkTo={pageId}
           key={page._id}
           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}
               checked={this.state.selectedPages.has(page)}
-              onClick={() => this.toggleCheckbox(page)} />
+              onClick={() => { return this.toggleCheckbox(page) }}
+            />
+)
             }
           <div className="page-list-option">
             <a href={page.path}><i className="icon-login" /></a>
@@ -251,7 +273,7 @@ export default class SearchResult extends React.Component {
 
     // TODO あとでなんとかする
     setTimeout(() => {
-      $('#search-result-list > nav').affix({ offset: { top: 50 }});
+      $('#search-result-list > nav').affix({ offset: { top: 50 } });
     }, 1200);
 
     /*
@@ -268,9 +290,16 @@ export default class SearchResult extends React.Component {
                 {allSelectCheck}
               </div>
               <div className="search-result-meta">
-                <i className="icon-magnifier" /> Found {this.props.searchResultMeta.total} pages with &quot;{this.props.searchingKeyword}&quot;
+                <i className="icon-magnifier" />
+                {' '}
+Found
+                {this.props.searchResultMeta.total}
+                {' '}
+pages with &quot;
+                {this.props.searchingKeyword}
+&quot;
               </div>
-              <div className="clearfix"></div>
+              <div className="clearfix" />
               <div className="page-list">
                 <ul className="page-list-ul page-list-ul-flat nav">
                   {listView}
@@ -280,10 +309,11 @@ export default class SearchResult extends React.Component {
           </div>
           <div className="col-md-8 search-result-content" id="search-result-content">
             <SearchResultList
-              crowi={this.props.crowi} crowiRenderer={this.props.crowiRenderer}
+              crowi={this.props.crowi}
+              crowiRenderer={this.props.crowiRenderer}
               pages={this.props.pages}
               searchingKeyword={this.props.searchingKeyword}
-              />
+            />
           </div>
         </div>
         <DeletePageListModal
@@ -295,7 +325,7 @@ export default class SearchResult extends React.Component {
           toggleDeleteCompletely={this.toggleDeleteCompletely}
         />
 
-      </div>//content-main
+      </div>// content-main
     );
   }
 }
@@ -307,7 +337,7 @@ SearchResult.propTypes = {
   pages: PropTypes.array.isRequired,
   searchingKeyword: PropTypes.string.isRequired,
   searchResultMeta: PropTypes.object.isRequired,
-  searchError: PropTypes.object
+  searchError: PropTypes.object,
 };
 SearchResult.defaultProps = {
   tree: '',

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

@@ -6,11 +6,10 @@ import GrowiRenderer from '../../util/GrowiRenderer';
 import RevisionLoader from '../Page/RevisionLoader';
 
 export default class SearchResultList extends React.Component {
-
   constructor(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() {
@@ -32,7 +31,7 @@ export default class SearchResultList extends React.Component {
 
     return (
       <div>
-      {resultList}
+        {resultList}
       </div>
     );
   }

+ 14 - 17
src/client/js/components/SearchTypeahead.js

@@ -9,9 +9,7 @@ import PageListMeta from './PageList/PageListMeta';
 import PagePath from './PageList/PagePath';
 
 export default class SearchTypeahead extends React.Component {
-
   constructor(props) {
-
     super(props);
 
     this.state = {
@@ -60,7 +58,6 @@ export default class SearchTypeahead extends React.Component {
   }
 
   search(keyword) {
-
     if (keyword === '') {
       this.setState({
         keyword: '',
@@ -69,11 +66,11 @@ export default class SearchTypeahead extends React.Component {
       return;
     }
 
-    this.setState({isLoading: true});
+    this.setState({ isLoading: true });
 
-    this.crowi.apiGet('/search', {q: keyword})
-      .then(res => { this.onSearchSuccess(res) })
-      .catch(err => { this.onSearchError(err) });
+    this.crowi.apiGet('/search', { q: keyword })
+      .then((res) => { this.onSearchSuccess(res) })
+      .catch((err) => { this.onSearchError(err) });
   }
 
   /**
@@ -102,10 +99,10 @@ export default class SearchTypeahead extends React.Component {
   }
 
   onInputChange(text) {
-    this.setState({input: text});
+    this.setState({ input: text });
     this.props.onInputChange(text);
     if (text === '') {
-      this.setState({pages: []});
+      this.setState({ pages: [] });
     }
   }
 
@@ -141,9 +138,9 @@ export default class SearchTypeahead extends React.Component {
    * Get restore form button to initialize button
    */
   getRestoreFormButton() {
-    let isHidden = (this.state.input === this.props.keywordOnInit);
+    const isHidden = (this.state.input === this.props.keywordOnInit);
 
-    return isHidden ? <span></span> : (
+    return isHidden ? <span /> : (
       <button type="button" className="btn btn-link search-clear" onMouseDown={this.restoreInitialData}>
         <i className="icon-close" />
       </button>
@@ -154,16 +151,16 @@ export default class SearchTypeahead extends React.Component {
     const page = option;
     return (
       <span>
-      <UserPicture user={page.lastUpdateUser} size="sm" />
-      <PagePath page={page} />
-      <PageListMeta page={page} />
+        <UserPicture user={page.lastUpdateUser} size="sm" />
+        <PagePath page={page} />
+        <PageListMeta page={page} />
       </span>
     );
   }
 
   render() {
     const defaultSelected = (this.props.keywordOnInit != '')
-      ? [{path: this.props.keywordOnInit}]
+      ? [{ path: this.props.keywordOnInit }]
       : [];
     const inputProps = { autoComplete: 'off' };
     if (this.props.inputName != null) {
@@ -187,8 +184,8 @@ export default class SearchTypeahead extends React.Component {
               // DIRTY HACK
               //  note: The default searchText string has been shown wrongly even if isLoading is false
               //        since upgrade react-bootstrap-typeahead to v3.3.2 -- 2019.02.05 Yuki Takei
-          align='left'
-          submitFormOnEnter={true}
+          align="left"
+          submitFormOnEnter
           onSearch={this.search}
           onInputChange={this.onInputChange}
           onKeyDown={this.onKeyDown}

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

@@ -4,10 +4,9 @@ import PropTypes from 'prop-types';
 import UserPicture from './UserPicture';
 
 export default class User extends React.Component {
-
   render() {
     const user = this.props.user;
-    const userLink = '/user/' + user.username;
+    const userLink = `/user/${user.username}`;
 
     const username = this.props.username;
     const name = this.props.name;
@@ -17,11 +16,22 @@ export default class User extends React.Component {
         <a href={userLink}>
           <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>
       </span>

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

@@ -4,22 +4,20 @@ import PropTypes from 'prop-types';
 
 // TODO UserComponent?
 export default class UserPicture extends React.Component {
-
   getUserPicture(user) {
     // gravatar
     if (user.isGravatarEnabled === true) {
       return this.generateGravatarSrc(user);
     }
     // uploaded image
-    else if (user.image != null) {
+    if (user.image != null) {
       return user.image;
     }
-    else if (user.imageAttachment != null) {
+    if (user.imageAttachment != null) {
       return user.imageAttachment.filePathProxied;
     }
-    else {
-      return '/images/icons/user.svg';
-    }
+
+    return '/images/icons/user.svg';
   }
 
   generateGravatarSrc(user) {
@@ -29,10 +27,10 @@ export default class UserPicture extends React.Component {
   }
 
   getClassName() {
-    let className = ['img-circle', 'picture'];
+    const className = ['img-circle', 'picture'];
     // size
     if (this.props.size) {
-      className.push('picture-' + this.props.size);
+      className.push(`picture-${this.props.size}`);
     }
 
     return className.join(' ');
@@ -46,7 +44,7 @@ export default class UserPicture extends React.Component {
         src={this.getUserPicture(user)}
         alt={user.username}
         className={this.getClassName()}
-        />
+      />
     );
   }
 }

+ 7 - 9
src/client/js/hackmd-agent.js

@@ -16,7 +16,7 @@ import { debounce } from 'throttle-debounce';
 
 /* eslint-disable no-console  */
 
-const allowedOrigin = '{{origin}}';         // will be replaced by swig
+const allowedOrigin = '{{origin}}'; // will be replaced by swig
 
 
 /**
@@ -45,7 +45,6 @@ function setValueToCodemirror(value) {
 function setValueToCodemirrorOnInit(newValue) {
   if (window.cmClient != null) {
     setValueToCodemirror(newValue);
-    return;
   }
   else {
     const intervalId = setInterval(() => {
@@ -86,7 +85,7 @@ function addEventListenersToCodemirror() {
     return;
   }
 
-  //// change event
+  // // change event
   editor.on('change', (cm, change) => {
     if (change.origin === 'ignoreHistory') {
       // do nothing because this operation triggered by other user
@@ -95,7 +94,7 @@ function addEventListenersToCodemirror() {
     debouncedPostParentToNotifyBodyChanges(cm.doc.getValue());
   });
 
-  //// save event
+  // // save event
   // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
   codemirror.commands.save = function(cm) {
     postParentToSaveWithShortcut(cm.doc.getValue());
@@ -117,12 +116,12 @@ function connectToParentWithPenpal() {
       },
       setValueOnInit(newValue) {
         setValueToCodemirrorOnInit(newValue);
-      }
-    }
+      },
+    },
   });
-  connection.promise.then(parent => {
+  connection.promise.then((parent) => {
     window.growi = parent;
-  }).catch(err => {
+  }).catch((err) => {
     console.log(err);
   });
 }
@@ -148,4 +147,3 @@ function connectToParentWithPenpal() {
 
   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  */
 
-const styles = '{{styles}}';         // will be replaced by swig
+const styles = '{{styles}}'; // will be replaced by swig
 
 /**
  * Insert link tag to load style file

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

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

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

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

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

@@ -5,9 +5,9 @@ require('./thirdparty-js/jQuery.style.switcher');
 require('./thirdparty-js/switchery/switchery');
 require('./thirdparty-js/switchery/switchery.css');
 
-$(function() {
+$(() => {
   $('#slackNotificationForm').on('submit', function(e) {
-    $.post('/_api/admin/notification.add', $(this).serialize(), function(res) {
+    $.post('/_api/admin/notification.add', $(this).serialize(), (res) => {
       if (res.ok) {
         // TODO Fix
         location.reload();
@@ -18,7 +18,7 @@ $(function() {
   });
 
   $('form.admin-remove-updatepost').on('submit', function(e) {
-    $.post('/_api/admin/notification.remove', $(this).serialize(), function(res) {
+    $.post('/_api/admin/notification.remove', $(this).serialize(), (res) => {
       if (res.ok) {
         // TODO Fix
         location.reload();
@@ -29,7 +29,7 @@ $(function() {
 
   $('#createdUserModal').modal('show');
 
-  $('#admin-password-reset-modal').on('show.bs.modal', function(button) {
+  $('#admin-password-reset-modal').on('show.bs.modal', (button) => {
     const data = $(button.relatedTarget);
     const userId = data.data('user-id');
     const email = data.data('user-email');
@@ -39,16 +39,16 @@ $(function() {
   });
 
   $('form#admin-users-reset-password').on('submit', function(e) {
-    $.post('/_api/admin/users.resetPassword', $(this).serialize(), function(res) {
+    $.post('/_api/admin/users.resetPassword', $(this).serialize(), (res) => {
       if (res.ok) {
         // TODO Fix
-        //location.reload();
+        // location.reload();
         $('#admin-password-reset-modal').modal('hide');
         $('#admin-password-reset-modal-done').modal('show');
 
         $('#admin-password-reset-done-user').text(res.user.email);
         $('#admin-password-reset-done-password').text(res.newPassword);
-        return ;
+        return;
       }
 
       // fixme
@@ -58,7 +58,7 @@ $(function() {
     return false;
   });
 
-  $('#admin-delete-user-group-modal').on('show.bs.modal', function(button) {
+  $('#admin-delete-user-group-modal').on('show.bs.modal', (button) => {
     const data = $(button.relatedTarget);
     const userGroupId = data.data('user-group-id');
     const userGroupName = data.data('user-group-name');
@@ -68,9 +68,8 @@ $(function() {
   });
 
   $('form#user-group-relation-create').on('submit', function(e) {
-    $.post('/admin/user-group-relation/create', $(this).serialize(), function(res) {
+    $.post('/admin/user-group-relation/create', $(this).serialize(), (res) => {
       $('#admin-add-user-group-relation-modal').modal('hide');
-      return;
     });
   });
 
@@ -89,9 +88,9 @@ $(function() {
       contentType: false,
       data: fd,
       dataType: 'json',
-      success: function(data) {
+      success(data) {
         if (data.status) {
-          $('#settingUserPicture').attr('src', data.url + '?time=' + (new Date()));
+          $('#settingUserPicture').attr('src', `${data.url}?time=${new Date()}`);
           $('#pictureUploadFormMessage')
             .addClass('alert alert-success')
             .html('変更しました');
@@ -102,7 +101,7 @@ $(function() {
             .html('変更中にエラーが発生しました。');
         }
         $('#pictureUploadFormProgress').html('');
-      }
+      },
     });
     return false;
   });
@@ -112,11 +111,9 @@ $(function() {
 
   // switchery
   const elems = Array.prototype.slice.call(document.querySelectorAll('.js-switch'));
-  elems.forEach(function(elem) {
+  elems.forEach((elem) => {
     const color = elem.dataset.color;
     const size = elem.dataset.size;
-    new Switchery(elem, { color, size });   // eslint-disable-line no-undef
+    new Switchery(elem, { color, size }); // eslint-disable-line no-undef
   });
 });
-
-

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

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

+ 118 - 114
src/client/js/legacy/crowi.js

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

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

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

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

@@ -1,5 +1,4 @@
 export default class CrowiPlugin {
-
   /**
    * process plugin entry
    *
@@ -34,9 +33,7 @@ export default class CrowiPlugin {
           });
       }
     });
-
   }
-
 }
 
-window.crowiPlugin = new CrowiPlugin();     // FIXME
+window.crowiPlugin = new CrowiPlugin(); // FIXME

+ 18 - 21
src/client/js/util/Crowi.js

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

+ 10 - 11
src/client/js/util/GrowiRenderer.js

@@ -1,8 +1,8 @@
 import MarkdownIt from 'markdown-it';
 
-import Linker        from './PreProcessor/Linker';
-import CsvToTable    from './PreProcessor/CsvToTable';
-import XssFilter     from './PreProcessor/XssFilter';
+import Linker from './PreProcessor/Linker';
+import CsvToTable from './PreProcessor/CsvToTable';
+import XssFilter from './PreProcessor/XssFilter';
 import CrowiTemplate from './PostProcessor/CrowiTemplate';
 
 import EmojiConfigurer from './markdown-it/emoji';
@@ -22,7 +22,6 @@ const logger = require('@alias/logger')('growi:util:GrowiRenderer');
 
 
 export default class GrowiRenderer {
-
   /**
    *
    * @param {Crowi} crowi
@@ -33,8 +32,9 @@ export default class GrowiRenderer {
     this.crowi = crowi;
     this.originRenderer = originRenderer || {};
     this.options = Object.assign( // merge options
-      { isAutoSetup: true },      // default options
-      options || {});             // specified options
+      { isAutoSetup: true }, // default options
+      options || {},
+    ); // specified options
 
     // initialize processors
     //  that will be retrieved if originRenderer exists
@@ -89,21 +89,21 @@ export default class GrowiRenderer {
           new TocAndAnchorConfigurer(crowi, options.renderToc),
           new HeaderLineNumberConfigurer(crowi),
           new HeaderWithEditLinkConfigurer(crowi),
-          new TableWithHandsontableButtonConfigurer(crowi)
+          new TableWithHandsontableButtonConfigurer(crowi),
         ]);
         break;
       case 'editor':
         this.markdownItConfigurers = this.markdownItConfigurers.concat([
           new FooternoteConfigurer(crowi),
           new HeaderLineNumberConfigurer(crowi),
-          new TableConfigurer(crowi)
+          new TableConfigurer(crowi),
         ]);
         break;
       // case 'comment':
       //   break;
       default:
         this.markdownItConfigurers = this.markdownItConfigurers.concat([
-          new TableConfigurer(crowi)
+          new TableConfigurer(crowi),
         ]);
         break;
     }
@@ -115,7 +115,7 @@ export default class GrowiRenderer {
   setup() {
     const crowiConfig = this.crowi.config;
 
-    let isEnabledLinebreaks = undefined;
+    let isEnabledLinebreaks;
     switch (this.options.mode) {
       case 'comment':
         isEnabledLinebreaks = crowiConfig.isEnabledLinebreaksInComments;
@@ -204,5 +204,4 @@ export default class GrowiRenderer {
 
   highlightCode(code, lang) {
   }
-
 }

+ 12 - 14
src/client/js/util/PostProcessor/CrowiTemplate.js

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

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

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

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

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

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

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

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

@@ -5,7 +5,6 @@ import { DisplayUpdate } from 'codemirror/src/display/update_display';
 import { adjustView } from 'codemirror/src/display/view_tracking';
 
 class UpdateDisplayUtil {
-
   /**
    * Transplant 'updateDisplayIfNeeded' method to fix weseek/growi#703
    *
@@ -21,7 +20,7 @@ class UpdateDisplayUtil {
     const update = new DisplayUpdate(cm, cm.getViewport());
 
     // Compute a suitable new viewport (from & to)
-    let end = doc.first + doc.size;
+    const end = doc.first + doc.size;
     let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
     let to = Math.min(end, update.visible.to + cm.options.viewportMargin);
     if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
@@ -34,7 +33,6 @@ class UpdateDisplayUtil {
 
     display.viewOffset = heightAtLine(getLine(doc, display.viewFrom));
   }
-
 }
 
 

+ 8 - 10
src/client/js/util/interceptor/detach-code-blocks.js

@@ -11,7 +11,6 @@ class DetachCodeBlockUtil {
  * The interceptor that detach code blocks
  */
 export class DetachCodeBlockInterceptor extends BasicInterceptor {
-
   constructor(crowi) {
     super();
     this.logger = require('@alias/logger')('growi:DetachCodeBlockInterceptor');
@@ -31,7 +30,7 @@ export class DetachCodeBlockInterceptor extends BasicInterceptor {
     if (contextName === 'prePreProcess') {
       return 'markdown';
     }
-    else if (contextName === 'prePostProcess') {
+    if (contextName === 'prePostProcess') {
       return 'parsedHTML';
     }
   }
@@ -42,7 +41,7 @@ export class DetachCodeBlockInterceptor extends BasicInterceptor {
   process(contextName, ...args) {
     this.logger.debug(`processing: 'contextName'=${contextName}`);
 
-    const context = Object.assign(args[0]);   // clone
+    const context = Object.assign(args[0]); // clone
     const targetKey = this.getTargetKey(contextName);
     /* eslint-disable no-unused-vars */
     const currentPagePath = context.currentPagePath;
@@ -53,11 +52,11 @@ export class DetachCodeBlockInterceptor extends BasicInterceptor {
     // see: https://regex101.com/r/8PAEcC/5
     context[targetKey] = context[targetKey].replace(/(^(```|~~~)(.|[\r\n])*?(```|~~~)$)|(`[^\r\n]*?`)|(<pre>(.|[\r\n])*?<\/pre>)|(<pre\s[^>]*>(.|[\r\n])*?<\/pre>)/gm, (all) => {
       // create ID
-      const replaceId = 'dcb-' + this.createRandomStr(8);
+      const replaceId = `dcb-${this.createRandomStr(8)}`;
       this.logger.debug(`'replaceId'=${replaceId} : `, all);
 
       // register to context
-      let dcbContext = {};
+      const dcbContext = {};
       dcbContext.content = all;
       dcbContext.substituteContent = DetachCodeBlockUtil.createReplaceStr(replaceId);
       context.dcbContextMap[replaceId] = dcbContext;
@@ -79,7 +78,7 @@ export class DetachCodeBlockInterceptor extends BasicInterceptor {
   createRandomStr(length) {
     const bag = 'abcdefghijklmnopqrstuvwxyz0123456789';
     let generated = '';
-    for (var i = 0; i < length; i++) {
+    for (let i = 0; i < length; i++) {
       generated += bag[Math.floor(Math.random() * bag.length)];
     }
     return generated;
@@ -91,7 +90,6 @@ export class DetachCodeBlockInterceptor extends BasicInterceptor {
  * The interceptor that restore detached code blocks
  */
 export class RestoreCodeBlockInterceptor extends BasicInterceptor {
-
   constructor(crowi) {
     super();
     this.logger = require('@alias/logger')('growi:DetachCodeBlockInterceptor');
@@ -111,7 +109,7 @@ export class RestoreCodeBlockInterceptor extends BasicInterceptor {
     if (contextName === 'postPreProcess') {
       return 'markdown';
     }
-    else if (contextName === 'preRenderHtml' || contextName === 'preRenderPreviewHtml'
+    if (contextName === 'preRenderHtml' || contextName === 'preRenderPreviewHtml'
         || contextName === 'preRenderCommentHtml' || contextName === 'preRenderCommentPreviewHtml') {
       return 'parsedHTML';
     }
@@ -123,13 +121,13 @@ export class RestoreCodeBlockInterceptor extends BasicInterceptor {
   process(contextName, ...args) {
     this.logger.debug(`processing: 'contextName'=${contextName}`);
 
-    const context = Object.assign(args[0]);   // clone
+    const context = Object.assign(args[0]); // clone
     const targetKey = this.getTargetKey(contextName);
 
     // forEach keys of dcbContextMap
     Object.keys(context.dcbContextMap).forEach((replaceId) => {
       // get context object from context
-      let dcbContext = context.dcbContextMap[replaceId];
+      const dcbContext = context.dcbContextMap[replaceId];
 
       // replace it with content by using getter function so that the doller sign does not work
       // see: https://github.com/weseek/growi/issues/285

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

@@ -1,5 +1,4 @@
 export default class BlockdiagConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
     const config = crowi.getConfig();

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

@@ -1,5 +1,4 @@
 export default class EmojiConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
   }
@@ -9,13 +8,13 @@ export default class EmojiConfigurer {
 
     const emojiShortnameUnicodeMap = {};
 
-    for (let unicode in emojiStrategy) {
+    for (const unicode in emojiStrategy) {
       const data = emojiStrategy[unicode];
       const shortname = data.shortname.replace(/:/g, '');
       emojiShortnameUnicodeMap[shortname] = String.fromCharCode(unicode);
     }
 
-    md.use(require('markdown-it-emoji'), {defs: emojiShortnameUnicodeMap});
+    md.use(require('markdown-it-emoji'), { defs: emojiShortnameUnicodeMap });
 
     // integrate markdown-it-emoji and emojione
     md.renderer.rules.emoji = (token, idx) => {
@@ -23,5 +22,4 @@ export default class EmojiConfigurer {
       return emojione.shortnameToImage(shortname);
     };
   }
-
 }

+ 0 - 2
src/client/js/util/markdown-it/footernote.js

@@ -1,5 +1,4 @@
 export default class FooternoteConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
   }
@@ -7,5 +6,4 @@ export default class FooternoteConfigurer {
   configure(md) {
     md.use(require('markdown-it-footnote'));
   }
-
 }

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

@@ -1,5 +1,4 @@
 export default class HeaderLineNumberConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
     this.firstLine = 0;
@@ -27,9 +26,8 @@ export default class HeaderLineNumberConfigurer {
       if (original) {
         return original(tokens, idx, options, env, self);
       }
-      else {
-        return self.renderToken(tokens, idx, options, env, self);
-      }
+
+      return self.renderToken(tokens, idx, options, env, self);
     };
   }
 }

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

@@ -1,5 +1,4 @@
 export default class HeaderWithEditLinkConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
   }

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

@@ -1,5 +1,4 @@
 export default class HeaderConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
 
@@ -18,9 +17,8 @@ export default class HeaderConfigurer {
       if (original) {
         return original(tokens, idx, options, env, self);
       }
-      else {
-        return self.renderToken(tokens, idx, options, env, self);
-      }
+
+      return self.renderToken(tokens, idx, options, env, self);
     };
   }
 

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

@@ -1,10 +1,9 @@
 export default class MathJaxConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
 
     const config = crowi.getConfig();
-    this.isEnabled = !!config.env.MATHJAX;  // convert to boolean
+    this.isEnabled = !!config.env.MATHJAX; // convert to boolean
   }
 
   configure(md) {
@@ -12,5 +11,4 @@ export default class MathJaxConfigurer {
       md.use(require('markdown-it-mathjax')());
     }
   }
-
 }

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

@@ -2,7 +2,6 @@ import plantumlEncoder from 'plantuml-encoder';
 import urljoin from 'url-join';
 
 export default class PlantUMLConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
     const config = crowi.getConfig();

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

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

+ 0 - 2
src/client/js/util/markdown-it/table.js

@@ -1,5 +1,4 @@
 export default class TableConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
   }
@@ -9,5 +8,4 @@ export default class TableConfigurer {
       return '<table class="table table-bordered">';
     };
   }
-
 }

+ 0 - 2
src/client/js/util/markdown-it/task-lists.js

@@ -1,5 +1,4 @@
 export default class TaskListsConfigurer {
-
   constructor(crowi) {
     this.crowi = crowi;
   }
@@ -14,5 +13,4 @@ export default class TaskListsConfigurer {
       liClass: 'task-list-item',
     });
   }
-
 }

+ 0 - 2
src/client/js/util/markdown-it/toc-and-anchor.js

@@ -1,5 +1,4 @@
 export default class TocAndAnchorConfigurer {
-
   constructor(crowi, renderToc) {
     this.crowi = crowi;
     this.renderToc = renderToc;
@@ -23,5 +22,4 @@ export default class TocAndAnchorConfigurer {
       });
     }
   }
-
 }

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

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

+ 158 - 163
src/client/js/util/reveal/plugins/markdown.js

@@ -5,12 +5,17 @@
  * Referred from The reveal.js markdown plugin.
  * https://github.com/hakimel/reveal.js/blob/master/plugin/markdown/markdown.js
  */
-export default function( marked ) {
+export default function(marked) {
+  const DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$';
 
-  const DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
-    DEFAULT_NOTES_SEPARATOR = 'notes?:',
-    DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
-    DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
+
+  const DEFAULT_NOTES_SEPARATOR = 'notes?:';
+
+
+  const DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$';
+
+
+  const DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
 
   const SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
 
@@ -19,29 +24,29 @@ export default function( marked ) {
    * Retrieves the markdown contents of a slide section
    * element. Normalizes leading tabs/whitespace.
    */
-  function getMarkdownFromSlide( section ) {
-
+  function getMarkdownFromSlide(section) {
     // look for a <script> or <textarea data-template> wrapper
-    let template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
+    const template = section.querySelector('[data-template]') || section.querySelector('script');
 
     // strip leading whitespace so it isn't evaluated as code
-    let text = ( template || section ).textContent;
+    let text = (template || section).textContent;
 
     // restore script end tags
-    text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
+    text = text.replace(new RegExp(SCRIPT_END_PLACEHOLDER, 'g'), '</script>');
 
-    let leadingWs = text.match( /^\n?(\s*)/ )[1].length,
-      leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
+    const leadingWs = text.match(/^\n?(\s*)/)[1].length;
 
-    if ( leadingTabs > 0 ) {
-      text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}', 'g'), '\n' );
+
+    const leadingTabs = text.match(/^\n?(\t*)/)[1].length;
+
+    if (leadingTabs > 0) {
+      text = text.replace(new RegExp(`\\n?\\t{${leadingTabs}}`, 'g'), '\n');
     }
-    else if ( leadingWs > 1 ) {
-      text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
+    else if (leadingWs > 1) {
+      text = text.replace(new RegExp(`\\n? {${leadingWs}}`, 'g'), '\n');
     }
 
     return text;
-
   }
 
   /**
@@ -50,106 +55,113 @@ export default function( marked ) {
    * parsing. Used to forward any other user-defined arguments
    * to the output markdown slide.
    */
-  function getForwardedAttributes( section ) {
+  function getForwardedAttributes(section) {
+    const attributes = section.attributes;
+    const result = [];
 
-    let attributes = section.attributes;
-    let result = [];
+    for (let i = 0, len = attributes.length; i < len; i++) {
+      const name = attributes[i].name;
 
-    for ( let i = 0, len = attributes.length; i < len; i++ ) {
-      let name = attributes[i].name,
-        value = attributes[i].value;
+
+      const value = attributes[i].value;
 
       // disregard attributes that are used for markdown loading/parsing
-      if ( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
+      if (/data\-(markdown|separator|vertical|notes)/gi.test(name)) continue;
 
-      if ( value ) {
-        result.push( name + '="' + value + '"' );
+      if (value) {
+        result.push(`${name}="${value}"`);
       }
       else {
-        result.push( name );
+        result.push(name);
       }
     }
 
-    return result.join( ' ' );
-
+    return result.join(' ');
   }
 
   /**
    * Inspects the given options and fills out default
    * values for what's not defined.
    */
-  function getSlidifyOptions( options ) {
-
+  function getSlidifyOptions(options) {
     options = options || {};
     options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
     options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
     options.attributes = options.attributes || '';
 
     return options;
-
   }
 
   /**
    * Helper function for constructing a markdown slide.
    */
-  function createMarkdownSlide( content, options ) {
-
-    options = getSlidifyOptions( options );
+  function createMarkdownSlide(content, options) {
+    options = getSlidifyOptions(options);
 
-    let notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
+    const notesMatch = content.split(new RegExp(options.notesSeparator, 'mgi'));
 
-    if ( notesMatch.length === 2 ) {
-      content = notesMatch[0] + '<aside class="notes">' + marked(notesMatch[1].trim()) + '</aside>';
+    if (notesMatch.length === 2) {
+      content = `${notesMatch[0]}<aside class="notes">${marked(notesMatch[1].trim())}</aside>`;
     }
 
     // prevent script end tags in the content from interfering
     // with parsing
-    content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
-
-    return '<script type="text/template">' + content + '</script>';
+    content = content.replace(/<\/script>/g, SCRIPT_END_PLACEHOLDER);
 
+    return `<script type="text/template">${content}</script>`;
   }
 
   /**
    * Parses a data string into multiple slides based
    * on the passed in separator arguments.
    */
-  function slidify( markdown, options ) {
+  function slidify(markdown, options) {
+    options = getSlidifyOptions(options);
+
+    const separatorRegex = new RegExp(options.separator + (options.verticalSeparator ? `|${options.verticalSeparator}` : ''), 'mg');
+
+
+    const horizontalSeparatorRegex = new RegExp(options.separator);
+
+    let matches;
+
+
+    let lastIndex = 0;
+
+
+    let isHorizontal;
 
-    options = getSlidifyOptions( options );
 
-    let separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
-      horizontalSeparatorRegex = new RegExp( options.separator );
+    let wasHorizontal = true;
 
-    let matches,
-      lastIndex = 0,
-      isHorizontal,
-      wasHorizontal = true,
-      content,
-      sectionStack = [];
+
+    let content;
+
+
+    const sectionStack = [];
 
     // iterate until all blocks between separators are stacked up
-    while ( (matches = separatorRegex.exec( markdown )) != null ) {
+    while ((matches = separatorRegex.exec(markdown)) != null) {
       // notes = null;
 
       // determine direction (horizontal by default)
-      isHorizontal = horizontalSeparatorRegex.test( matches[0] );
+      isHorizontal = horizontalSeparatorRegex.test(matches[0]);
 
-      if ( !isHorizontal && wasHorizontal ) {
+      if (!isHorizontal && wasHorizontal) {
         // create vertical stack
-        sectionStack.push( [] );
+        sectionStack.push([]);
       }
 
       // pluck slide content from markdown input
-      content = markdown.substring( lastIndex, matches.index );
+      content = markdown.substring(lastIndex, matches.index);
 
-      if ( isHorizontal && wasHorizontal ) {
+      if (isHorizontal && wasHorizontal) {
         // add to horizontal stack
-        sectionStack.push( content );
+        sectionStack.push(content);
       }
       else {
         // add to vertical stack
-        sectionStack[sectionStack.length-1].push( content );
+        sectionStack[sectionStack.length - 1].push(content);
       }
 
       lastIndex = separatorRegex.lastIndex;
@@ -157,29 +169,28 @@ export default function( marked ) {
     }
 
     // add the remaining slide
-    ( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
+    (wasHorizontal ? sectionStack : sectionStack[sectionStack.length - 1]).push(markdown.substring(lastIndex));
 
     let markdownSections = '';
 
     // flatten the hierarchical stack, and insert <section data-markdown> tags
-    for ( let i = 0, len = sectionStack.length; i < len; i++ ) {
+    for (let i = 0, len = sectionStack.length; i < len; i++) {
       // vertical
-      if ( sectionStack[i] instanceof Array ) {
-        markdownSections += '<section '+ options.attributes +'>';
+      if (sectionStack[i] instanceof Array) {
+        markdownSections += `<section ${options.attributes}>`;
 
-        sectionStack[i].forEach( function( child ) {
-          markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
-        } );
+        sectionStack[i].forEach((child) => {
+          markdownSections += `<section data-markdown>${createMarkdownSlide(child, options)}</section>`;
+        });
 
         markdownSections += '</section>';
       }
       else {
-        markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
+        markdownSections += `<section ${options.attributes} data-markdown>${createMarkdownSlide(sectionStack[i], options)}</section>`;
       }
     }
 
     return markdownSections;
-
   }
 
   /**
@@ -188,76 +199,69 @@ export default function( marked ) {
    * handles loading of external markdown.
    */
   function processSlides() {
+    const sections = document.querySelectorAll('[data-markdown]');
 
-    let sections = document.querySelectorAll( '[data-markdown]'),
-      section;
 
-    for ( let i = 0, len = sections.length; i < len; i++ ) {
+    let section;
 
+    for (let i = 0, len = sections.length; i < len; i++) {
       section = sections[i];
 
-      if ( section.getAttribute( 'data-markdown' ).length ) {
+      if (section.getAttribute('data-markdown').length) {
+        const xhr = new XMLHttpRequest();
+
 
-        let xhr = new XMLHttpRequest(),
-          url = section.getAttribute( 'data-markdown' );
+        const url = section.getAttribute('data-markdown');
 
-        let datacharset = section.getAttribute( 'data-charset' );
+        const datacharset = section.getAttribute('data-charset');
 
         // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
-        if ( datacharset != null && datacharset != '' ) {
-          xhr.overrideMimeType( 'text/html; charset=' + datacharset );
+        if (datacharset != null && datacharset != '') {
+          xhr.overrideMimeType(`text/html; charset=${datacharset}`);
         }
 
         xhr.onreadystatechange = function() {
-          if ( xhr.readyState === 4 ) {
+          if (xhr.readyState === 4) {
             // file protocol yields status code 0 (useful for local debug, mobile applications etc.)
-            if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
-
-              section.outerHTML = slidify( xhr.responseText, {
-                separator: section.getAttribute( 'data-separator' ),
-                verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
-                notesSeparator: section.getAttribute( 'data-separator-notes' ),
-                attributes: getForwardedAttributes( section )
+            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
+              section.outerHTML = slidify(xhr.responseText, {
+                separator: section.getAttribute('data-separator'),
+                verticalSeparator: section.getAttribute('data-separator-vertical'),
+                notesSeparator: section.getAttribute('data-separator-notes'),
+                attributes: getForwardedAttributes(section),
               });
-
             }
             else {
-
-              section.outerHTML = '<section data-state="alert">' +
-                'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
-                'Check your browser\'s JavaScript console for more details.' +
-                '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
-                '</section>';
-
+              section.outerHTML = `${'<section data-state="alert">'
+                + 'ERROR: The attempt to fetch '}${url} failed with HTTP status ${xhr.status}.`
+                + 'Check your browser\'s JavaScript console for more details.'
+                + '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>'
+                + '</section>';
             }
           }
         };
 
-        xhr.open( 'GET', url, false );
+        xhr.open('GET', url, false);
 
         try {
           xhr.send();
         }
-        catch ( e ) {
-          alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
+        catch (e) {
+          alert(`Failed to get the Markdown file ${url}. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ${e}`);
         }
-
       }
-      else if ( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
-
-        section.outerHTML = slidify( getMarkdownFromSlide( section ), {
-          separator: section.getAttribute( 'data-separator' ),
-          verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
-          notesSeparator: section.getAttribute( 'data-separator-notes' ),
-          attributes: getForwardedAttributes( section )
+      else if (section.getAttribute('data-separator') || section.getAttribute('data-separator-vertical') || section.getAttribute('data-separator-notes')) {
+        section.outerHTML = slidify(getMarkdownFromSlide(section), {
+          separator: section.getAttribute('data-separator'),
+          verticalSeparator: section.getAttribute('data-separator-vertical'),
+          notesSeparator: section.getAttribute('data-separator-notes'),
+          attributes: getForwardedAttributes(section),
         });
-
       }
       else {
-        section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
+        section.innerHTML = createMarkdownSlide(getMarkdownFromSlide(section));
       }
     }
-
   }
 
   /**
@@ -269,20 +273,18 @@ export default function( marked ) {
    * directly on refresh (F5)
    * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
    */
-  function addAttributeInElement( node, elementTarget, separator ) {
-
-    let mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
-    let mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' );
+  function addAttributeInElement(node, elementTarget, separator) {
+    const mardownClassesInElementsRegex = new RegExp(separator, 'mg');
+    const mardownClassRegex = new RegExp('([^"= ]+?)="([^"=]+?)"', 'mg');
     let nodeValue = node.nodeValue;
-    let matches = mardownClassesInElementsRegex.exec( nodeValue );
-    if ( matches != null ) {
-
-      let classes = matches[1];
-      nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
+    const matches = mardownClassesInElementsRegex.exec(nodeValue);
+    if (matches != null) {
+      const classes = matches[1];
+      nodeValue = nodeValue.substring(0, matches.index) + nodeValue.substring(mardownClassesInElementsRegex.lastIndex);
       node.nodeValue = nodeValue;
       let matchesClass;
-      while ( (matchesClass = mardownClassRegex.exec( classes )) != null ) {
-        elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
+      while ((matchesClass = mardownClassRegex.exec(classes)) != null) {
+        elementTarget.setAttribute(matchesClass[1], matchesClass[2]);
       }
       return true;
     }
@@ -293,37 +295,36 @@ export default function( marked ) {
    * Add attributes to the parent element of a text node,
    * or the element of an attribute node.
    */
-  function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
-
-    if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
+  function addAttributes(section, element, previousElement, separatorElementAttributes, separatorSectionAttributes) {
+    if (element != null && element.childNodes != undefined && element.childNodes.length > 0) {
       let previousParentElement = element;
-      for ( let i = 0; i < element.childNodes.length; i++ ) {
-        let childElement = element.childNodes[i];
-        if ( i > 0 ) {
+      for (let i = 0; i < element.childNodes.length; i++) {
+        const childElement = element.childNodes[i];
+        if (i > 0) {
           let j = i - 1;
-          while ( j >= 0 ) {
-            let aPreviousChildElement = element.childNodes[j];
-            if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != 'BR' ) {
+          while (j >= 0) {
+            const aPreviousChildElement = element.childNodes[j];
+            if (typeof aPreviousChildElement.setAttribute === 'function' && aPreviousChildElement.tagName != 'BR') {
               previousParentElement = aPreviousChildElement;
               break;
             }
-            j = j - 1;
+            j -= 1;
           }
         }
         let parentSection = section;
-        if ( childElement.nodeName ==  'section' ) {
-          parentSection = childElement ;
-          previousParentElement = childElement ;
+        if (childElement.nodeName == 'section') {
+          parentSection = childElement;
+          previousParentElement = childElement;
         }
-        if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
-          addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
+        if (typeof childElement.setAttribute === 'function' || childElement.nodeType == Node.COMMENT_NODE) {
+          addAttributes(parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes);
         }
       }
     }
 
-    if ( element.nodeType == Node.COMMENT_NODE ) {
-      if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
-        addAttributeInElement( element, section, separatorSectionAttributes );
+    if (element.nodeType == Node.COMMENT_NODE) {
+      if (addAttributeInElement(element, previousElement, separatorElementAttributes) == false) {
+        addAttributeInElement(element, section, separatorSectionAttributes);
       }
     }
   }
@@ -333,47 +334,41 @@ export default function( marked ) {
    * DOM to HTML.
    */
   function convertSlides() {
+    const sections = document.querySelectorAll('[data-markdown]');
 
-    let sections = document.querySelectorAll( '[data-markdown]');
-
-    for ( let i = 0, len = sections.length; i < len; i++ ) {
-
-      let section = sections[i];
+    for (let i = 0, len = sections.length; i < len; i++) {
+      const section = sections[i];
 
       // Only parse the same slide once
-      if ( !section.getAttribute( 'data-markdown-parsed' ) ) {
-
-        section.setAttribute( 'data-markdown-parsed', true );
+      if (!section.getAttribute('data-markdown-parsed')) {
+        section.setAttribute('data-markdown-parsed', true);
 
-        let notes = section.querySelector( 'aside.notes' );
-        let markdown = getMarkdownFromSlide( section );
+        const notes = section.querySelector('aside.notes');
+        const markdown = getMarkdownFromSlide(section);
 
-        section.innerHTML = marked( markdown );
-        addAttributes(   section, section, null, section.getAttribute( 'data-element-attributes' ) ||
-                section.parentNode.getAttribute( 'data-element-attributes' ) ||
-                DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
-        section.getAttribute( 'data-attributes' ) ||
-                section.parentNode.getAttribute( 'data-attributes' ) ||
-                DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
+        section.innerHTML = marked(markdown);
+        addAttributes(section, section, null, section.getAttribute('data-element-attributes')
+                || section.parentNode.getAttribute('data-element-attributes')
+                || DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
+        section.getAttribute('data-attributes')
+                || section.parentNode.getAttribute('data-attributes')
+                || DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
 
         // If there were notes, we need to re-add them after
         // having overwritten the section's HTML
-        if ( notes ) {
-          section.appendChild( notes );
+        if (notes) {
+          section.appendChild(notes);
         }
-
       }
-
     }
-
   }
 
   // API
   return {
-    getMarkdownFromSlide: getMarkdownFromSlide,
-    createMarkdownSlide: createMarkdownSlide,
-    processSlides: processSlides,
-    addAttributes: addAttributes,
-    convertSlides: convertSlides
+    getMarkdownFromSlide,
+    createMarkdownSlide,
+    processSlides,
+    addAttributes,
+    convertSlides,
   };
 }