Browse Source

WIP: apply to src/client/js/components

Yuki Takei 7 years ago
parent
commit
9f630a8138

+ 4 - 0
.eslintrc.js

@@ -112,7 +112,11 @@ module.exports = {
       { extensions: ['.jsx']},
     ],
     'react/no-unused-prop-types': 'off',
+    'react/jsx-one-expression-per-line': 'off',
+    'react/no-access-state-in-setstate': 'off',
+    'react/prefer-stateless-function': 'off',
     'react/require-default-props': 'off',
+    'react/self-closing-comp': 'off',
     'react/sort-comp': 'off',
     // "react/jsx-uses-vars": 1,
     // "react/no-string-refs": "off",

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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