فهرست منبع

Merge branch 'support/apply-bootstrap4' into support/apply-booystrap4-adjust-checkbox

yusuketk 6 سال پیش
والد
کامیت
6442388b0a
21فایلهای تغییر یافته به همراه472 افزوده شده و 379 حذف شده
  1. 9 3
      src/client/js/components/Admin/Customize/CustomizeBehaviorOption.jsx
  2. 39 35
      src/client/js/components/Admin/Customize/CustomizeBehaviorSetting.jsx
  3. 24 19
      src/client/js/components/Admin/Customize/CustomizeCssSetting.jsx
  4. 3 2
      src/client/js/components/Admin/Customize/CustomizeFunctionOption.jsx
  5. 109 90
      src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx
  6. 33 29
      src/client/js/components/Admin/Customize/CustomizeHeaderSetting.jsx
  7. 63 44
      src/client/js/components/Admin/Customize/CustomizeHighlightSetting.jsx
  8. 9 3
      src/client/js/components/Admin/Customize/CustomizeLayoutOption.jsx
  9. 3 3
      src/client/js/components/Admin/Customize/CustomizeLayoutOptions.jsx
  10. 14 6
      src/client/js/components/Admin/Customize/CustomizeLayoutSetting.jsx
  11. 57 42
      src/client/js/components/Admin/Customize/CustomizeScriptSetting.jsx
  12. 34 20
      src/client/js/components/Admin/Customize/CustomizeTitle.jsx
  13. 7 7
      src/client/js/components/Admin/ElasticsearchManagement/ElasticsearchManagement.jsx
  14. 1 1
      src/client/js/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.jsx
  15. 1 1
      src/client/js/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx
  16. 1 1
      src/client/js/components/Admin/ElasticsearchManagement/ReconnectControls.jsx
  17. 16 18
      src/client/js/components/Admin/ElasticsearchManagement/StatusTable.jsx
  18. 2 2
      src/client/js/components/Admin/MarkdownSetting/WhiteListInput.jsx
  19. 17 14
      src/client/js/components/Admin/Notification/GlobalNotificationList.jsx
  20. 29 38
      src/client/js/components/Admin/Notification/SlackAppConfiguration.jsx
  21. 1 1
      src/client/js/components/Admin/Notification/UserTriggerNotification.jsx

+ 9 - 3
src/client/js/components/Admin/Customize/CustomizeBehaviorOption.jsx

@@ -9,9 +9,15 @@ class CustomizeBehaviorOption extends React.PureComponent {
     return (
       <React.Fragment>
         <h4>
-          <div className="radio radio-primary">
-            <input type="radio" id={`radioBehavior${this.props.behaviorType}`} checked={this.props.isSelected} onChange={this.props.onSelected} />
-            <label htmlFor={`radioBehavior${this.props.behaviorType}`}>
+          <div className="custom-control custom-radio">
+            <input
+              type="radio"
+              className="custom-control-input"
+              id={`radioBehavior${this.props.behaviorType}`}
+              checked={this.props.isSelected}
+              onChange={this.props.onSelected}
+            />
+            <label className="custom-control-label" htmlFor={`radioBehavior${this.props.behaviorType}`}>
               {/* eslint-disable-next-line react/no-danger */}
               <span dangerouslySetInnerHTML={{ __html: this.props.labelHtml }} />
             </label>

+ 39 - 35
src/client/js/components/Admin/Customize/CustomizeBehaviorSetting.jsx

@@ -38,44 +38,48 @@ class CustomizeBehaviorSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.behavior')}</h2>
         <div className="row">
-          <div className="col-xs-6">
-            <CustomizeBehaviorOption
-              behaviorType="growi"
-              isSelected={adminCustomizeContainer.state.currentBehavior === 'growi'}
-              onSelected={() => adminCustomizeContainer.switchBehaviorType('growi')}
-              labelHtml={`GROWI Simplified Behavior <small class="text-success">${t('admin:customize_setting.recommended')}</small>`}
-            >
-              <ul>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text1') }} /></li>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text2') }} /></li>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text3') }} /></li>
-              </ul>
-            </CustomizeBehaviorOption>
-          </div>
-
-          <div className="col-xs-6">
-            <CustomizeBehaviorOption
-              behaviorType="crowi-plus"
-              isSelected={adminCustomizeContainer.state.currentBehavior === 'crowi-plus'}
-              onSelected={() => adminCustomizeContainer.switchBehaviorType('crowi-plus')}
-              labelHtml="Crowi Classic Behavior"
-            >
-              <ul>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text1') }} /></li>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text2') }} /></li>
-                <ul>
-                  <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text3') }} /></li>
-                </ul>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text4') }} /></li>
-                <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text5') }} /></li>
-              </ul>
-            </CustomizeBehaviorOption>
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.behavior')}</h2>
+            <div className="row">
+              <div className="col-6">
+                <CustomizeBehaviorOption
+                  behaviorType="growi"
+                  isSelected={adminCustomizeContainer.state.currentBehavior === 'growi'}
+                  onSelected={() => adminCustomizeContainer.switchBehaviorType('growi')}
+                  labelHtml={`GROWI Simplified Behavior <small class="text-success">${t('admin:customize_setting.recommended')}</small>`}
+                >
+                  <ul>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text1') }} /></li>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text2') }} /></li>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.growi_text3') }} /></li>
+                  </ul>
+                </CustomizeBehaviorOption>
+              </div>
+
+              <div className="col-6">
+                <CustomizeBehaviorOption
+                  behaviorType="crowi-plus"
+                  isSelected={adminCustomizeContainer.state.currentBehavior === 'crowi-plus'}
+                  onSelected={() => adminCustomizeContainer.switchBehaviorType('crowi-plus')}
+                  labelHtml="Crowi Classic Behavior"
+                >
+                  <ul>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text1') }} /></li>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text2') }} /></li>
+                    <ul>
+                      <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text3') }} /></li>
+                    </ul>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text4') }} /></li>
+                    <li><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.behavior_desc.crowi_text5') }} /></li>
+                  </ul>
+                </CustomizeBehaviorOption>
+              </div>
+            </div>
+
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 24 - 19
src/client/js/components/Admin/Customize/CustomizeCssSetting.jsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { Card, CardBody } from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -36,27 +37,31 @@ class CustomizeCssSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.custom_css')}</h2>
-        <p className="well">
-          {t('admin:customize_setting.write_css')}<br />
-          {t('admin:customize_setting.reflect_change')}
-        </p>
-        <div className="form-group">
-          <div className="col-xs-12">
-            <CustomCssEditor
-              value={adminCustomizeContainer.state.currentCustomizeCss || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeCss(inputValue) }}
-            />
-          </div>
-          <div className="col-xs-12">
-            <p className="help-block text-right">
-              <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
-              {t('admin:customize_setting.ctrl_space')}
-            </p>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_css')}</h2>
+
+            <Card className="card well my-3">
+              <CardBody className="px-0 py-2">
+                { t('admin:customize_setting.write_css') }<br />
+                { t('admin:customize_setting.reflect_change') }
+              </CardBody>
+            </Card>
+
+            <div className="form-group">
+              <CustomCssEditor
+                value={adminCustomizeContainer.state.currentCustomizeCss || ''}
+                onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeCss(inputValue) }}
+              />
+              <p className="form-text text-muted text-right">
+                <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
+                {t('admin:customize_setting.ctrl_space')}
+              </p>
+            </div>
+
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 3 - 2
src/client/js/components/Admin/Customize/CustomizeFunctionOption.jsx

