Browse Source

Merge pull request #876 from weseek/support/apply-eslint-to-jsx

Support/apply eslint to jsx
Yuki Takei 7 years ago
parent
commit
596b1d6b4f

+ 7 - 3
.eslintrc.js

@@ -1,8 +1,13 @@
 module.exports = {
   parser: 'babel-eslint',
+  parserOptions: {
+    ecmaFeatures: {
+      jsx: true
+    }
+  },
   extends: [
-    'airbnb',
-    'plugin:react/recommended',
+    'airbnb-base',
+    'airbnb/rules/react',
   ],
   env: {
     browser: true,
@@ -113,7 +118,6 @@ module.exports = {
     'react/require-default-props': 'off',
     'react/self-closing-comp': 'off',
     'react/sort-comp': 'off',
-    'jsx-a11y/img-redundant-alt': 'off',
     // eslint-plugin-import rules
     'import/no-extraneous-dependencies': 'off',
     'import/no-dynamic-require': 'off',

+ 2 - 7
package.json

@@ -32,8 +32,8 @@
     "clean:report": "rimraf -- report",
     "clean": "npm-run-all -p clean:*",
     "heroku-postbuild": "sh bin/heroku/install-plugins.sh && npm run build:prod",
-    "lint:js:fix": "eslint . --fix",
-    "lint:js": "eslint .",
+    "lint:js:fix": "eslint **/*.{js,jsx} --fix",
+    "lint:js": "eslint **/*.{js,jsx}",
     "lint:styles:fix": "prettier-stylelint --quiet --write src/client/styles/scss/**/*.scss",
     "lint:styles": "stylelint src/client/styles/scss/**/*.scss",
     "lint": "npm-run-all -p lint:js lint:styles",
@@ -232,10 +232,5 @@
     "node": ">=8.11.1 <11",
     "npm": ">=5.6.0 <7",
     "yarn": ">=1.5.1 <2"
-  },
-  "config": {
-    "blanket": {
-      "pattern": "./src/lib/**/*.js"
-    }
   }
 }

+ 1 - 0
src/client/js/components/BookmarkButton.jsx

@@ -74,6 +74,7 @@ export default class BookmarkButton extends React.Component {
 
     return (
       <button
+        type="button"
         href="#"
         title="Bookmark"
         onClick={this.handleClick}

+ 0 - 1
src/client/js/components/Common/UserPictureList.jsx

@@ -20,7 +20,6 @@ export default class UserPictureList extends React.Component {
 
     this.state = {
       users,
-      tooltipUsername: '',
     };
 
   }

+ 3 - 3
src/client/js/components/HeaderSearchBox.jsx

@@ -46,17 +46,17 @@ class HeaderSearchBox extends React.Component {
   }
 
   search() {
-    const url = new URL(location.href);
+    const url = new URL(window.location.href);
     url.pathname = '/_search';
 
     // construct search query
     let q = this.state.text;
     if (this.state.isScopeChildren) {
-      q += ` prefix:${location.pathname}`;
+      q += ` prefix:${window.location.pathname}`;
     }
     url.searchParams.append('q', q);
 
-    location.href = url.href;
+    window.location.href = url.href;
   }
 
   render() {

+ 4 - 1
src/client/js/components/InstallerForm.jsx

@@ -36,7 +36,10 @@ class InstallerForm extends React.Component {
 
   render() {
     const hasErrorClass = this.state.isValidUserName ? '' : ' has-error';
-    const unavailableUserId = this.state.isValidUserName ? '' : <span><i className="icon-fw icon-ban" />{ this.props.t('installer.unavaliable_user_id') }</span>;
+    const unavailableUserId = this.state.isValidUserName
+      ? ''
+      : <span><i className="icon-fw icon-ban" />{ this.props.t('installer.unavaliable_user_id') }</span>;
+
     return (
       <div className={`login-dialog p-t-10 p-b-10 col-sm-offset-4 col-sm-4${hasErrorClass}`}>
         <p className="alert alert-success">

+ 1 - 0
src/client/js/components/LikeButton.jsx

@@ -51,6 +51,7 @@ export default class LikeButton extends React.Component {
 
     return (
       <button
+        type="button"
         href="#"
         title="Like"
         onClick={this.handleClick}

+ 8 - 3
src/client/js/components/Page.jsx

@@ -31,11 +31,16 @@ export default class Page extends React.Component {
   launchHandsontableModal(beginLineNumber, endLineNumber) {
     const tableLines = this.state.markdown.split(/\r\n|\r|\n/).slice(beginLineNumber - 1, endLineNumber).join('\n');
     this.setState({ currentTargetTableArea: { beginLineNumber, endLineNumber } });
-    this.refs.handsontableModal.show(MarkdownTable.fromMarkdownString(tableLines));
+    this.handsontableModal.show(MarkdownTable.fromMarkdownString(tableLines));
   }
 
   saveHandlerForHandsontableModal(markdownTable) {
-    const newMarkdown = mtu.replaceMarkdownTableInMarkdown(markdownTable, this.state.markdown, this.state.currentTargetTableArea.beginLineNumber, this.state.currentTargetTableArea.endLineNumber);
+    const newMarkdown = mtu.replaceMarkdownTableInMarkdown(
+      markdownTable,
+      this.state.markdown,
+      this.state.currentTargetTableArea.beginLineNumber,
+      this.state.currentTargetTableArea.endLineNumber,
+    );
     this.props.onSaveWithShortcut(newMarkdown);
     this.setState({ currentTargetTableArea: null });
   }
@@ -51,7 +56,7 @@ export default class Page extends React.Component {
           markdown={this.state.markdown}
           pagePath={this.props.pagePath}
         />
-        <HandsontableModal ref="handsontableModal" onSave={this.saveHandlerForHandsontableModal} />
+        <HandsontableModal ref={(c) => { this.handsontableModal = c }} onSave={this.saveHandlerForHandsontableModal} />
       </div>
     );
   }

+ 1 - 1
src/client/js/components/Page/RevisionRenderer.jsx

@@ -77,7 +77,7 @@ export default class RevisionRenderer extends React.Component {
       .then(() => { return interceptorManager.process('postPostProcess', context) })
       .then(() => { return interceptorManager.process('preRenderHtml', context) })
       .then(() => {
-        this.setState({ html: context.parsedHTML, markdown });
+        this.setState({ html: context.parsedHTML });
       })
       // process interceptors for post rendering
       .then(() => { return interceptorManager.process('postRenderHtml', context) });

+ 14 - 10
src/client/js/components/PageComment/CommentForm.jsx

@@ -82,7 +82,7 @@ export default class CommentForm extends React.Component {
     const value = event.target.checked;
     this.setState({ isMarkdown: value });
     // changeMode
-    this.refs.editor.setGfmMode(value);
+    this.editor.setGfmMode(value);
   }
 
   handleSelect(key) {
@@ -132,7 +132,7 @@ export default class CommentForm extends React.Component {
           isSlackEnabled: false,
         });
         // reset value
-        this.refs.editor.setValue('');
+        this.editor.setValue('');
       })
       .catch((err) => {
         const errorMessage = err.message || 'An unknown error occured when posting comment';
@@ -143,7 +143,7 @@ export default class CommentForm extends React.Component {
   getCommentHtml() {
     return (
       <CommentPreview
-        inputRef={(el) => { return this.previewElement = el }}
+        inputRef={(el) => { this.previewElement = el }}
         html={this.state.html}
       />
     );
@@ -205,12 +205,12 @@ export default class CommentForm extends React.Component {
           // modify to "![fileName](url)" syntax
           insertText = `!${insertText}`;
         }
-        this.refs.editor.insertText(insertText);
+        this.editor.insertText(insertText);
       })
       .catch(this.apiErrorHandler)
       // finally
       .then(() => {
-        this.refs.editor.terminateUploadingState();
+        this.editor.terminateUploadingState();
       });
   }
 
@@ -271,7 +271,11 @@ export default class CommentForm extends React.Component {
                 {/* Add Comment Button */}
                 { !this.state.isFormShown
                   && (
-                  <button className={`btn btn-lg ${isLayoutTypeGrowi ? 'btn-link' : 'btn-primary'} center-block`} onClick={this.showCommentFormBtnClickHandler}>
+                  <button
+                    type="button"
+                    className={`btn btn-lg ${isLayoutTypeGrowi ? 'btn-link' : 'btn-primary'} center-block`}
+                    onClick={this.showCommentFormBtnClickHandler}
+                  >
                     <i className="icon-bubble"></i> Add Comment
                   </button>
                   )
@@ -284,7 +288,7 @@ export default class CommentForm extends React.Component {
                       <Tabs activeKey={this.state.key} id="comment-form-tabs" onSelect={this.handleSelect} animation={false}>
                         <Tab eventKey={1} title="Write">
                           <Editor
-                            ref="editor"
+                            ref={(c) => { this.editor = c }}
                             value={this.state.comment}
                             isGfmMode={this.state.isMarkdown}
                             editorOptions={this.props.editorOptions}
@@ -298,7 +302,7 @@ export default class CommentForm extends React.Component {
                             onCtrlEnter={this.postComment}
                           />
                         </Tab>
-                        { this.state.isMarkdown == true
+                        { this.state.isMarkdown
                           && (
                           <Tab eventKey={2} title="Preview">
                             <div className="comment-form-preview">
@@ -312,7 +316,7 @@ export default class CommentForm extends React.Component {
                     <div className="comment-submit">
                       <div className="d-flex">
                         <label style={{ flex: 1 }}>
-                          { isLayoutTypeGrowi && this.state.key == 1
+                          { isLayoutTypeGrowi && this.state.key === 1
                             && (
                             <span>
                               <input
@@ -323,7 +327,7 @@ export default class CommentForm extends React.Component {
                                 value="1"
                                 onChange={this.updateStateCheckbox}
                               />
-                              Markdown
+                              <span className="ml-2">Markdown</span>
                             </span>
                             )
                         }

+ 9 - 8
src/client/js/components/PageEditor/Editor.jsx

@@ -39,8 +39,8 @@ export default class Editor extends AbstractEditor {
 
   getEditorSubstance() {
     return this.props.isMobile
-      ? this.refs.taEditor
-      : this.refs.cmEditor;
+      ? this.taEditor
+      : this.cmEditor;
   }
 
   /**
@@ -161,7 +161,7 @@ export default class Editor extends AbstractEditor {
 
   dropHandler(accepted, rejected) {
     // rejected
-    if (accepted.length != 1) { // length should be 0 or 1 because `multiple={false}` is set
+    if (accepted.length !== 1) { // length should be 0 or 1 because `multiple={false}` is set
       this.setState({ dropzoneActive: false });
       return;
     }
@@ -213,7 +213,8 @@ export default class Editor extends AbstractEditor {
       <div className="m-0 navbar navbar-default navbar-editor" style={{ minHeight: 'unset' }}>
         <ul className="pl-2 nav nav-navbar">
           { this.getNavbarItems() != null && this.getNavbarItems().map((item, idx) => {
-            return <li key={idx}>{item}</li>;
+            // eslint-disable-next-line react/no-array-index-key
+            return <li key={`navbarItem-${idx}`}>{item}</li>;
           }) }
         </ul>
       </div>
@@ -240,7 +241,7 @@ export default class Editor extends AbstractEditor {
     return (
       <div style={flexContainer} className="editor-container">
         <Dropzone
-          ref="dropzone"
+          ref={(c) => { this.dropzone = c }}
           disableClick
           accept={this.getAcceptableType()}
           className={this.getDropzoneClassName()}
@@ -258,7 +259,7 @@ export default class Editor extends AbstractEditor {
           {/* for PC */}
           { !isMobile && (
             <CodeMirrorEditor
-              ref="cmEditor"
+              ref={(c) => { this.cmEditor = c }}
               onPasteFiles={this.pasteFilesHandler}
               onDragEnter={this.dragEnterHandler}
               {...this.props}
@@ -269,7 +270,7 @@ export default class Editor extends AbstractEditor {
           {/* for mobile */}
           { isMobile && (
             <TextAreaEditor
-              ref="taEditor"
+              ref={(c) => { this.taEditor = c }}
               onPasteFiles={this.pasteFilesHandler}
               onDragEnter={this.dragEnterHandler}
               {...this.props}
@@ -284,7 +285,7 @@ export default class Editor extends AbstractEditor {
           <button
             type="button"
             className="btn btn-default btn-block btn-open-dropzone"
-            onClick={() => { this.refs.dropzone.open() }}
+            onClick={() => { this.dropzone.open() }}
           >
 
             <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;

+ 12 - 11
src/client/js/components/PageEditor/HandsontableModal.jsx

@@ -153,7 +153,7 @@ export default class HandsontableModal extends React.PureComponent {
 
   save() {
     const markdownTable = new MarkdownTable(
-      this.refs.hotTable.hotInstance.getData(),
+      this.hotTable.hotInstance.getData(),
       { align: [].concat(this.state.markdownTable.options.align) },
     ).normalizeCells();
 
@@ -180,7 +180,8 @@ export default class HandsontableModal extends React.PureComponent {
    * In detail, when the setState method is called with those state passed,
    * React will start re-render process for the HotTable of this component because the HotTable receives those state values by props.
    * HotTable#shouldComponentUpdate is called in this re-render process and calls the updateSettings method for the Handsontable instance.
-   * In updateSettings method, the loadData method is called in some case. (refs: https://github.com/handsontable/handsontable/blob/6.2.0/src/core.js#L1652-L1657)
+   * In updateSettings method, the loadData method is called in some case.
+   *  (refs: https://github.com/handsontable/handsontable/blob/6.2.0/src/core.js#L1652-L1657)
    * The updateSettings method calls in the HotTable always lead to call the loadData method because the HotTable passes data source by settings.data.
    * After the loadData method is executed, afterLoadData hooks are called.
    */
@@ -215,7 +216,7 @@ export default class HandsontableModal extends React.PureComponent {
     // store column index
     this.manuallyResizedColumnIndicesSet.add(currentColumn);
     // force re-render
-    const hotInstance = this.refs.hotTable.hotInstance;
+    const hotInstance = this.hotTable.hotInstance;
     hotInstance.render();
   }
 
@@ -319,12 +320,12 @@ export default class HandsontableModal extends React.PureComponent {
    * synchronize the handsontable alignment to the markdowntable alignment
    */
   synchronizeAlignment() {
-    if (this.refs.hotTable == null) {
+    if (this.hotTable == null) {
       return;
     }
 
     const align = this.state.markdownTable.options.align;
-    const hotInstance = this.refs.hotTable.hotInstance;
+    const hotInstance = this.hotTable.hotInstance;
 
     for (let i = 0; i < align.length; i++) {
       for (let j = 0; j < hotInstance.countRows(); j++) {
@@ -335,7 +336,7 @@ export default class HandsontableModal extends React.PureComponent {
   }
 
   alignButtonHandler(direction) {
-    const selectedRange = this.refs.hotTable.hotInstance.getSelectedRange();
+    const selectedRange = this.hotTable.hotInstance.getSelectedRange();
     if (selectedRange == null) return;
 
     let startCol;
@@ -387,8 +388,8 @@ export default class HandsontableModal extends React.PureComponent {
    *  according to the height of this.refs.hotTableContainer
    */
   expandHotTableHeight() {
-    if (this.state.isWindowExpanded && this.refs.hotTableContainer != null) {
-      const height = this.refs.hotTableContainer.getBoundingClientRect().height;
+    if (this.state.isWindowExpanded && this.hotTableContainer != null) {
+      const height = this.hotTableContainer.getBoundingClientRect().height;
       this.setState({ handsontableHeight: height });
     }
   }
@@ -396,7 +397,7 @@ export default class HandsontableModal extends React.PureComponent {
   renderExpandOrContractButton() {
     const iconClassName = this.state.isWindowExpanded ? 'icon-size-actual' : 'icon-size-fullscreen';
     return (
-      <button className="close mr-3" onClick={this.state.isWindowExpanded ? this.contractWindow : this.expandWindow}>
+      <button type="button" className="close mr-3" onClick={this.state.isWindowExpanded ? this.contractWindow : this.expandWindow}>
         <i className={iconClassName} style={{ fontSize: '0.8em' }} aria-hidden="true"></i>
       </button>
     );
@@ -432,9 +433,9 @@ export default class HandsontableModal extends React.PureComponent {
               </div>
             </Collapse>
           </div>
-          <div ref="hotTableContainer" className="m-4 hot-table-container">
+          <div ref={(c) => { this.hotTableContainer = c }} className="m-4 hot-table-container">
             <HotTable
-              ref="hotTable"
+              ref={(c) => { this.hotTable = c }}
               data={this.state.markdownTable.table}
               settings={this.handsontableSettings}
               height={this.state.handsontableHeight}

+ 18 - 6
src/client/js/components/PageEditorByHackmd.jsx

@@ -44,7 +44,7 @@ export default class PageEditorByHackmd extends React.PureComponent {
       return Promise.reject(new Error('HackmdEditor component has not initialized'));
     }
 
-    return this.refs.hackmdEditor.getValue()
+    return this.hackmdEditor.getValue()
       .then((document) => {
         this.setState({ markdown: document });
         return document;
@@ -54,7 +54,7 @@ export default class PageEditorByHackmd extends React.PureComponent {
   setMarkdown(markdown, updateEditorValue = true) {
     this.setState({ markdown });
     if (this.state.isInitialized && updateEditorValue) {
-      this.refs.hackmdEditor.setValue(markdown);
+      this.hackmdEditor.setValue(markdown);
     }
   }
 
@@ -73,7 +73,6 @@ export default class PageEditorByHackmd extends React.PureComponent {
       initialRevisionId: updatedRevisionId,
       revisionId: updatedRevisionId,
       revisionIdHackmdSynced: updatedRevisionIdHackmdSynced,
-      isDraftUpdatingInRealtime: false,
     });
   }
 
@@ -202,7 +201,7 @@ export default class PageEditorByHackmd extends React.PureComponent {
     if (this.state.isInitialized) {
       return (
         <HackmdEditor
-          ref="hackmdEditor"
+          ref={(c) => { this.hackmdEditor = c }}
           hackmdUri={hackmdUri}
           pageIdOnHackmd={this.state.pageIdOnHackmd}
           initializationMarkdown={isResume ? null : this.state.markdown}
@@ -259,7 +258,14 @@ export default class PageEditorByHackmd extends React.PureComponent {
           </div>
           <p className="text-center">
             Click to edit from the previous continuation<br />
-            or <button className="btn btn-link text-danger p-0 hackmd-discard-button" onClick={() => { return this.discardChanges() }}>Discard changes</button>.
+            or
+            <button
+              type="button"
+              className="btn btn-link text-danger p-0 hackmd-discard-button"
+              onClick={() => { return this.discardChanges() }}
+            >
+              Discard changes
+            </button>.
           </p>
           { isHackmdDocumentOutdated
             && (
@@ -268,7 +274,13 @@ export default class PageEditorByHackmd extends React.PureComponent {
               <div className="panel-body text-center">
                 The current draft on HackMD is based on&nbsp;
                 <a href={`?revision=${revisionIdHackmdSynced}`}><span className="label label-default">{revisionIdHackmdSynced.substr(-8)}</span></a>.<br />
-                <button className="btn btn-link text-danger p-0 hackmd-discard-button" onClick={() => { return this.discardChanges() }}>Discard it</button> to start to edit with current revision.
+                <button
+                  type="button"
+                  className="btn btn-link text-danger p-0 hackmd-discard-button"
+                  onClick={() => { return this.discardChanges() }}
+                >
+                  Discard it
+                </button> to start to edit with current revision.
               </div>
             </div>
             )

+ 2 - 2
src/client/js/components/PageEditorByHackmd/HackmdEditor.jsx

@@ -32,7 +32,7 @@ export default class HackmdEditor extends React.PureComponent {
 
     const connection = Penpal.connectToChild({
       url,
-      appendTo: this.refs.iframeContainer,
+      appendTo: this.iframeContainer,
       methods: { // expose methods to HackMD
         notifyBodyChanges(document) {
           _this.notifyBodyChangesHandler(document);
@@ -78,7 +78,7 @@ export default class HackmdEditor extends React.PureComponent {
   render() {
     return (
       // will be rendered in componentDidMount
-      <div id="iframe-hackmd-container" ref="iframeContainer"></div>
+      <div id="iframe-hackmd-container" ref={(c) => { this.iframeContainer = c }}></div>
     );
   }
 

+ 3 - 3
src/client/js/components/PagePathAutoComplete.jsx

@@ -11,8 +11,8 @@ export default class PagePathAutoComplete extends React.Component {
     super(props);
 
     this.state = {
-      searchError: null,
     };
+
     this.crowi = this.props.crowi;
 
     this.onSubmit = this.onSubmit.bind(this);
@@ -27,7 +27,7 @@ export default class PagePathAutoComplete extends React.Component {
 
   onSubmit(query) {
     // get the closest form element
-    const elem = this.refs.rootDom;
+    const elem = this.rootDom;
     const form = elem.closest('form');
     // submit with jQuery
     $(form).submit();
@@ -41,7 +41,7 @@ export default class PagePathAutoComplete extends React.Component {
 
   render() {
     return (
-      <div ref="rootDom">
+      <div ref={(c) => { this.rootDom = c }}>
         <SearchTypeahead
           ref={this.searchTypeaheadDom}
           crowi={this.crowi}

+ 5 - 1
src/client/js/components/PageStatusAlert.jsx

@@ -58,6 +58,10 @@ class PageStatusAlert extends React.Component {
     });
   }
 
+  refreshPage() {
+    window.location.reload();
+  }
+
   renderSomeoneEditingAlert() {
     return (
       <div className="alert-hackmd-someone-editing myadmin-alert alert-success myadmin-alert-bottom alertbottom2">
@@ -100,7 +104,7 @@ class PageStatusAlert extends React.Component {
         &nbsp;
         <i className="fa fa-angle-double-right"></i>
         &nbsp;
-        <a href="javascript:location.reload();">
+        <a onClick={this.refreshPage}>
           {label2}
         </a>
       </div>

+ 2 - 2
src/client/js/components/RecentCreated/RecentCreated.jsx

@@ -92,7 +92,7 @@ export default class RecentCreated extends React.Component {
    */
   generateFirstPrev(activePage) {
     const paginationItems = [];
-    if (activePage != 1) {
+    if (activePage !== 1) {
       paginationItems.push(
         <Pagination.First key="first" onClick={() => { return this.getRecentCreatedList(1) }} />,
       );
@@ -134,7 +134,7 @@ export default class RecentCreated extends React.Component {
    */
   generateNextLast(activePage, totalPage) {
     const paginationItems = [];
-    if (totalPage != activePage) {
+    if (totalPage !== activePage) {
       paginationItems.push(
         <Pagination.Next key="next" onClick={() => { return this.getRecentCreatedList(this.state.activePage + 1) }} />,
       );

+ 5 - 5
src/client/js/components/SavePageControls.jsx

@@ -27,8 +27,8 @@ class SavePageControls extends React.PureComponent {
   }
 
   getCurrentOptionsToSave() {
-    const slackNotificationOptions = this.refs.slackNotification.getCurrentOptionsToSave();
-    const grantSelectorOptions = this.refs.grantSelector.getCurrentOptionsToSave();
+    const slackNotificationOptions = this.slackNotification.getCurrentOptionsToSave();
+    const grantSelectorOptions = this.grantSelector.getCurrentOptionsToSave();
     return Object.assign(slackNotificationOptions, grantSelectorOptions);
   }
 
@@ -60,7 +60,7 @@ class SavePageControls extends React.PureComponent {
       <div className="d-flex align-items-center form-inline">
         <div className="mr-2">
           <SlackNotification
-            ref="slackNotification"
+            ref={(c) => { this.slackNotification = c }}
             crowi={this.props.crowi}
             pageId={this.props.pageId}
             pagePath={this.props.pagePath}
@@ -75,8 +75,8 @@ class SavePageControls extends React.PureComponent {
             <GrantSelector
               crowi={this.props.crowi}
               ref={(elem) => {
-                  if (this.refs.grantSelector == null) {
-                    this.refs.grantSelector = elem.getWrappedInstance();
+                  if (this.grantSelector == null) {
+                    this.grantSelector = elem.getWrappedInstance();
                   }
                 }}
               grant={this.props.grant}

+ 1 - 2
src/client/js/components/SavePageControls/GrantSelector.jsx

@@ -189,7 +189,6 @@ class GrantSelector extends React.Component {
     // add specified group option
     grantElems.push(
       <option
-        ref="specifiedGroupOption"
         key="specifiedGroupKey"
         value={SPECIFIED_GROUP_VALUE}
         style={{ display: grantGroup ? 'inherit' : 'none' }}
@@ -209,7 +208,7 @@ class GrantSelector extends React.Component {
           bsClass={bsClassName}
           className="btn-group-sm selectpicker"
           onChange={this.changeGrantHandler}
-          inputRef={(el) => { return this.grantSelectorInputEl = el }}
+          inputRef={(el) => { this.grantSelectorInputEl = el }}
         >
 
           {grantElems}

+ 2 - 2
src/client/js/components/SlackNotification.jsx

@@ -56,8 +56,8 @@ export default class SlackNotification extends React.Component {
     return (
       <div className="input-group input-group-sm input-group-slack extended-setting">
         <label className="input-group-addon">
-          <img id="slack-mark-white" src="/images/icons/slack/mark-monochrome_white.svg" width="18" height="18" />
-          <img id="slack-mark-black" src="/images/icons/slack/mark-monochrome_black.svg" width="18" height="18" />
+          <img id="slack-mark-white" alt="slack-mark" src="/images/icons/slack/mark-monochrome_white.svg" width="18" height="18" />
+          <img id="slack-mark-black" alt="slack-mark" src="/images/icons/slack/mark-monochrome_black.svg" width="18" height="18" />
           <input type="checkbox" value="1" checked={this.state.isSlackEnabled} onChange={this.updateStateCheckbox} />
         </label>
         <input