@@ -7,14 +7,15 @@ class CustomizeFunctionOption extends React.PureComponent {
   render() {
     return (
       <React.Fragment>
-        <div className="checkbox checkbox-success">
+        <div className="custom-control custom-switch checkbox-success">
           <input
+            className="custom-control-input"
             type="checkbox"
             id={this.props.optionId}
             checked={this.props.isChecked}
             onChange={this.props.onChecked}
           />
-          <label htmlFor={this.props.optionId}>
+          <label className="custom-control-label" htmlFor={this.props.optionId}>
             <strong>{this.props.label}</strong>
           </label>
         </div>

+ 109 - 90
src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx

@@ -1,6 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import {
+  Card, CardBody,
+  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
+} from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -16,9 +20,18 @@ class CustomizeBehaviorSetting extends React.Component {
   constructor(props) {
     super(props);
 
+    this.state = {
+      isDropdownOpen: false,
+    };
+
+    this.onToggleDropdown = this.onToggleDropdown.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
   }
 
+  onToggleDropdown() {
+    this.setState({ isDropdownOpen: !this.state.isDropdownOpen });
+  }
+
   async onClickSubmit() {
     const { t, adminCustomizeContainer } = this.props;
 
@@ -36,104 +49,110 @@ class CustomizeBehaviorSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.function')}</h2>
-        <p className="well">{t('admin:customize_setting.function_desc')}</p>
-
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isEnabledTimeline"
-              label={t('admin:customize_setting.function_options.timeline')}
-              isChecked={adminCustomizeContainer.state.isEnabledTimeline}
-              onChecked={() => { adminCustomizeContainer.switchEnableTimeline() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.timeline_desc1')}<br />
-                {t('admin:customize_setting.function_options.timeline_desc2')}<br />
-                {t('admin:customize_setting.function_options.timeline_desc3')}
-              </p>
-            </CustomizeFunctionOption>
-          </div>
-        </div>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.function')}</h2>
+            <Card className="card well my-3">
+              <CardBody className="px-0 py-2">
+                {t('admin:customize_setting.function_desc')}
+              </CardBody>
+            </Card>
+
+
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isEnabledTimeline"
+                  label={t('admin:customize_setting.function_options.timeline')}
+                  isChecked={adminCustomizeContainer.state.isEnabledTimeline}
+                  onChecked={() => { adminCustomizeContainer.switchEnableTimeline() }}
+                >
+                  <p className="form-text text-muted">
+                    {t('admin:customize_setting.function_options.timeline_desc1')}<br />
+                    {t('admin:customize_setting.function_options.timeline_desc2')}<br />
+                    {t('admin:customize_setting.function_options.timeline_desc3')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isSavedStatesOfTabChanges"
-              label={t('admin:customize_setting.function_options.tab_switch')}
-              isChecked={adminCustomizeContainer.state.isSavedStatesOfTabChanges}
-              onChecked={() => { adminCustomizeContainer.switchSavedStatesOfTabChanges() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.tab_switch_desc1')}<br />
-                {t('admin:customize_setting.function_options.tab_switch_desc2')}
-              </p>
-            </CustomizeFunctionOption>
-          </div>
-        </div>
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isSavedStatesOfTabChanges"
+                  label={t('admin:customize_setting.function_options.tab_switch')}
+                  isChecked={adminCustomizeContainer.state.isSavedStatesOfTabChanges}
+                  onChecked={() => { adminCustomizeContainer.switchSavedStatesOfTabChanges() }}
+                >
+                  <p className="form-text text-muted">
+                    {t('admin:customize_setting.function_options.tab_switch_desc1')}<br />
+                    {t('admin:customize_setting.function_options.tab_switch_desc2')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isEnabledAttachTitleHeader"
-              label={t('admin:customize_setting.function_options.attach_title_header')}
-              isChecked={adminCustomizeContainer.state.isEnabledAttachTitleHeader}
-              onChecked={() => { adminCustomizeContainer.switchEnabledAttachTitleHeader() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.attach_title_header_desc')}
-              </p>
-            </CustomizeFunctionOption>
-          </div>
-        </div>
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isEnabledAttachTitleHeader"
+                  label={t('admin:customize_setting.function_options.attach_title_header')}
+                  isChecked={adminCustomizeContainer.state.isEnabledAttachTitleHeader}
+                  onChecked={() => { adminCustomizeContainer.switchEnabledAttachTitleHeader() }}
+                >
+                  <p className="form-text text-muted">
+                    {t('admin:customize_setting.function_options.attach_title_header_desc')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <div className="my-0 btn-group">
-              <label>{t('admin:customize_setting.function_options.recent_created__n_draft_num_desc')}</label>
-              <div className="dropdown">
-                <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                  <span className="pull-left">{adminCustomizeContainer.state.currentRecentCreatedLimit}</span>
-                  <span className="bs-caret pull-right">
-                    <span className="caret" />
-                  </span>
-                </button>
-                {/* TODO adjust dropdown after BS4 */}
-                <ul className="dropdown-menu" role="menu">
-                  <li key={10} role="presentation" type="button" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(10) }}>
-                    <a role="menuitem">10</a>
-                  </li>
-                  <li key={30} role="presentation" type="button" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(30) }}>
-                    <a role="menuitem">30</a>
-                  </li>
-                  <li key={50} role="presentation" type="button" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(50) }}>
-                    <a role="menuitem">50</a>
-                  </li>
-                </ul>
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <div className="my-0 w-100">
+                  <label>{t('admin:customize_setting.function_options.recent_created__n_draft_num_desc')}</label>
+                </div>
+                <Dropdown isOpen={this.state.isDropdownOpen} toggle={this.onToggleDropdown}>
+                  <DropdownToggle className="text-right col-6" caret>
+                    <span className="float-left">{adminCustomizeContainer.state.currentRecentCreatedLimit}</span>
+                  </DropdownToggle>
+                  {/* TODO adjust dropdown after BS4 */}
+                  <DropdownMenu className="dropdown-menu" role="menu">
+                    <DropdownItem key={10} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(10) }}>
+                      <a role="menuitem">10</a>
+                    </DropdownItem>
+                    <DropdownItem key={30} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(30) }}>
+                      <a role="menuitem">30</a>
+                    </DropdownItem>
+                    <DropdownItem key={50} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(50) }}>
+                      <a role="menuitem">50</a>
+                    </DropdownItem>
+                  </DropdownMenu>
+                </Dropdown>
+                <p className="form-text text-muted">
+                  {t('admin:customize_setting.function_options.recently_created_n_draft_num_desc')}
+                </p>
               </div>
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.recently_created_n_draft_num_desc')}
-              </p>
             </div>
-          </div>
-        </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <CustomizeFunctionOption
-              optionId="isEnabledStaleNotification"
-              label={t('admin:customize_setting.function_options.stale_notification')}
-              isChecked={adminCustomizeContainer.state.isEnabledStaleNotification}
-              onChecked={() => { adminCustomizeContainer.switchEnableStaleNotification() }}
-            >
-              <p className="help-block">
-                {t('admin:customize_setting.function_options.stale_notification_desc')}
-              </p>
-            </CustomizeFunctionOption>
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <CustomizeFunctionOption
+                  optionId="isEnabledStaleNotification"
+                  label={t('admin:customize_setting.function_options.stale_notification')}
+                  isChecked={adminCustomizeContainer.state.isEnabledStaleNotification}
+                  onChecked={() => { adminCustomizeContainer.switchEnableStaleNotification() }}
+                >
+                  <p className="form-text text-muted">
+                    {t('admin:customize_setting.function_options.stale_notification_desc')}
+                  </p>
+                </CustomizeFunctionOption>
+              </div>
+            </div>
+
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 33 - 29
src/client/js/components/Admin/Customize/CustomizeHeaderSetting.jsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { Card, CardBody } from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -36,36 +37,39 @@ class CustomizeHeaderSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.custom_header')}</h2>
-
-        <p
-          className="well"
-          // eslint-disable-next-line react/no-danger
-          dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_header_detail') }}
-        />
-
-        <div className="help-block">
-          {t('Example')}:
-          <pre className="hljs">
-            {/* eslint-disable-next-line react/no-unescaped-entities */}
-            <code>&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js" defer&gt;&lt;/script&gt;</code>
-          </pre>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_header')}</h2>
+
+            <Card className="card well my-3">
+              <CardBody className="px-0 py-2">
+                <span
+                  // eslint-disable-next-line react/no-danger
+                  dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_header_detail') }}
+                />
+              </CardBody>
+            </Card>
+            <div className="form-text text-muted">
+              { t('Example') }:
+              <pre className="hljs">
+                {/* eslint-disable-next-line react/no-unescaped-entities */}
+                <code>&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js" defer&gt;&lt;/script&gt;</code>
+              </pre>
+            </div>
+
+            <div className="form-group">
+              <CustomHeaderEditor
+                value={adminCustomizeContainer.state.currentCustomizeHeader || ''}
+                onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeHeader(inputValue) }}
+              />
+              <p className="form-text text-muted text-right">
+                <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
+                {t('admin:customize_setting.ctrl_space')}
+              </p>
+            </div>
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          </div>
         </div>
-
-        <div className="col-xs-12">
-          <CustomHeaderEditor
-            value={adminCustomizeContainer.state.currentCustomizeHeader || ''}
-            onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeHeader(inputValue) }}
-          />
-        </div>
-        <div className="col-xs-12">
-          <p className="help-block text-right">
-            <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
-            {t('admin:customize_setting.ctrl_space')}
-          </p>
-        </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 63 - 44
src/client/js/components/Admin/Customize/CustomizeHighlightSetting.jsx

@@ -2,6 +2,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import {
+  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
+} from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -16,9 +19,18 @@ class CustomizeHighlightSetting extends React.Component {
   constructor(props) {
     super(props);
 
+    this.state = {
+      isDropdownOpen: false,
+    };
+
+    this.onToggleDropdown = this.onToggleDropdown.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
   }
 
+  onToggleDropdown() {
+    this.setState({ isDropdownOpen: !this.state.isDropdownOpen });
+  }
+
   async onClickSubmit() {
     const { t, adminCustomizeContainer } = this.props;
 
@@ -64,62 +76,69 @@ class CustomizeHighlightSetting extends React.Component {
       const isBorderEnable = option[1].border;
 
       menuItem.push(
-        <li key={styleId} role="presentation" type="button" onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}>
-          <a role="button">{styleName}</a>
-        </li>,
+        <DropdownItem
+          key={styleId}
+          role="presentation"
+          onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}
+        >
+          <a role="menuitem">{styleName}</a>
+        </DropdownItem>,
       );
     });
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.code_highlight')}</h2>
-
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <div className="my-0 btn-group">
-              <label>{t('admin:customize_setting.theme')}</label>
-              <div className="dropdown">
-                <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                  <span className="pull-left">{adminCustomizeContainer.state.currentHighlightJsStyleName}</span>
-                  <span className="bs-caret pull-right">
-                    <span className="caret" />
-                  </span>
-                </button>
-                {/* TODO adjust dropdown after BS4 */}
-                <ul className="dropdown-menu" role="menu">
-                  {menuItem}
-                </ul>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.code_highlight')}</h2>
+
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <div className="my-0">
+                  <label>{t('admin:customize_setting.theme')}</label>
+                </div>
+                <Dropdown isOpen={this.state.isDropdownOpen} toggle={this.onToggleDropdown}>
+                  <DropdownToggle className="text-right col-6" caret>
+                    <span className="float-left">{adminCustomizeContainer.state.currentHighlightJsStyleName}</span>
+                  </DropdownToggle>
+                  <DropdownMenu className="dropdown-menu" role="menu">
+                    {menuItem}
+                  </DropdownMenu>
+                </Dropdown>
+                <p className="form-text text-warning">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.nocdn_desc') }} />
+                </p>
+              </div>
+            </div>
+
+            <div className="form-group row">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch checkbox-success">
+                  <input
+                    type="checkbox"
+                    className="custom-control-input"
+                    id="highlightBorder"
+                    checked={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled}
+                    onChange={() => { adminCustomizeContainer.switchHighlightJsStyleBorder() }}
+                  />
+                  <label className="custom-control-label" htmlFor="highlightBorder">
+                    <strong>Border</strong>
+                  </label>
+                </div>
               </div>
-              {/* eslint-disable-next-line react/no-danger */}
-              <p className="help-block text-warning"><span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.nocdn_desc') }} /></p>
             </div>
-          </div>
-        </div>
 
-        <div className="form-group row">
-          <div className="col-xs-offset-3 col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
-              <input
-                type="checkbox"
-                id="highlightBorder"
-                checked={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled}
-                onChange={() => { adminCustomizeContainer.switchHighlightJsStyleBorder() }}
-              />
-              <label htmlFor="highlightBorder">
-                <strong>Border</strong>
-              </label>
+            <div className="form-text text-muted">
+              <label>Examples:</label>
+              <div className="wiki">
+                {this.renderHljsDemo()}
+              </div>
             </div>
-          </div>
-        </div>
 
-        <div className="help-block">
-          <label>Examples:</label>
-          <div className="wiki">
-            {this.renderHljsDemo()}
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 9 - 3
src/client/js/components/Admin/Customize/CustomizeLayoutOption.jsx

@@ -10,9 +10,15 @@ class CustomizeLayoutOption extends React.Component {
     return (
       <React.Fragment>
         <h4>
-          <div className="radio radio-primary">
-            <input type="radio" id={`radio-layout-${layoutType}`} checked={this.props.isSelected} onChange={this.props.onSelected} />
-            <label htmlFor={`radio-layout-${layoutType}`}>
+          <div className="custom-control custom-radio">
+            <input
+              type="radio"
+              className="custom-control-input"
+              id={`radio-layout-${layoutType}`}
+              checked={this.props.isSelected}
+              onChange={this.props.onSelected}
+            />
+            <label className="custom-control-label" htmlFor={`radio-layout-${layoutType}`}>
               {/* eslint-disable-next-line react/no-danger */}
               <span dangerouslySetInnerHTML={{ __html: this.props.labelHtml }} />
             </label>

+ 3 - 3
src/client/js/components/Admin/Customize/CustomizeLayoutOptions.jsx

@@ -15,7 +15,7 @@ class CustomizeLayoutOptions extends React.Component {
 
     return (
       <div className="row">
-        <div className="col-sm-4">
+        <div className="col-md-4">
           <CustomizeLayoutOption
             layoutType="crowi-plus"
             isSelected={adminCustomizeContainer.state.currentLayout === 'growi'}
@@ -31,7 +31,7 @@ class CustomizeLayoutOptions extends React.Component {
           </CustomizeLayoutOption>
         </div>
 
-        <div className="col-sm-4">
+        <div className="col-md-4">
           <CustomizeLayoutOption
             layoutType="kibela"
             isSelected={adminCustomizeContainer.state.currentLayout === 'kibela'}
@@ -47,7 +47,7 @@ class CustomizeLayoutOptions extends React.Component {
           </CustomizeLayoutOption>
         </div>
 
-        <div className="col-sm-4">
+        <div className="col-md-4">
           <CustomizeLayoutOption
             layoutType="classic"
             isSelected={adminCustomizeContainer.state.currentLayout === 'crowi'}

+ 14 - 6
src/client/js/components/Admin/Customize/CustomizeLayoutSetting.jsx

@@ -48,12 +48,20 @@ class CustomizeLayoutSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.layout')}</h2>
-        <CustomizeLayoutOptions />
-        <h2 className="admin-setting-header">{t('admin:customize_setting.theme')}</h2>
-        {this.renderDevAlert()}
-        <CustomizeThemeOptions />
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.layout')}</h2>
+            <CustomizeLayoutOptions />
+          </div>
+        </div>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.theme')}</h2>
+            {this.renderDevAlert()}
+            <CustomizeThemeOptions />
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          </div>
+        </div>
       </React.Fragment>
     );
   }

+ 57 - 42
src/client/js/components/Admin/Customize/CustomizeScriptSetting.jsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { Card, CardBody } from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
@@ -44,50 +45,64 @@ class CustomizeScriptSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.custom_script')}</h2>
-        <p className="well">
-          {t('admin:customize_setting.write_java')}<br />
-          {t('admin:customize_setting.reflect_change')}
-        </p>
-
-        <div className="help-block">
-          Placeholders:<br />
-          (Available after <code>load</code> event)
-          <dl className="dl-horizontal">
-            <dt><code>$</code></dt>
-            <dd>jQuery instance</dd>
-            <dt><code>appContainer</code></dt>
-            <dd>GROWI App <a href="https://github.com/jamiebuilds/unstated">Unstated Container</a></dd>
-            <dt><code>growiRenderer</code></dt>
-            <dd>GROWI Renderer origin instance</dd>
-            <dt><code>growiPlugin</code></dt>
-            <dd>GROWI Plugin Manager instance</dd>
-            <dt><code>Crowi</code></dt>
-            <dd>Crowi legacy instance (jQuery based)</dd>
-          </dl>
-        </div>
-
-        <div className="help-block">
-          Examples:
-          <pre className="hljs"><code>{this.getExampleCode()}</code></pre>
-        </div>
-
-        <div className="form-group">
-          <div className="col-xs-12">
-            <CustomScriptEditor
-              value={adminCustomizeContainer.state.currentCustomizeScript || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeScript(inputValue) }}
-            />
-          </div>
-          <div className="col-xs-12">
-            <p className="help-block text-right">
-              <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
-              {t('admin:customize_setting.ctrl_space')}
-            </p>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_script')}</h2>
+            <Card className="card well">
+              <CardBody className="px-0 py-2">
+                {t('admin:customize_setting.write_java')}<br />
+                {t('admin:customize_setting.reflect_change')}
+              </CardBody>
+            </Card>
+
+            <div className="form-text text-muted">
+              Placeholders:<br />
+              (Available after <code>load</code> event)
+            </div>
+            <table className="table table-borderless table-sm form-text text-muted offset-1">
+              <tbody>
+                <tr>
+                  <th className="text-right"><code>$</code></th>
+                  <td>jQuery instance</td>
+                </tr>
+                <tr>
+                  <th className="text-right"><code>appContainer</code></th>
+                  <td>GROWI App <a href="https://github.com/jamiebuilds/unstated">Unstated Container</a></td>
+                </tr>
+                <tr>
+                  <th className="text-right"><code>growiRenderer</code></th>
+                  <td>GROWI Renderer origin instance</td>
+                </tr>
+                <tr>
+                  <th className="text-right"><code>growiPlugin</code></th>
+                  <td>GROWI Plugin Manager instance</td>
+                </tr>
+                <tr>
+                  <th className="text-right"><code>Crowi</code></th>
+                  <td>Crowi legacy instance (jQuery based)</td>
+                </tr>
+              </tbody>
+            </table>
+
+            <div className="form-text text-muted">
+              Examples:
+              <pre className="hljs"><code>{this.getExampleCode()}</code></pre>
+            </div>
+
+            <div className="form-group">
+              <CustomScriptEditor
+                value={adminCustomizeContainer.state.currentCustomizeScript || ''}
+                onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeScript(inputValue) }}
+              />
+              <p className="form-text text-muted text-right">
+                <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
+                {t('admin:customize_setting.ctrl_space')}
+              </p>
+            </div>
+
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           </div>
         </div>
-
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 34 - 20
src/client/js/components/Admin/Customize/CustomizeTitle.jsx

@@ -1,6 +1,8 @@
+/* eslint-disable max-len */
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { Card, CardBody } from 'reactstrap';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminCustomizeContainer from '../../../services/AdminCustomizeContainer';
@@ -34,27 +36,39 @@ class CustomizeTitle extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="admin-setting-header">{t('admin:customize_setting.custom_title')}</h2>
-        <p
-          className="well"
-          // eslint-disable-next-line react/no-danger
-          dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_title_detail') }}
-        />
-        {/* TODO i18n */}
-        <div className="help-block">
-          Default Value: <code>&#123;&#123;page&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
-          <br />
-          Default Output: <pre><code className="xml">&lt;title&gt;/Sandbox - {'GROWI'}&lt;&#047;title&gt;</code></pre>
-        </div>
-        <div className="form-group">
-          <input
-            className="form-control"
-            defaultValue={currentCustomizeTitle}
-            onChange={(e) => { adminCustomizeContainer.changeCustomizeTitle(e.target.value) }}
-          />
-        </div>
+        <div className="row">
+          <div className="col-12">
+            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_title')}</h2>
+          </div>
 
-        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          <div className="col-12">
+            <Card className="card well">
+              <CardBody className="px-0 py-2">
+                <span
+                  // eslint-disable-next-line react/no-danger
+                  dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_title_detail') }}
+                />
+              </CardBody>
+            </Card>
+          </div>
+
+          {/* TODO i18n */}
+          <div className="form-text text-muted col-12">
+            Default Value: <code>&#123;&#123;page&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
+            <br />
+            Default Output: <pre><code className="xml">&lt;title&gt;/Sandbox - {'GROWI'}&lt;&#047;title&gt;</code></pre>
+          </div>
+          <div className="form-group col-12">
+            <input
+              className="form-control"
+              defaultValue={currentCustomizeTitle}
+              onChange={(e) => { adminCustomizeContainer.changeCustomizeTitle(e.target.value) }}
+            />
+          </div>
+          <div className="col-12">
+            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          </div>
+        </div>
       </React.Fragment>
     );
   }

+ 7 - 7
src/client/js/components/Admin/ElasticsearchManagement/ElasticsearchManagement.jsx

@@ -147,7 +147,7 @@ class ElasticsearchManagement extends React.Component {
     return (
       <>
         <div className="row">
-          <div className="col-xs-12">
+          <div className="col-sm-12">
             <StatusTable
               isConfigured={isConfigured}
               isConnected={isConnected}
@@ -162,8 +162,8 @@ class ElasticsearchManagement extends React.Component {
 
         {/* Controls */}
         <div className="row">
-          <label className="col-xs-3 control-label">{ t('full_text_search_management.reconnect') }</label>
-          <div className="col-xs-6">
+          <label className="col-sm-3 col-form-label">{ t('full_text_search_management.reconnect') }</label>
+          <div className="col-sm-6">
             <ReconnectControls
               isConfigured={isConfigured}
               isConnected={isConnected}
@@ -175,8 +175,8 @@ class ElasticsearchManagement extends React.Component {
         <hr />
 
         <div className="row">
-          <label className="col-xs-3 control-label">{ t('full_text_search_management.normalize') }</label>
-          <div className="col-xs-6">
+          <label className="col-sm-3 col-form-label">{ t('full_text_search_management.normalize') }</label>
+          <div className="col-sm-6">
             <NormalizeIndicesControls
               isRebuildingProcessing={isRebuildingProcessing}
               isRebuildingCompleted={isRebuildingCompleted}
@@ -189,8 +189,8 @@ class ElasticsearchManagement extends React.Component {
         <hr />
 
         <div className="row">
-          <label className="col-xs-3 control-label">{ t('full_text_search_management.rebuild') }</label>
-          <div className="col-xs-6">
+          <label className="col-sm-3 col-form-label">{ t('full_text_search_management.rebuild') }</label>
+          <div className="col-sm-6">
             <RebuildIndexControls
               isRebuildingProcessing={isRebuildingProcessing}
               isRebuildingCompleted={isRebuildingCompleted}

+ 1 - 1
src/client/js/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.jsx

@@ -15,7 +15,7 @@ class NormalizeIndicesControls extends React.PureComponent {
       <>
         <button
           type="submit"
-          className={`btn btn-outline ${isEnabled ? 'btn-info' : 'btn-default'}`}
+          className={`btn btn-outline ${isEnabled ? 'btn-info' : 'btn-light'}`}
           onClick={() => { this.props.onNormalizingRequested() }}
           disabled={!isEnabled}
         >

+ 1 - 1
src/client/js/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx

@@ -76,7 +76,7 @@ class RebuildIndexControls extends React.Component {
 
         <button
           type="submit"
-          className="btn btn-inverse"
+          className="btn btn-primary"
           onClick={() => { this.props.onRebuildingRequested() }}
           disabled={!isEnabled}
         >

+ 1 - 1
src/client/js/components/Admin/ElasticsearchManagement/ReconnectControls.jsx

@@ -15,7 +15,7 @@ class ReconnectControls extends React.PureComponent {
       <>
         <button
           type="submit"
-          className={`btn btn-outline ${isEnabled ? 'btn-success' : 'btn-default'}`}
+          className={`btn btn-outline ${isEnabled ? 'btn-success' : 'btn-light'}`}
           onClick={() => { this.props.onReconnectingRequested() }}
           disabled={!isEnabled}
         >

+ 16 - 18
src/client/js/components/Admin/ElasticsearchManagement/StatusTable.jsx

@@ -11,24 +11,22 @@ class StatusTable extends React.PureComponent {
 
     const aliasLabels = aliases.map((aliasName) => {
       return (
-        <span key={`label-${indexName}-${aliasName}`} className="label label-primary mr-2">
+        <span key={`badge-${indexName}-${aliasName}`} className="badge badge-primary mr-2">
           <i className="icon-tag"></i> {aliasName}
         </span>
       );
     });
 
     return (
-      <div className="panel panel-default">
-        <div className="panel-heading" role="tab">
-          <h4 className="panel-title">
-            <a role="button" data-toggle="collapse" data-parent="#accordion" href={`#${collapseId}`} aria-expanded="true" aria-controls={collapseId}>
-              <i className="fa fa-fw fa-database"></i> {indexName}
-            </a>
-            <span className="ml-3">{aliasLabels}</span>
-          </h4>
+      <div className="card">
+        <div className="card-header">
+          <a role="button" data-toggle="collapse" href={`#${collapseId}`} aria-expanded="true" aria-controls={collapseId}>
+            <i className="fa fa-fw fa-database"></i> {indexName}
+          </a>
+          <span className="ml-3">{aliasLabels}</span>
         </div>
-        <div id={collapseId} className="panel-collapse collapse" role="tabpanel">
-          <div className="panel-body">
+        <div id={collapseId} className="collapse">
+          <div className="card-body">
             <pre>
               {JSON.stringify(body, null, 2)}
             </pre>
@@ -98,21 +96,21 @@ class StatusTable extends React.PureComponent {
     const { isConfigured, isConnected, isNormalized } = this.props;
 
 
-    let connectionStatusLabel = <span className="label label-default">――</span>;
+    let connectionStatusLabel = <span className="badge badge-default">――</span>;
     if (isConfigured != null && !isConfigured) {
-      connectionStatusLabel = <span className="label label-default">{ t('full_text_search_management.connection_status_label_unconfigured') }</span>;
+      connectionStatusLabel = <span className="badge badge-default">{ t('full_text_search_management.connection_status_label_unconfigured') }</span>;
     }
     else if (isConnected != null) {
       connectionStatusLabel = isConnected
-        ? <span className="label label-success">{ t('full_text_search_management.connection_status_label_connected') }</span>
-        : <span className="label label-danger">{ t('full_text_search_management.connection_status_label_disconnected') }</span>;
+        ? <span className="badge badge-success">{ t('full_text_search_management.connection_status_label_connected') }</span>
+        : <span className="badge badge-danger">{ t('full_text_search_management.connection_status_label_disconnected') }</span>;
     }
 
-    let indicesStatusLabel = <span className="label label-default">――</span>;
+    let indicesStatusLabel = <span className="label badge-default">――</span>;
     if (isNormalized != null) {
       indicesStatusLabel = isNormalized
-        ? <span className="label label-info">{ t('full_text_search_management.indices_status_label_normalized') }</span>
-        : <span className="label label-warning">{ t('full_text_search_management.indices_status_label_unnormalized') }</span>;
+        ? <span className="badge badge-info">{ t('full_text_search_management.indices_status_label_normalized') }</span>
+        : <span className="badge badge-warning">{ t('full_text_search_management.indices_status_label_unnormalized') }</span>;
     }
 
     return (

+ 2 - 2
src/client/js/components/Admin/MarkdownSetting/WhiteListInput.jsx

@@ -38,7 +38,7 @@ class WhiteListInput extends React.Component {
         <div className="mt-4">
           <div className="d-flex justify-content-between">
             {t('admin:markdown_setting.xss_options.tag_names')}
-            <p id="btn-import-tags" className="btn btn-xs btn-primary" onClick={this.onClickRecommendTagButton}>
+            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0" onClick={this.onClickRecommendTagButton}>
               {t('admin:markdown_setting.xss_options.import_recommended', { target: 'Tags' })}
             </p>
           </div>
@@ -55,7 +55,7 @@ class WhiteListInput extends React.Component {
         <div className="mt-4">
           <div className="d-flex justify-content-between">
             {t('admin:markdown_setting.xss_options.tag_attributes')}
-            <p id="btn-import-tags" className="btn btn-xs btn-primary" onClick={this.onClickRecommendAttrButton}>
+            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0" onClick={this.onClickRecommendAttrButton}>
               {t('admin:markdown_setting.xss_options.import_recommended', { target: 'Attrs' })}
             </p>
           </div>

+ 17 - 14
src/client/js/components/Admin/Notification/GlobalNotificationList.jsx

@@ -129,22 +129,25 @@ class GlobalNotificationList extends React.Component {
                   && <span data-toggle="tooltip" data-placement="top" title="Slack"><i className="fa fa-slack"></i> {notification.slackChannels}</span>}
               </td>
               <td className="td-abs-center">
-                <div className="btn-group admin-group-menu">
-                  <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
+                <div className="dropdown">
+                  <button
+                    className="btn btn-secondary dropdown-toggle"
+                    type="button"
+                    id="dropdownMenuButton"
+                    data-toggle="dropdown"
+                    aria-haspopup="true"
+                    aria-expanded="false"
+                  >
                     <i className="icon-settings"></i> <span className="caret"></span>
                   </button>
-                  <ul className="dropdown-menu" role="menu">
-                    <li>
-                      <a href={urljoin('/admin/global-notification/', notification._id)}>
-                        <i className="icon-fw icon-note"></i> {t('Edit')}
-                      </a>
-                    </li>
-                    <li onClick={() => this.openConfirmationModal(notification)}>
-                      <a>
-                        <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
-                      </a>
-                    </li>
-                  </ul>
+                  <div className="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
+                    <a className="dropdown-item" href={urljoin('/admin/global-notification/', notification._id)}>
+                      <i className="icon-fw icon-note"></i> {t('Edit')}
+                    </a>
+                    <a className="dropdown-item" onClick={() => this.openConfirmationModal(notification)}>
+                      <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
+                    </a>
+                  </div>
                 </div>
               </td>
             </tr>

+ 29 - 38
src/client/js/components/Admin/Notification/SlackAppConfiguration.jsx

@@ -1,9 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-import {
-  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
-} from 'reactstrap';
 
 import loggerFactory from '@alias/logger';
 
@@ -21,18 +18,9 @@ class SlackAppConfiguration extends React.Component {
   constructor(props) {
     super(props);
 
-    this.state = {
-      isDropdownOpen: false,
-    };
-
-    this.onToggleDropdown = this.onToggleDropdown.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
   }
 
-  onToggleDropdown() {
-    this.setState({ isDropdownOpen: !this.state.isDropdownOpen });
-  }
-
   async onClickSubmit() {
     const { t, adminNotificationContainer } = this.props;
 
@@ -51,30 +39,33 @@ class SlackAppConfiguration extends React.Component {
 
     return (
       <React.Fragment>
-        <div className="mb-5">
-          <div className="col-xs-6 mt-3 text-left">
-            <Dropdown isOpen={this.state.isDropdownOpen} toggle={this.onToggleDropdown}>
-              <DropdownToggle caret>
-                {`Slack ${adminNotificationContainer.state.selectSlackOption}`}
-              </DropdownToggle>
-              <DropdownMenu className="dropdown-menu" role="menu">
-                <DropdownItem key="Incoming Webhooks" role="presentation" onClick={() => adminNotificationContainer.switchSlackOption('Incoming Webhooks')}>
-                  <a role="menuitem">Slack Incoming Webhooks</a>
-                </DropdownItem>
-                <DropdownItem key="App" role="presentation" onClick={() => adminNotificationContainer.switchSlackOption('App')}>
-                  <a role="menuitem">Slack App</a>
-                </DropdownItem>
-              </DropdownMenu>
-            </Dropdown>
+        <div className="row my-3">
+          <div className="col-6 text-left">
+            <div className="dropdown">
+              <button
+                className="btn btn-secondary dropdown-toggle"
+                type="button"
+                id="dropdownMenuButton"
+                data-toggle="dropdown"
+                aria-haspopup="true"
+                aria-expanded="true"
+              >
+                {`Slack ${adminNotificationContainer.state.selectSlackOption}`} <span className="caret"></span>
+              </button>
+              <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                <a className="dropdown-item" onClick={() => adminNotificationContainer.switchSlackOption('Incoming Webhooks')}>Slack Incoming Webhooks</a>
+                <a className="dropdown-item" onClick={() => adminNotificationContainer.switchSlackOption('App')}>Slack App</a>
+              </div>
+            </div>
           </div>
         </div>
         {adminNotificationContainer.state.selectSlackOption === 'Incoming Webhooks' ? (
           <React.Fragment>
             <h2 className="border-bottom mb-5">{t('notification_setting.slack_incoming_configuration')}</h2>
 
-            <div className="row mb-5">
-              <label className="col-xs-3 text-right">Webhook URL</label>
-              <div className="col-xs-6">
+            <div className="row mb-3">
+              <label className="col-3 text-right">Webhook URL</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -84,8 +75,8 @@ class SlackAppConfiguration extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
+            <div className="row mb-3">
+              <div className="offset-3 col-6 text-left">
                 <div className="custom-control custom-switch checkbox-success">
                   <input
                     type="checkbox"
@@ -109,12 +100,12 @@ class SlackAppConfiguration extends React.Component {
             <React.Fragment>
               <h2 className="border-bottom mb-5">{t('notification_setting.slack_app_configuration')}</h2>
 
-              <div className="well">
-                <i className="icon-fw icon-exclamation text-danger"></i><span className="text-danger">NOT RECOMMENDED</span>
-                <br /><br />
+              <div className="well card">
+                <span className="text-danger"><i className="icon-fw icon-exclamation"></i>NOT RECOMMENDED</span>
+                <br />
                 {/* eslint-disable-next-line react/no-danger */}
                 <span dangerouslySetInnerHTML={{ __html: t('notification_setting.slack_app_configuration_desc') }} />
-                <br /><br />
+                <br />
                 <a
                   href="#slack-incoming-webhooks"
                   data-toggle="tab"
@@ -125,8 +116,8 @@ class SlackAppConfiguration extends React.Component {
               </div>
 
               <div className="row mb-5">
-                <label className="col-xs-3 text-right">OAuth Access Token</label>
-                <div className="col-xs-6">
+                <label className="col-3 text-right">OAuth Access Token</label>
+                <div className="col-6">
                   <input
                     className="form-control"
                     type="text"

+ 1 - 1
src/client/js/components/Admin/Notification/UserTriggerNotification.jsx

@@ -81,7 +81,7 @@ class UserTriggerNotification extends React.Component {
 
     return (
       <React.Fragment>
-        <h2 className="border-bottom mb-5">{t('notification_setting.user_trigger_notification_header')}</h2>
+        <h2 className="border-bottom my-5">{t('notification_setting.user_trigger_notification_header')}</h2>
 
         <table className="table table-bordered">
           <thead>