Explorar o código

Merge pull request #1971 from weseek/support/apply-bootstrap4

Support/apply bootstrap4
Yuki Takei %!s(int64=6) %!d(string=hai) anos
pai
achega
0da93f22aa
Modificáronse 98 ficheiros con 1053 adicións e 1536 borrados
  1. 1 1
      config/webpack.common.js
  2. 1 1
      src/client/js/components/Admin/App/PluginSetting.jsx
  3. 1 1
      src/client/js/components/Admin/Customize/CustomizeFunctionOption.jsx
  4. 1 1
      src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx
  5. 17 29
      src/client/js/components/Admin/Customize/CustomizeLayoutOptions.jsx
  6. 1 1
      src/client/js/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx
  7. 1 1
      src/client/js/components/Admin/ImportData/GrowiArchive/ImportForm.jsx
  8. 27 31
      src/client/js/components/Admin/MarkdownSetting/LineBreakForm.jsx
  9. 13 3
      src/client/js/components/Admin/MarkdownSetting/PresentationForm.jsx
  10. 21 19
      src/client/js/components/Admin/Notification/GlobalNotificationList.jsx
  11. 110 81
      src/client/js/components/Admin/Notification/ManageGlobalNotification.jsx
  12. 1 1
      src/client/js/components/Admin/Notification/SlackAppConfiguration.jsx
  13. 4 4
      src/client/js/components/Admin/Notification/UserNotificationRow.jsx
  14. 21 13
      src/client/js/components/Admin/Notification/UserTriggerNotification.jsx
  15. 1 1
      src/client/js/components/Admin/Security/BasicSecuritySetting.jsx
  16. 2 2
      src/client/js/components/Admin/Security/GitHubSecuritySetting.jsx
  17. 1 1
      src/client/js/components/Admin/Security/GoogleSecuritySetting.jsx
  18. 13 11
      src/client/js/components/Admin/Security/LdapAuthTest.jsx
  19. 1 1
      src/client/js/components/Admin/Security/LdapSecuritySetting.jsx
  20. 2 2
      src/client/js/components/Admin/Security/OidcSecuritySetting.jsx
  21. 2 2
      src/client/js/components/Admin/Security/SamlSecuritySetting.jsx
  22. 2 2
      src/client/js/components/Admin/Security/SecuritySetting.jsx
  23. 1 1
      src/client/js/components/Admin/Security/TwitterSecuritySetting.jsx
  24. 1 1
      src/client/js/components/Admin/UserGroup/UserGroupTable.jsx
  25. 1 1
      src/client/js/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx
  26. 6 6
      src/client/js/components/Admin/UserManagement.jsx
  27. 1 1
      src/client/js/components/Admin/Users/UserMenu.jsx
  28. 2 2
      src/client/js/components/Admin/Users/UserTable.jsx
  29. 1 1
      src/client/js/components/BookmarkButton.jsx
  30. 1 1
      src/client/js/components/Me/ApiSettings.jsx
  31. 21 25
      src/client/js/components/Me/ExternalAccountLinkedMe.jsx
  32. 1 1
      src/client/js/components/Me/PasswordSettings.jsx
  33. 4 4
      src/client/js/components/Me/UserSettings.jsx
  34. 50 30
      src/client/js/components/Navbar/GrowiSubNavigation.jsx
  35. 29 36
      src/client/js/components/Navbar/GrowiSubNavigationForUserPage.jsx
  36. 1 1
      src/client/js/components/Navbar/PageCreator.jsx
  37. 2 2
      src/client/js/components/Navbar/RevisionAuthor.jsx
  38. 16 12
      src/client/js/components/Page/RevisionPath.jsx
  39. 1 1
      src/client/js/components/Page/TagEditor.jsx
  40. 2 0
      src/client/js/services/AppContainer.js
  41. 19 7
      src/client/js/services/PageContainer.js
  42. 0 7
      src/client/styles/agile-admin/inverse/colors/_apply-colors-dark.scss
  43. 0 4
      src/client/styles/agile-admin/inverse/colors/antarctic.scss
  44. 0 8
      src/client/styles/agile-admin/inverse/colors/spring.scss
  45. 4 0
      src/client/styles/scss/_admin.scss
  46. 1 1
      src/client/styles/scss/_editor-attachment.scss
  47. 0 25
      src/client/styles/scss/_layout.scss
  48. 1 1
      src/client/styles/scss/_layout_kibela.scss
  49. 5 0
      src/client/styles/scss/_me.scss
  50. 1 1
      src/client/styles/scss/_navbar.scss
  51. 4 3
      src/client/styles/scss/_on-edit.scss
  52. 8 8
      src/client/styles/scss/_override-bootstrap-variables.scss
  53. 150 140
      src/client/styles/scss/_override-bootstrap.scss
  54. 0 39
      src/client/styles/scss/_page_growi.scss
  55. 0 10
      src/client/styles/scss/_page_header.scss
  56. 97 0
      src/client/styles/scss/_subnav.scss
  57. 21 17
      src/client/styles/scss/_tag.scss
  58. 54 29
      src/client/styles/scss/_user.scss
  59. 0 7
      src/client/styles/scss/_user_growi.scss
  60. 13 5
      src/client/styles/scss/_vendor.scss
  61. 6 5
      src/client/styles/scss/atoms/_buttons.scss
  62. 3 3
      src/client/styles/scss/style-app.scss
  63. 18 19
      src/client/styles/scss/theme/_apply-colors-dark.scss
  64. 15 12
      src/client/styles/scss/theme/_apply-colors-light.scss
  65. 15 22
      src/client/styles/scss/theme/_apply-colors.scss
  66. 5 12
      src/client/styles/scss/theme/_reboot-bootstrap-colors.scss
  67. 15 0
      src/client/styles/scss/theme/_reboot-toastr-colors.scss
  68. 116 6
      src/client/styles/scss/theme/antarctic.scss
  69. 4 2
      src/client/styles/scss/theme/default.scss
  70. 1 3
      src/server/views/admin/Users_reserve.html
  71. 2 3
      src/server/views/admin/app.html
  72. 1 3
      src/server/views/admin/customize.html
  73. 1 3
      src/server/views/admin/export.html
  74. 1 3
      src/server/views/admin/external-accounts.html
  75. 1 3
      src/server/views/admin/global-notification-detail.html
  76. 1 3
      src/server/views/admin/importer.html
  77. 1 3
      src/server/views/admin/index.html
  78. 1 3
      src/server/views/admin/markdown.html
  79. 1 3
      src/server/views/admin/notification.html
  80. 1 3
      src/server/views/admin/search.html
  81. 1 3
      src/server/views/admin/security.html
  82. 1 3
      src/server/views/admin/user-group-detail.html
  83. 1 3
      src/server/views/admin/user-groups.html
  84. 1 3
      src/server/views/admin/users.html
  85. 1 1
      src/server/views/layout-crowi/base/layout.html
  86. 7 8
      src/server/views/layout-growi/base/layout.html
  87. 8 6
      src/server/views/layout-growi/user_page.html
  88. 6 10
      src/server/views/layout-growi/widget/header.html
  89. 2 2
      src/server/views/layout-growi/widget/liker-and-seenusers.html
  90. 1 1
      src/server/views/layout-kibela/base/layout.html
  91. 1 1
      src/server/views/layout-kibela/user_page.html
  92. 1 1
      src/server/views/layout/layout.html
  93. 0 85
      src/server/views/me/api_token.html
  94. 0 249
      src/server/views/me/external-accounts.html
  95. 4 354
      src/server/views/me/index.html
  96. 11 11
      src/server/views/modal/shortcuts.html
  97. 8 9
      src/server/views/tags.html
  98. 27 25
      src/server/views/widget/page_tabs.html

+ 1 - 1
config/webpack.common.js

@@ -43,7 +43,7 @@ module.exports = (options) => {
       // 'styles/theme-wood':          './src/client/styles/scss/theme/wood.scss',
       // 'styles/theme-christmas':          './src/client/styles/scss/theme/christmas.scss',
       // 'styles/theme-island':      './src/client/styles/scss/theme/island.scss',
-      // 'styles/theme-antarctic':      './src/client/styles/scss/theme/antarctic.scss',
+      'styles/theme-antarctic':      './src/client/styles/scss/theme/antarctic.scss',
       // styles for external services
       'styles/style-hackmd':          './src/client/styles/hackmd/style.scss',
     }, options.entry || {}), // Merge with env dependent settings

+ 1 - 1
src/client/js/components/Admin/App/PluginSetting.jsx

@@ -43,7 +43,7 @@ class PluginSetting extends React.Component {
 
         <div className="row form-group mb-5">
           <div className="offset-3 col-6 text-left">
-            <div className="custom-control custom-switch custom-checkbox-success">
+            <div className="custom-control custom-checkbox custom-checkbox-success">
               <input
                 id="isEnabledPlugins"
                 className="custom-control-input"

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

@@ -7,7 +7,7 @@ class CustomizeFunctionOption extends React.PureComponent {
   render() {
     return (
       <React.Fragment>
-        <div className="custom-control custom-switch custom-checkbox-success">
+        <div className="custom-control custom-checkbox custom-checkbox-success">
           <input
             className="custom-control-input"
             type="checkbox"

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

@@ -150,7 +150,7 @@ class CustomizeBehaviorSetting extends React.Component {
             </div>
 
             <div className="form-group row">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
+              <div className="offset-3 col-6 text-left">
                 <CustomizeFunctionOption
                   optionId="isAllReplyShown"
                   label={t('admin:customize_setting.function_options.show_all_reply_comments')}

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

@@ -14,8 +14,8 @@ class CustomizeLayoutOptions extends React.Component {
     const { t, adminCustomizeContainer } = this.props;
 
     return (
-      <div className="row">
-        <div className="col-md-4">
+      <div className="row row-cols-1 row-cols-md-2">
+        <div className="col text-center">
           <CustomizeLayoutOption
             layoutType="crowi-plus"
             isSelected={adminCustomizeContainer.state.currentLayout === 'growi'}
@@ -23,15 +23,17 @@ class CustomizeLayoutOptions extends React.Component {
             labelHtml={`GROWI Enhanced Layout <small class="text-success">${t('admin:customize_setting.recommended')}</small>`}
           >
             <h4>{t('admin:customize_setting.layout_desc.growi_title')}</h4>
-            <ul>
-              <li>{t('admin:customize_setting.layout_desc.growi_text1')}</li>
-              <li>{t('admin:customize_setting.layout_desc.growi_text2')}</li>
-              <li>{t('admin:customize_setting.layout_desc.growi_text3')}</li>
-            </ul>
+            <div className="text-justify d-inline-block">
+              <ul>
+                <li>{t('admin:customize_setting.layout_desc.growi_text1')}</li>
+                <li>{t('admin:customize_setting.layout_desc.growi_text2')}</li>
+                <li>{t('admin:customize_setting.layout_desc.growi_text3')}</li>
+              </ul>
+            </div>
           </CustomizeLayoutOption>
         </div>
 
-        <div className="col-md-4">
+        <div className="col text-center">
           <CustomizeLayoutOption
             layoutType="kibela"
             isSelected={adminCustomizeContainer.state.currentLayout === 'kibela'}
@@ -39,27 +41,13 @@ class CustomizeLayoutOptions extends React.Component {
             labelHtml="Kibela Like Layout"
           >
             <h4>{t('admin:customize_setting.layout_desc.kibela_title')}</h4>
-            <ul>
-              <li>{t('admin:customize_setting.layout_desc.kibela_text1')}</li>
-              <li>{t('admin:customize_setting.layout_desc.kibela_text2')}</li>
-              <li>{t('admin:customize_setting.layout_desc.kibela_text3')}</li>
-            </ul>
-          </CustomizeLayoutOption>
-        </div>
-
-        <div className="col-md-4">
-          <CustomizeLayoutOption
-            layoutType="classic"
-            isSelected={adminCustomizeContainer.state.currentLayout === 'crowi'}
-            onSelected={() => adminCustomizeContainer.switchLayoutType('crowi')}
-            labelHtml="Crowi Classic Layout"
-          >
-            <h4>{t('admin:customize_setting.layout_desc.crowi_title')}</h4>
-            <ul>
-              <li>{t('admin:customize_setting.layout_desc.crowi_text1')}</li>
-              <li>{t('admin:customize_setting.layout_desc.crowi_text2')}</li>
-              <li>{t('admin:customize_setting.layout_desc.crowi_text3')}</li>
-            </ul>
+            <div className="text-justify d-inline-block">
+              <ul>
+                <li>{t('admin:customize_setting.layout_desc.kibela_text1')}</li>
+                <li>{t('admin:customize_setting.layout_desc.kibela_text2')}</li>
+                <li>{t('admin:customize_setting.layout_desc.kibela_text3')}</li>
+              </ul>
+            </div>
           </CustomizeLayoutOption>
         </div>
       </div>

+ 1 - 1
src/client/js/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx

@@ -203,7 +203,7 @@ export default class ImportCollectionItem extends React.Component {
     return (
       <div className="card border-light">
         <div className="card-header bg-light">
-          <div className="d-flex justify-content-between align-items-center">
+          <div className="d-flex justify-content-between align-items-center flex-wrap">
             {/* left */}
             {this.renderCheckbox()}
             {/* right */}

+ 1 - 1
src/client/js/components/Admin/ImportData/GrowiArchive/ImportForm.jsx

@@ -387,7 +387,7 @@ class ImportForm extends React.Component {
           const isConfigButtonAvailable = Object.keys(IMPORT_OPTION_CLASS_MAPPING).includes(collectionName);
 
           return (
-            <div className="col-6 my-1" key={collectionName}>
+            <div className="col-md-6 my-1" key={collectionName}>
               <ImportCollectionItem
                 isImporting={isImporting}
                 isImported={collectionProgress ? isImported : false}

+ 27 - 31
src/client/js/components/Admin/MarkdownSetting/LineBreakForm.jsx

@@ -44,22 +44,20 @@ class LineBreakForm extends React.Component {
     const helpLineBreak = { __html: t('admin:markdown_setting.lineBreak_options.enable_lineBreak_desc') };
 
     return (
-      <div className="form-group text-left my-3">
-        <div className="col-8 offset-4">
-          <div className="custom-control custom-switch custom-checkbox-success">
-            <input
-              type="checkbox"
-              className="custom-control-input"
-              id="isEnabledLinebreaks"
-              checked={isEnabledLinebreaks}
-              onChange={() => { adminMarkDownContainer.setState({ isEnabledLinebreaks: !isEnabledLinebreaks }) }}
-            />
-            <label className="custom-control-label" htmlFor="isEnabledLinebreaks">
-              {t('admin:markdown_setting.lineBreak_options.enable_lineBreak') }
-            </label>
-          </div>
-          <p className="help-block" dangerouslySetInnerHTML={helpLineBreak} />
+      <div className="col">
+        <div className="custom-control custom-checkbox custom-checkbox-success">
+          <input
+            type="checkbox"
+            className="custom-control-input"
+            id="isEnabledLinebreaks"
+            checked={isEnabledLinebreaks}
+            onChange={() => { adminMarkDownContainer.setState({ isEnabledLinebreaks: !isEnabledLinebreaks }) }}
+          />
+          <label className="custom-control-label" htmlFor="isEnabledLinebreaks">
+            {t('admin:markdown_setting.lineBreak_options.enable_lineBreak') }
+          </label>
         </div>
+        <p className="form-text text-muted" dangerouslySetInnerHTML={helpLineBreak} />
       </div>
     );
   }
@@ -71,22 +69,20 @@ class LineBreakForm extends React.Component {
     const helpLineBreakInComment = { __html: t('admin:markdown_setting.lineBreak_options.enable_lineBreak_for_comment_desc') };
 
     return (
-      <div className="form-group text-left my-3">
-        <div className="col-8 offset-4">
-          <div className="custom-control custom-switch custom-checkbox-success">
-            <input
-              type="checkbox"
-              className="custom-control-input"
-              id="isEnabledLinebreaksInComments"
-              checked={isEnabledLinebreaksInComments}
-              onChange={() => { adminMarkDownContainer.setState({ isEnabledLinebreaksInComments: !isEnabledLinebreaksInComments }) }}
-            />
-            <label className="custom-control-label" htmlFor="isEnabledLinebreaksInComments">
-              {t('admin:markdown_setting.lineBreak_options.enable_lineBreak') }
-            </label>
-          </div>
-          <p className="help-block" dangerouslySetInnerHTML={helpLineBreakInComment} />
+      <div className="col">
+        <div className="custom-control custom-checkbox custom-checkbox-success">
+          <input
+            type="checkbox"
+            className="custom-control-input"
+            id="isEnabledLinebreaksInComments"
+            checked={isEnabledLinebreaksInComments}
+            onChange={() => { adminMarkDownContainer.setState({ isEnabledLinebreaksInComments: !isEnabledLinebreaksInComments }) }}
+          />
+          <label className="custom-control-label" htmlFor="isEnabledLinebreaksInComments">
+            {t('admin:markdown_setting.lineBreak_options.enable_lineBreak') }
+          </label>
         </div>
+        <p className="form-text text-muted" dangerouslySetInnerHTML={helpLineBreakInComment} />
       </div>
     );
   }
@@ -96,7 +92,7 @@ class LineBreakForm extends React.Component {
 
     return (
       <React.Fragment>
-        <fieldset className="col-12">
+        <fieldset className="form-group row row-cols-1 row-cols-md-2 mx-3">
           {this.renderLineBreakOption()}
           {this.renderLineBreakInCommentOption()}
         </fieldset>

+ 13 - 3
src/client/js/components/Admin/MarkdownSetting/PresentationForm.jsx

@@ -42,7 +42,7 @@ class PresentationForm extends React.Component {
     return (
       <fieldset className="form-group col-12 my-2">
 
-        <label className="col-12 control-label font-weight-bold text-left mt-3">
+        <label className="col-8 offset-4 control-label font-weight-bold text-left mt-3">
           {t('admin:markdown_setting.presentation_options.page_break_setting')}
         </label>
 
@@ -60,7 +60,12 @@ class PresentationForm extends React.Component {
                 <p className="font-weight-bold">{ t('admin:markdown_setting.presentation_options.preset_one_separator') }</p>
                 <div className="mt-3">
                   { t('admin:markdown_setting.presentation_options.preset_one_separator_desc') }
-                  <pre><code>{ t('admin:markdown_setting.presentation_options.preset_one_separator_value') }</code></pre>
+                  <input
+                    className="form-control"
+                    type="text"
+                    value={t('admin:markdown_setting.presentation_options.preset_one_separator_value')}
+                    readOnly
+                  />
                 </div>
               </label>
             </div>
@@ -79,7 +84,12 @@ class PresentationForm extends React.Component {
                 <p className="font-weight-bold">{ t('admin:markdown_setting.presentation_options.preset_two_separator') }</p>
                 <div className="mt-3">
                   { t('admin:markdown_setting.presentation_options.preset_two_separator_desc') }
-                  <pre><code>{ t('admin:markdown_setting.presentation_options.preset_two_separator_value') }</code></pre>
+                  <input
+                    className="form-control"
+                    type="text"
+                    value={t('admin:markdown_setting.presentation_options.preset_two_separator_value')}
+                    readOnly
+                  />
                 </div>
               </label>
             </div>

+ 21 - 19
src/client/js/components/Admin/Notification/GlobalNotificationList.jsx

@@ -91,42 +91,44 @@ class GlobalNotificationList extends React.Component {
                 {notification.triggerPath}
               </td>
               <td>
-                {notification.triggerEvents.includes('pageCreate') && (
-                  <span className="badge badge-pill badge-success" data-toggle="tooltip" data-placement="top" title="Page Create">
+                <ul className="list-inline">
+                  {notification.triggerEvents.includes('pageCreate') && (
+                  <li className="list-inline-item badge badge-pill badge-success" data-toggle="tooltip" data-placement="top" title="Page Create">
                     <i className="icon-doc"></i> CREATE
-                  </span>
+                  </li>
                 )}
-                {notification.triggerEvents.includes('pageEdit') && (
-                  <span className="badge badge-pill badge-warning" data-toggle="tooltip" data-placement="top" title="Page Edit">
+                  {notification.triggerEvents.includes('pageEdit') && (
+                  <li className="list-inline-item badge badge-pill badge-warning" data-toggle="tooltip" data-placement="top" title="Page Edit">
                     <i className="icon-pencil"></i> EDIT
-                  </span>
+                  </li>
                 )}
-                {notification.triggerEvents.includes('pageMove') && (
-                  <span className="badge badge-pill badge-warning" data-toggle="tooltip" data-placement="top" title="Page Move">
+                  {notification.triggerEvents.includes('pageMove') && (
+                  <li className="list-inline-item badge badge-pill badge-warning" data-toggle="tooltip" data-placement="top" title="Page Move">
                     <i className="icon-action-redo"></i> MOVE
-                  </span>
+                  </li>
                 )}
-                {notification.triggerEvents.includes('pageDelete') && (
-                  <span className="badge badge-pill badge-danger" data-toggle="tooltip" data-placement="top" title="Page Delte">
+                  {notification.triggerEvents.includes('pageDelete') && (
+                  <li className="list-inline-item badge badge-pill badge-danger" data-toggle="tooltip" data-placement="top" title="Page Delte">
                     <i className="icon-fire"></i> DELETE
-                  </span>
+                  </li>
                 )}
-                {notification.triggerEvents.includes('pageLike') && (
-                  <span className="badge badge-pill badge-info" data-toggle="tooltip" data-placement="top" title="Page Like">
+                  {notification.triggerEvents.includes('pageLike') && (
+                  <li className="list-inline-item badge badge-pill badge-info" data-toggle="tooltip" data-placement="top" title="Page Like">
                     <i className="icon-like"></i> LIKE
-                  </span>
+                  </li>
                 )}
-                {notification.triggerEvents.includes('comment') && (
-                  <span className="badge badge-pill badge-light" data-toggle="tooltip" data-placement="top" title="New Comment">
+                  {notification.triggerEvents.includes('comment') && (
+                  <li className="list-inline-item badge badge-pill badge-light" data-toggle="tooltip" data-placement="top" title="New Comment">
                     <i className="icon-fw icon-bubble"></i> POST
-                  </span>
+                  </li>
                 )}
+                </ul>
               </td>
               <td>
                 {notification.__t === 'mail'
                   && <span data-toggle="tooltip" data-placement="top" title="Email"><i className="ti-email"></i> {notification.toEmail}</span>}
                 {notification.__t === 'slack'
-                  && <span data-toggle="tooltip" data-placement="top" title="Slack"><i className="fa fa-slack"></i> {notification.slackChannels}</span>}
+                  && <span data-toggle="tooltip" data-placement="top" title="Slack"><i className="fa fa-hashtag"></i> {notification.slackChannels}</span>}
               </td>
               <td className="td-abs-center">
                 <div className="dropdown">

+ 110 - 81
src/client/js/components/Admin/Notification/ManageGlobalNotification.jsx

@@ -165,100 +165,129 @@ class ManageGlobalNotification extends React.Component {
 
             {this.state.notifyToType === 'mail'
               ? (
-                <div className="form-group notify-to-option" id="mail-input">
-                  <input
-                    className="form-control"
-                    type="text"
-                    name="toEmail"
-                    placeholder="Email"
-                    value={this.state.emailToSend}
-                    onChange={(e) => { this.onChangeEmailToSend(e.target.value) }}
-                  />
-                  <p className="help">
+                <>
+                  <div className="input-group notify-to-option" id="mail-input">
+                    <div className="input-group-prepend">
+                      <span className="input-group-text" id="mail-addon"><i className="ti-email" /></span>
+                    </div>
+                    <input
+                      className="form-control"
+                      type="text"
+                      aria-describedby="mail-addon"
+                      name="toEmail"
+                      placeholder="Email"
+                      value={this.state.emailToSend}
+                      onChange={(e) => { this.onChangeEmailToSend(e.target.value) }}
+                    />
+
+                  </div>
+                  <p className="p-2">
                     <b>Hint: </b>
                     <a href="https://ifttt.com/create" target="blank">{t('notification_setting.email.ifttt_link')}
                       <i className="icon-share-alt" />
                     </a>
                   </p>
-                </div>
+                </>
               )
               : (
-                <div className="form-group notify-to-option" id="slack-input">
-                  <input
-                    className="form-control"
-                    type="text"
-                    name="notificationGlobal[slackChannels]"
-                    placeholder="Slack Channel"
-                    value={this.state.slackChannelToSend}
-                    onChange={(e) => { this.onChangeSlackChannelToSend(e.target.value) }}
-                  />
-                </div>
+                <>
+                  <div className="input-group notify-to-option" id="slack-input">
+                    <div className="input-group-prepend">
+                      <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+                    </div>
+                    <input
+                      className="form-control"
+                      type="text"
+                      aria-describedby="slack-channel-addon"
+                      name="notificationGlobal[slackChannels]"
+                      placeholder="Slack Channel"
+                      value={this.state.slackChannelToSend}
+                      onChange={(e) => { this.onChangeSlackChannelToSend(e.target.value) }}
+                    />
+                  </div>
+                  <p className="p-2">
+                    {/* eslint-disable-next-line react/no-danger */}
+                    <span dangerouslySetInnerHTML={{ __html: t('notification_setting.channel_desc') }} />
+                  </p>
+                </>
               )}
           </div>
 
           <div className="offset-1 col-sm-5">
             <div className="form-group">
               <h3>{t('notification_setting.trigger_events')}</h3>
-              <TriggerEventCheckBox
-                checkbox="success"
-                event="pageCreate"
-                checked={this.state.triggerEvents.has('pageCreate')}
-                onChange={() => this.onChangeTriggerEvents('pageCreate')}
-              >
-                <span className="badge badge-pill badge-success">
-                  <i className="icon-doc"></i> CREATE
-                </span>
-              </TriggerEventCheckBox>
-              <TriggerEventCheckBox
-                checkbox="warning"
-                event="pageEdit"
-                checked={this.state.triggerEvents.has('pageEdit')}
-                onChange={() => this.onChangeTriggerEvents('pageEdit')}
-              >
-                <span className="badge badge-pill badge-warning">
-                  <i className="icon-pencil"></i>EDIT
-                </span>
-              </TriggerEventCheckBox>
-              <TriggerEventCheckBox
-                checkbox="warning"
-                event="pageMove"
-                checked={this.state.triggerEvents.has('pageMove')}
-                onChange={() => this.onChangeTriggerEvents('pageMove')}
-              >
-                <span className="badge badge-pill badge-warning">
-                  <i className="icon-action-redo"></i>MOVE
-                </span>
-              </TriggerEventCheckBox>
-              <TriggerEventCheckBox
-                checkbox="danger"
-                event="pageDelete"
-                checked={this.state.triggerEvents.has('pageDelete')}
-                onChange={() => this.onChangeTriggerEvents('pageDelete')}
-              >
-                <span className="badge badge-pill badge-danger">
-                  <i className="icon-fire"></i>DELETE
-                </span>
-              </TriggerEventCheckBox>
-              <TriggerEventCheckBox
-                checkbox="info"
-                event="pageLike"
-                checked={this.state.triggerEvents.has('pageLike')}
-                onChange={() => this.onChangeTriggerEvents('pageLike')}
-              >
-                <span className="badge badge-pill badge-info">
-                  <i className="icon-like"></i>LIKE
-                </span>
-              </TriggerEventCheckBox>
-              <TriggerEventCheckBox
-                checkbox="secondary"
-                event="comment"
-                checked={this.state.triggerEvents.has('comment')}
-                onChange={() => this.onChangeTriggerEvents('comment')}
-              >
-                <span className="badge badge-pill badge-light">
-                  <i className="icon-bubble"></i>POST
-                </span>
-              </TriggerEventCheckBox>
+              <div className="my-1">
+                <TriggerEventCheckBox
+                  checkbox="success"
+                  event="pageCreate"
+                  checked={this.state.triggerEvents.has('pageCreate')}
+                  onChange={() => this.onChangeTriggerEvents('pageCreate')}
+                >
+                  <span className="badge badge-pill badge-success">
+                    <i className="icon-doc mr-1" /> CREATE
+                  </span>
+                </TriggerEventCheckBox>
+              </div>
+              <div className="my-1">
+                <TriggerEventCheckBox
+                  checkbox="warning"
+                  event="pageEdit"
+                  checked={this.state.triggerEvents.has('pageEdit')}
+                  onChange={() => this.onChangeTriggerEvents('pageEdit')}
+                >
+                  <span className="badge badge-pill badge-warning">
+                    <i className="icon-pencil mr-1" />EDIT
+                  </span>
+                </TriggerEventCheckBox>
+              </div>
+              <div className="my-1">
+                <TriggerEventCheckBox
+                  checkbox="warning"
+                  event="pageMove"
+                  checked={this.state.triggerEvents.has('pageMove')}
+                  onChange={() => this.onChangeTriggerEvents('pageMove')}
+                >
+                  <span className="badge badge-pill badge-warning">
+                    <i className="icon-action-redo mr-1" />MOVE
+                  </span>
+                </TriggerEventCheckBox>
+              </div>
+              <div className="my-1">
+                <TriggerEventCheckBox
+                  checkbox="danger"
+                  event="pageDelete"
+                  checked={this.state.triggerEvents.has('pageDelete')}
+                  onChange={() => this.onChangeTriggerEvents('pageDelete')}
+                >
+                  <span className="badge badge-pill badge-danger">
+                    <i className="icon-fire mr-1" />DELETE
+                  </span>
+                </TriggerEventCheckBox>
+              </div>
+              <div className="my-1">
+                <TriggerEventCheckBox
+                  checkbox="info"
+                  event="pageLike"
+                  checked={this.state.triggerEvents.has('pageLike')}
+                  onChange={() => this.onChangeTriggerEvents('pageLike')}
+                >
+                  <span className="badge badge-pill badge-info">
+                    <i className="icon-like mr-1" />LIKE
+                  </span>
+                </TriggerEventCheckBox>
+              </div>
+              <div className="my-1">
+                <TriggerEventCheckBox
+                  checkbox="secondary"
+                  event="comment"
+                  checked={this.state.triggerEvents.has('comment')}
+                  onChange={() => this.onChangeTriggerEvents('comment')}
+                >
+                  <span className="badge badge-pill badge-secondary">
+                    <i className="icon-bubble mr-1" />POST
+                  </span>
+                </TriggerEventCheckBox>
+              </div>
 
             </div>
           </div>

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

@@ -77,7 +77,7 @@ class SlackAppConfiguration extends React.Component {
 
             <div className="row mb-3">
               <div className="offset-3 col-6 text-left">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     type="checkbox"
                     className="custom-control-input"

+ 4 - 4
src/client/js/components/Admin/Notification/UserNotificationRow.jsx

@@ -15,14 +15,14 @@ class UserNotificationRow extends React.PureComponent {
     return (
       <React.Fragment>
         <tr className="admin-notif-row" key={notification._id}>
-          <td>
+          <td className="px-4">
             {notification.pathPattern}
           </td>
-          <td>
-            {notification.channel}
+          <td className="px-4">
+            <span data-toggle="tooltip" data-placement="top" title="Slack"><i className="fa fa-hashtag"></i> {notification.channel}</span>
           </td>
           <td>
-            <button type="submit" className="btn btn-outline-secondary" onClick={() => { this.props.onClickDeleteBtn(notification._id) }}>{t('Delete')}</button>
+            <button type="submit" className="btn btn-outline-danger" onClick={() => { this.props.onClickDeleteBtn(notification._id) }}>{t('Delete')}</button>
           </td>
         </tr>
       </React.Fragment>

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

@@ -103,22 +103,30 @@ class UserTriggerNotification extends React.Component {
                   placeholder="e.g. /projects/xxx/MTG/*"
                   onChange={(e) => { this.changePathPattern(e.target.value) }}
                 />
-                {/* eslint-disable-next-line react/no-danger */}
-                <p className="help-block" dangerouslySetInnerHTML={{ __html: t('notification_setting.pattern_desc') }} />
+                <p className="p-2 mb-0">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <span dangerouslySetInnerHTML={{ __html: t('notification_setting.pattern_desc') }} />
+                </p>
               </td>
 
               <td>
-                <input
-                  className="form-control form-inline"
-                  type="text"
-                  name="channel"
-                  value={this.state.channel}
-                  placeholder="e.g. project-xxx"
-                  onChange={(e) => { this.changeChannel(e.target.value) }}
-                />
-                {/* eslint-disable-next-line react/no-danger */}
-                <p className="help-block" dangerouslySetInnerHTML={{ __html: t('notification_setting.channel_desc') }} />
-
+                <div className="input-group notify-to-option" id="slack-input">
+                  <div className="input-group-prepend">
+                    <span className="input-group-text"><i className="fa fa-hashtag" /></span>
+                  </div>
+                  <input
+                    className="form-control form-inline"
+                    type="text"
+                    name="channel"
+                    value={this.state.channel}
+                    placeholder="e.g. project-xxx"
+                    onChange={(e) => { this.changeChannel(e.target.value) }}
+                  />
+                </div>
+                <p className="p-2 mb-0">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <span dangerouslySetInnerHTML={{ __html: t('notification_setting.channel_desc') }} />
+                </p>
               </td>
               <td>
                 <button type="button" className="btn btn-primary" disabled={!this.validateForm()} onClick={this.onClickSubmit}>{t('add')}</button>

+ 1 - 1
src/client/js/components/Admin/Security/BasicSecuritySetting.jsx

@@ -96,7 +96,7 @@ class BasicSecurityManagement extends React.Component {
         <React.Fragment>
           <div className="row mb-5">
             <div className="offset-3 col-6">
-              <div className="custom-control custom-switch custom-checkbox-success">
+              <div className="custom-control custom-checkbox custom-checkbox-success">
                 <input
                   id="bindByEmail-basic"
                   className="custom-control-input"

+ 2 - 2
src/client/js/components/Admin/Security/GitHubSecuritySetting.jsx

@@ -83,7 +83,7 @@ class GitHubSecurityManagement extends React.Component {
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('github') && isGitHubEnabled)
-              && <div className="badg badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -149,7 +149,7 @@ class GitHubSecurityManagement extends React.Component {
 
             <div className="row mb-5">
               <div className="offset-3 col-6 text-left">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     id="bindByUserNameGitHub"
                     className="custom-control-input"

+ 1 - 1
src/client/js/components/Admin/Security/GoogleSecuritySetting.jsx

@@ -149,7 +149,7 @@ class GoogleSecurityManagement extends React.Component {
 
             <div className="row mb-5">
               <div className="offset-3 col-6">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     id="bindByUserNameGoogle"
                     className="custom-control-input"

+ 13 - 11
src/client/js/components/Admin/Security/LdapAuthTest.jsx

@@ -88,9 +88,9 @@ class LdapAuthTest extends React.Component {
       <React.Fragment>
         {this.state.successMessage != null && <div className="alert alert-success">{this.state.successMessage}</div>}
         {this.state.errorMessage != null && <div className="alert alert-warning">{this.state.errorMessage}</div>}
-        <div className="row p-3">
-          <label htmlFor="username" className="col-xs-3 text-right">{t('username')}</label>
-          <div className="col-xs-6">
+        <div className="form-group row">
+          <label htmlFor="username" className="col-3 col-form-label">{t('username')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               name="username"
@@ -99,9 +99,9 @@ class LdapAuthTest extends React.Component {
             />
           </div>
         </div>
-        <div className="row p-3">
-          <label htmlFor="password" className="col-xs-3 text-right">{t('Password')}</label>
-          <div className="col-xs-6">
+        <div className="form-group row">
+          <label htmlFor="password" className="col-3 col-form-label">{t('Password')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="password"
@@ -111,13 +111,15 @@ class LdapAuthTest extends React.Component {
             />
           </div>
         </div>
-        <div>
-          <h5>Logs</h5>
-          <textarea id="taLogs" className="col-xs-12" rows="4" value={this.state.logs} readOnly />
-        </div>
 
-        <button type="button" className="btn btn-outline-secondary mt-3 col-xs-offset-5 col-xs-2" onClick={this.testLdapCredentials}>Test</button>
+        <div className="form-group">
+          <label><h5>Logs</h5></label>
+          <textarea id="taLogs" className="col" rows="4" value={this.state.logs} readOnly />
+        </div>
 
+        <div>
+          <button type="button" className="btn btn-outline-secondary offset-5 col-2" onClick={this.testLdapCredentials}>Test</button>
+        </div>
       </React.Fragment>
 
     );

+ 1 - 1
src/client/js/components/Admin/Security/LdapSecuritySetting.jsx

@@ -275,7 +275,7 @@ class LdapSecuritySetting extends React.Component {
 
             <div className="row mb-5">
               <div className="offset-3 col-6">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     type="checkbox"
                     className="custom-control-input"

+ 2 - 2
src/client/js/components/Admin/Security/OidcSecuritySetting.jsx

@@ -262,7 +262,7 @@ class OidcSecurityManagement extends React.Component {
 
             <div className="row mb-3">
               <div className="offset-3 col-6">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     id="bindByUserName-oidc"
                     className="custom-control-input"
@@ -284,7 +284,7 @@ class OidcSecurityManagement extends React.Component {
 
             <div className="row mb-5">
               <div className="offset-3 col-6">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     id="bindByEmail-oidc"
                     className="custom-control-input"

+ 2 - 2
src/client/js/components/Admin/Security/SamlSecuritySetting.jsx

@@ -422,7 +422,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
 
             <div className="row mb-5">
               <div className="offset-3 col-6 text-left">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     id="bindByUserName-SAML"
                     className="custom-control-input"
@@ -444,7 +444,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
 
             <div className="row mb-5">
               <div className="offset-3 col-6 text-left">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     id="bindByEmail-SAML"
                     className="custom-control-input"

+ 2 - 2
src/client/js/components/Admin/Security/SecuritySetting.jsx

@@ -104,7 +104,7 @@ class SecuritySetting extends React.Component {
         <div className="row mb-5">
           <strong className="col-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_1') }} />
           <div className="col-6">
-            <div className="custom-control custom-switch custom-checkbox-success">
+            <div className="custom-control custom-checkbox custom-checkbox-success">
               <input
                 type="checkbox"
                 className="custom-control-input"
@@ -122,7 +122,7 @@ class SecuritySetting extends React.Component {
         <div className="row mb-5">
           <strong className="col-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_2') }} />
           <div className="col-6">
-            <div className="custom-control custom-switch custom-checkbox-success">
+            <div className="custom-control custom-checkbox custom-checkbox-success">
               <input
                 type="checkbox"
                 className="custom-control-input"

+ 1 - 1
src/client/js/components/Admin/Security/TwitterSecuritySetting.jsx

@@ -149,7 +149,7 @@ class TwitterSecurityManagement extends React.Component {
 
             <div className="row mb-5">
               <div className="offset-3 col-6">
-                <div className="custom-control custom-switch custom-checkbox-success">
+                <div className="custom-control custom-checkbox custom-checkbox-success">
                   <input
                     id="bindByUserNameTwitter"
                     className="custom-control-input"

+ 1 - 1
src/client/js/components/Admin/UserGroup/UserGroupTable.jsx

@@ -69,7 +69,7 @@ class UserGroupTable extends React.Component {
                   <td>
                     <ul className="list-inline">
                       {this.state.userGroupRelations[group._id].map((user) => {
-                        return <li key={user._id} className="list-inline-item badge badge-pill badge-primary">{this.xss.process(user.username)}</li>;
+                        return <li key={user._id} className="list-inline-item badge badge-pill badge-warning">{this.xss.process(user.username)}</li>;
                       })}
                     </ul>
                   </td>

+ 1 - 1
src/client/js/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx

@@ -140,7 +140,7 @@ class UserGroupUserFormByInput extends React.Component {
         <div className="col-2 pl-0">
           <button
             type="button"
-            className="btn btn-sm btn-success"
+            className="btn btn-success"
             disabled={!this.validateForm()}
             onClick={this.addUserBySubmit}
           >

+ 6 - 6
src/client/js/components/Admin/UserManagement.jsx

@@ -95,7 +95,7 @@ class UserManagement extends React.Component {
     const { t, adminUsersContainer } = this.props;
 
     const pager = (
-      <div className="pull-right">
+      <div className="pull-right my-3">
         <PaginationWrapper
           activePage={adminUsersContainer.state.activePage}
           changePage={this.handlePage}
@@ -164,7 +164,7 @@ class UserManagement extends React.Component {
                   onClick={() => { this.handleClick('all') }}
                 />
                 <label className="custom-control-label" htmlFor="c1">
-                  <span className="badge badge-primary d-inline-block vt mt-1">All</span>
+                  <span className="badge badge-pill badge-primary d-inline-block vt mt-1">All</span>
                 </label>
               </div>
 
@@ -177,7 +177,7 @@ class UserManagement extends React.Component {
                   onClick={() => { this.handleClick('registered') }}
                 />
                 <label className="custom-control-label" htmlFor="c2">
-                  <span className="badge badge-info d-inline-block vt mt-1">Approval Pending</span>
+                  <span className="badge badge-pill badge-info d-inline-block vt mt-1">Approval Pending</span>
                 </label>
               </div>
 
@@ -190,7 +190,7 @@ class UserManagement extends React.Component {
                   onClick={() => { this.handleClick('active') }}
                 />
                 <label className="custom-control-label" htmlFor="c3">
-                  <span className="badge badge-success d-inline-block vt mt-1">Active</span>
+                  <span className="badge badge-pill badge-success d-inline-block vt mt-1">Active</span>
                 </label>
               </div>
 
@@ -203,7 +203,7 @@ class UserManagement extends React.Component {
                   onClick={() => { this.handleClick('suspended') }}
                 />
                 <label className="custom-control-label" htmlFor="c4">
-                  <span className="badge badge-secondary d-inline-block vt mt-1">Suspended</span>
+                  <span className="badge badge-pill badge-secondary d-inline-block vt mt-1">Suspended</span>
                 </label>
               </div>
 
@@ -216,7 +216,7 @@ class UserManagement extends React.Component {
                   onClick={() => { this.handleClick('invited') }}
                 />
                 <label className="custom-control-label" htmlFor="c5">
-                  <span className="badge badge-info d-inline-block vt mt-1">Invited</span>
+                  <span className="badge badge-pill badge-info d-inline-block vt mt-1">Invited</span>
                 </label>
               </div>
             </div>

+ 1 - 1
src/client/js/components/Admin/Users/UserMenu.jsx

@@ -36,7 +36,7 @@ class UserMenu extends React.Component {
         <li className="dropdown-divider"></li>
         <li className="dropdown-header">{t('admin:user_management.user_table.edit_menu')}</li>
         <li>
-          <a className="dropdown-item" href="" onClick={this.onPasswordResetClicked}>
+          <a className="dropdown-item" href="#" onClick={this.onPasswordResetClicked}>
             <i className="icon-fw icon-key"></i>{ t('admin:user_management.reset_password') }
           </a>
         </li>

+ 2 - 2
src/client/js/components/Admin/Users/UserTable.jsx

@@ -56,7 +56,7 @@ class UserTable extends React.Component {
     }
 
     return (
-      <span className={`badge ${additionalClassName}`}>
+      <span className={`badge badge-pill ${additionalClassName}`}>
         {text}
       </span>
     );
@@ -71,7 +71,7 @@ class UserTable extends React.Component {
     const { t } = this.props;
 
     if (isAdmin) {
-      return <span className="badge badge-primary ml-2">{t('admin:user_management.user_table.administrator')}</span>;
+      return <span className="badge badge-primary badge-pill ml-2">{t('admin:user_management.user_table.administrator')}</span>;
     }
   }
 

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

@@ -64,7 +64,7 @@ class BookmarkButton extends React.Component {
         href="#"
         title="Bookmark"
         onClick={this.handleClick}
-        className={`btn btn-circle btn-outline-warning btn-bookmark border-0 ${`btn-${this.props.size}`} ${this.state.bookmarked && 'active'}`}
+        className={`btn btn-circle btn-outline-warning btn-bookmark border-0 ${`btn-${this.props.size}`} ${this.state.isBookmarked && 'active'}`}
       >
         <i className="icon-star"></i>
       </button>

+ 1 - 1
src/client/js/components/Me/ApiSettings.jsx

@@ -38,7 +38,7 @@ class ApiSettings extends React.Component {
     return (
       <React.Fragment>
 
-        <div className="mb-5 container-fluid">
+        <div className="container-fluid my-4">
           <h2 className="border-bottom">{ t('API Token Settings') }</h2>
         </div>
 

+ 21 - 25
src/client/js/components/Me/ExternalAccountLinkedMe.jsx

@@ -67,7 +67,7 @@ class ExternalAccountLinkedMe extends React.Component {
 
     return (
       <Fragment>
-        <div className="container-fluid p-0 my-4">
+        <div className="container-fluid my-4">
           <h2 className="border-bottom">
             <button type="button" className="btn btn-outline-secondary btn-sm pull-right" onClick={this.openAssociateModal}>
               <i className="icon-plus" aria-hidden="true" />
@@ -77,31 +77,27 @@ class ExternalAccountLinkedMe extends React.Component {
           </h2>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <table className="table table-bordered table-user-list">
-              <thead>
-                <tr>
-                  <th width="120px">Authentication Provider</th>
-                  <th>
-                    <code>accountId</code>
-                  </th>
-                  <th width="200px">{ t('Created') }</th>
-                  <th width="150px">{ t('Admin') }</th>
-                </tr>
-              </thead>
-              <tbody>
-                {externalAccounts !== 0 && externalAccounts.map(account => (
-                  <ExternalAccountRow
-                    account={account}
-                    key={account._id}
-                    openDisassociateModal={this.openDisassociateModal}
-                  />
+        <table className="table table-bordered table-user-list">
+          <thead>
+            <tr>
+              <th width="120px">Authentication Provider</th>
+              <th>
+                <code>accountId</code>
+              </th>
+              <th width="200px">{ t('Created') }</th>
+              <th width="150px">{ t('Admin') }</th>
+            </tr>
+          </thead>
+          <tbody>
+            {externalAccounts !== 0 && externalAccounts.map(account => (
+              <ExternalAccountRow
+                account={account}
+                key={account._id}
+                openDisassociateModal={this.openDisassociateModal}
+              />
                 ))}
-              </tbody>
-            </table>
-          </div>
-        </div>
+          </tbody>
+        </table>
 
         <AssociateModal
           isOpen={this.state.isAssociateModalOpen}

+ 1 - 1
src/client/js/components/Me/PasswordSettings.jsx

@@ -68,7 +68,7 @@ class PasswordSettings extends React.Component {
     return (
       <React.Fragment>
         {(!personalContainer.state.isPasswordSet) && <div className="alert alert-warning m-t-10">{ t('Password is not set') }</div>}
-        <div className="mb-5 container-fluid">
+        <div className="container-fluid my-4">
           {(personalContainer.state.isPasswordSet)
             ? <h2 className="border-bottom">{t('personal_settings.update_password')}</h2>
           : <h2 className="border-bottom">{t('personal_settings.set_new_password')}</h2>}

+ 4 - 4
src/client/js/components/Me/UserSettings.jsx

@@ -13,15 +13,15 @@ class UserSettings extends React.Component {
 
     return (
       <Fragment>
-        <div className="mb-5 container-fluid">
+        <div className="container-fluid my-4">
           <h2 className="border-bottom">{t('Basic Info')}</h2>
-          <BasicInfoSettings />
         </div>
+        <BasicInfoSettings />
 
-        <div className="mb-5 container-fluid">
+        <div className="container-fluid my-4">
           <h2 className="border-bottom">{t('Set Profile Image')}</h2>
-          <ProfileImageSettings />
         </div>
+        <ProfileImageSettings />
 
       </Fragment>
     );

+ 50 - 30
src/client/js/components/Navbar/GrowiSubNavigation.jsx

@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
 
-import { isTrashPage } from '../../../../lib/util/path-utils';
+import { isTrashPage } from '@commons/util/path-utils';
+
 import { createSubscribedElement } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
 import RevisionPath from '../Page/RevisionPath';
@@ -18,53 +19,72 @@ const GrowiSubNavigation = (props) => {
   const isPageForbidden = document.querySelector('#grw-subnav').getAttribute('data-is-forbidden-page');
   const { appContainer, pageContainer } = props;
   const {
-    pageId, path, createdAt, creator, updatedAt, revisionAuthor, isCompactMode,
+    pageId, path, createdAt, creator, updatedAt, revisionAuthor, isHeaderSticky, isSubnavCompact,
   } = pageContainer.state;
-  const compactClassName = isCompactMode ? 'fixed-top grw-compact-subnavbar px-3' : null;
 
-  // Display only the RevisionPath if the page is trash or forbidden
-  if (isTrashPage(path) || isPageForbidden) {
+  const isPageNotFound = pageId == null;
+  const isPageInTrash = isTrashPage(path);
+
+  // Display only the RevisionPath
+  if (isPageNotFound || isPageForbidden || isPageInTrash) {
     return (
-      <div className="d-flex align-items-center">
-        <div className="title-container mr-auto">
-          <h1>
-            <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageId} pagePath={pageContainer.state.path} />
-          </h1>
-        </div>
+      <div className="d-flex align-items-center px-3 py-3 grw-subnavbar">
+        <h1 className="m-0">
+          <RevisionPath
+            behaviorType={appContainer.config.behaviorType}
+            pageId={pageId}
+            pagePath={pageContainer.state.path}
+            isPageNotFound
+            isPageForbidden
+            isPageInTrash
+          />
+        </h1>
       </div>
     );
   }
 
+  const additionalClassNames = ['grw-subnavbar'];
+  if (isHeaderSticky) {
+    additionalClassNames.push('grw-subnavbar-sticky');
+  }
+  if (isSubnavCompact) {
+    additionalClassNames.push('grw-subnavbar-compact');
+  }
+
   return (
-    <div className={`d-flex align-items-center ${compactClassName}`}>
+    <div className={`d-flex align-items-center justify-content-between px-3 py-1 ${additionalClassNames.join(' ')}`}>
 
       {/* Page Path */}
-      <div className="title-container mr-auto">
-        <h1>
+      <div>
+        <h1 className="m-0">
           <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageId} pagePath={pageContainer.state.path} />
         </h1>
         <TagLabels />
       </div>
 
-      {/* Header Button */}
-      <div className="mr-2">
-        <LikeButton pageId={pageId} isLiked={pageContainer.state.isLiked} />
-      </div>
-      <div>
-        <BookmarkButton pageId={pageId} crowi={appContainer} />
-      </div>
+      <div className="d-flex align-items-center">
+        {/* Header Button */}
+        <div className="mr-2">
+          <LikeButton pageId={pageId} isLiked={pageContainer.state.isLiked} />
+        </div>
+        <div>
+          <BookmarkButton pageId={pageId} crowi={appContainer} />
+        </div>
 
-      {/* Page Authors */}
-      <ul className="authors text-nowrap d-none d-lg-block">
-        {creator != null && <li><PageCreator creator={creator} createdAt={createdAt} isCompactMode={isCompactMode} /></li>}
-        { revisionAuthor != null
-          && (
+        {/* Page Authors */}
+        <ul className="authors text-nowrap d-none d-lg-block">
+          { creator != null && (
+            <li>
+              <PageCreator creator={creator} createdAt={createdAt} isCompactMode={isSubnavCompact} />
+            </li>
+          ) }
+          { revisionAuthor != null && (
             <li className="mt-1">
-              <RevisionAuthor revisionAuthor={revisionAuthor} updatedAt={updatedAt} isCompactMode={isCompactMode} />
+              <RevisionAuthor revisionAuthor={revisionAuthor} updatedAt={updatedAt} isCompactMode={isSubnavCompact} />
             </li>
-          )
-        }
-      </ul>
+          ) }
+        </ul>
+      </div>
 
     </div>
   );

+ 29 - 36
src/client/js/components/Navbar/GrowiSubNavigationForUserPage.jsx

@@ -1,8 +1,7 @@
-import React, { useState, useEffect } from 'react';
+import React from 'react';
 import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
-import { throttle } from 'throttle-debounce';
 
 import { createSubscribedElement } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
@@ -14,53 +13,47 @@ import UserPicture from '../User/UserPicture';
 const GrowiSubNavigationForUserPage = (props) => {
   const pageUser = JSON.parse(document.querySelector('#grw-subnav-for-user-page').getAttribute('data-page-user'));
   const { appContainer, pageContainer } = props;
-  const { pageId } = pageContainer.state;
-  const [isCompactMode, setIsCompactMode] = useState(false);
-  const scrollAmountForFixed = 175;
-  const layoutType = appContainer.getConfig().layoutType;
-
-  useEffect(() => {
-    window.addEventListener('scroll', throttle(300, () => {
-      setIsCompactMode(window.pageYOffset > scrollAmountForFixed);
-    }));
-  }, []);
+  const { pageId, isHeaderSticky, isSubnavCompact } = pageContainer.state;
+
+  const additionalClassNames = ['grw-subnavbar', 'grw-subnavbar-user-page'];
+  if (isHeaderSticky) {
+    additionalClassNames.push('grw-subnavbar-sticky');
+  }
+  if (isSubnavCompact) {
+    additionalClassNames.push('py-2 grw-subnavbar-compact');
+  }
+  else {
+    additionalClassNames.push('py-3');
+  }
 
   return (
-    <div className={(isCompactMode && layoutType === 'growi') && 'fixed-top grw-compact-subnavbar px-3'}>
-
-      {/* Page Path */}
-      <h4>
+    <div className={`px-3 ${additionalClassNames.join(' ')}`}>
+      <h4 className="grw-user-page-path">
         <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageId} pagePath={pageContainer.state.path} />
       </h4>
 
-      <div className="d-flex">
-        <div className="users-info d-flex align-items-center mr-auto">
+      <div className="d-flex align-items-center justify-content-between">
+
+        <div className="users-info d-flex align-items-center">
           <UserPicture user={pageUser} />
 
           <div className="users-meta">
-            <div className="d-flex align-items-center">
-              <h1>
-                {pageUser.name}
-              </h1>
-            </div>
-            <div className="user-page-meta">
-              <ul>
-                <li className="user-page-username"><i className="icon-user mr-1"></i>{pageUser.username}</li>
-                <li className="user-page-email">
-                  <i className="icon-envelope mr-1"></i>
-                  {pageUser.isEmailPublished ? pageUser.email : '*****'}
-                </li>
-                {pageUser.introduction && <li className="user-page-introduction"><p>{pageUser.introduction}</p></li>}
-              </ul>
-            </div>
+            <h1>
+              {pageUser.name}
+            </h1>
+            <ul className="user-page-meta mt-1 mb-0">
+              <li className="user-page-username"><i className="icon-user mr-1"></i>{pageUser.username}</li>
+              <li className="user-page-email">
+                <i className="icon-envelope mr-1"></i>
+                {pageUser.isEmailPublished ? pageUser.email : '*****'}
+              </li>
+              {pageUser.introduction && <li className="user-page-introduction"><p>{pageUser.introduction}</p></li>}
+            </ul>
           </div>
         </div>
 
-        {/* Header Button */}
         <BookmarkButton pageId={pageId} crowi={appContainer} size="lg" />
       </div>
-
-
     </div>
   );
 

+ 1 - 1
src/client/js/components/Navbar/PageCreator.jsx

@@ -7,7 +7,7 @@ import { userPageRoot } from '../../../../lib/util/path-utils';
 const PageCreator = (props) => {
   const { creator, createdAt, isCompactMode } = props;
   const creatInfo = isCompactMode
-    ? (<div>Created in <span className="text-muted">{createdAt}</span></div>)
+    ? (<div>Created at <span className="text-muted">{createdAt}</span></div>)
     : (<div><div>Created by <a href={userPageRoot(creator)}>{creator.name}</a></div><div className="text-muted">{createdAt}</div></div>);
   const pictureSize = isCompactMode ? 'xs' : 'sm';
 

+ 2 - 2
src/client/js/components/Navbar/RevisionAuthor.jsx

@@ -7,8 +7,8 @@ import { userPageRoot } from '../../../../lib/util/path-utils';
 const RevisionAuthor = (props) => {
   const { revisionAuthor, updatedAt, isCompactMode } = props;
   const updateInfo = isCompactMode
-    ? (<div>Updated in <span className="text-muted">{updatedAt}</span></div>)
-    : (<div><div>Updated in  <a href={userPageRoot(revisionAuthor)}>{revisionAuthor.name}</a></div><div className="text-muted">{updatedAt}</div></div>);
+    ? (<div>Updated at <span className="text-muted">{updatedAt}</span></div>)
+    : (<div><div>Updated by  <a href={userPageRoot(revisionAuthor)}>{revisionAuthor.name}</a></div><div className="text-muted">{updatedAt}</div></div>);
   const pictureSize = isCompactMode ? 'xs' : 'sm';
 
   return (

+ 16 - 12
src/client/js/components/Page/RevisionPath.jsx

@@ -16,7 +16,6 @@ class RevisionPath extends React.Component {
       pages: [],
       isListPage: false,
       isLinkToListPage: true,
-      isInTrash: false,
     };
 
     // retrieve xss library from window
@@ -60,12 +59,6 @@ class RevisionPath extends React.Component {
     const pages = [];
     const pagePaths = [];
     splitted.forEach((pageName) => {
-      // skip trash
-      if (pageName === 'trash' && splitted.length > 1) {
-        this.setState({ isInTrash: true });
-        return;
-      }
-
       pagePaths.push(encodeURIComponent(pageName));
       pages.push({
         pagePath: urljoin('/', ...pagePaths),
@@ -109,10 +102,10 @@ class RevisionPath extends React.Component {
       padding: '0 2px',
     };
 
-    const { isInTrash } = this.state;
+    const { isPageInTrash, isPageForbidden } = this.props;
     const pageLength = this.state.pages.length;
 
-    const rootElement = isInTrash
+    const rootElement = isPageInTrash
       ? (
         <>
           <span className="path-segment">
@@ -159,9 +152,11 @@ class RevisionPath extends React.Component {
 
         <CopyDropdown t={this.props.t} pagePath={this.props.pagePath} pageId={this.props.pageId} buttonStyle={buttonStyle}></CopyDropdown>
 
-        <a href="#edit" className="d-block btn btn-outline-secondary btn-edit text-muted" style={buttonStyle}>
-          <i className="icon-note" />
-        </a>
+        { !isPageInTrash && !isPageForbidden && (
+          <a href="#edit" className="d-block text-muted btn btn-secondary bg-transparent btn-edit border-0" style={buttonStyle}>
+            <i className="icon-note" />
+          </a>
+        ) }
       </span>
     );
   }
@@ -173,6 +168,15 @@ RevisionPath.propTypes = {
   behaviorType: PropTypes.string.isRequired,
   pagePath: PropTypes.string.isRequired,
   pageId: PropTypes.string,
+  isPageNotFound: PropTypes.bool,
+  isPageForbidden: PropTypes.bool,
+  isPageInTrash: PropTypes.bool,
+};
+
+RevisionPath.defaultProps = {
+  isPageNotFound: false,
+  isPageForbidden: false,
+  isPageInTrash: false,
 };
 
 export default withTranslation()(RevisionPath);

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

@@ -46,7 +46,7 @@ export default class TagEditor extends React.Component {
 
   render() {
     return (
-      <Modal isOpen={this.state.isOpenModal} toggle={this.closeModalHandler} id="editTagModal">
+      <Modal isOpen={this.state.isOpenModal} toggle={this.closeModalHandler} id="edit-tag-modal">
         <ModalHeader tag="h4" toggle={this.closeModalHandler} className="bg-primary">
           <span className="text-white">Edit Tags</span>
         </ModalHeader>

+ 2 - 0
src/client/js/services/AppContainer.js

@@ -374,10 +374,12 @@ export default class AppContainer extends Container {
 
     // switch to dark mode
     if (isDarkMode) {
+      document.documentElement.removeAttribute('light');
       document.documentElement.setAttribute('dark', 'true');
     }
     // switch to light mode
     else {
+      document.documentElement.setAttribute('light', 'true');
       document.documentElement.removeAttribute('dark');
     }
   }

+ 19 - 7
src/client/js/services/PageContainer.js

@@ -5,10 +5,10 @@ import loggerFactory from '@alias/logger';
 import * as entities from 'entities';
 import * as toastr from 'toastr';
 
-import { throttle } from 'throttle-debounce';
-
 const logger = loggerFactory('growi:services:PageContainer');
-const scrollAmountForFixed = 122;
+const scrollThresForSticky = 50;
+const scrollThresForCompact = 100;
+const scrollThresForThrottling = 200;
 
 /**
  * Service container related to Page
@@ -58,7 +58,9 @@ export default class PageContainer extends Container {
       pageIdOnHackmd: mainContent.getAttribute('data-page-id-on-hackmd') || null,
       hasDraftOnHackmd: !!mainContent.getAttribute('data-page-has-draft-on-hackmd'),
       isHackmdDraftUpdatingInRealtime: false,
-      isCompactMode: false,
+
+      isHeaderSticky: false,
+      isSubnavCompact: false,
     };
 
     this.initStateMarkdown();
@@ -69,9 +71,19 @@ export default class PageContainer extends Container {
     this.addWebSocketEventHandlers = this.addWebSocketEventHandlers.bind(this);
     this.addWebSocketEventHandlers();
 
-    window.addEventListener('scroll', throttle(300, () => {
-      this.setState({ isCompactMode: window.pageYOffset > scrollAmountForFixed });
-    }));
+    window.addEventListener('scroll', () => {
+      const currentYOffset = window.pageYOffset;
+
+      // original throttling
+      if (this.state.isSubnavCompact && scrollThresForThrottling < currentYOffset) {
+        return;
+      }
+
+      this.setState({
+        isHeaderSticky: scrollThresForSticky < currentYOffset,
+        isSubnavCompact: scrollThresForCompact < currentYOffset,
+      });
+    });
   }
 
   /**

+ 0 - 7
src/client/styles/agile-admin/inverse/colors/_apply-colors-dark.scss

@@ -1,10 +1,3 @@
-.top-left-part {
-  .logo-mark,
-  .logo-text {
-    fill: white;
-  }
-}
-
 /*
  * Button
  */

+ 0 - 4
src/client/styles/agile-admin/inverse/colors/antarctic.scss

@@ -91,10 +91,6 @@ table,
  * Accentcolor (yellow)
  */
 
-header.affix {
-  border-bottom: 4px solid $accentcolor;
-}
-
 .modal {
   .modal-header {
     border-bottom: 4px solid $accentcolor;

+ 0 - 8
src/client/styles/agile-admin/inverse/colors/spring.scss

@@ -49,14 +49,6 @@ $wikilinktext-hover: gba(171, 224, 174, 0.9);
   border-top-color: $third-main-color;
 }
 
-/*
- * Accentcolor (green)
- */
-
-header.affix {
-  border-bottom: 4px solid $accentcolor;
-}
-
 .modal {
   .modal-header {
     border-bottom: 4px solid $accentcolor;

+ 4 - 0
src/client/styles/scss/_admin.scss

@@ -1,4 +1,8 @@
 .admin-page {
+  .grw-header.sticky-top {
+    height: unset;
+  }
+
   .admin-user-menu {
     .dropdown-menu {
       right: 0;

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

@@ -100,7 +100,7 @@
     /* end of.dropzone */
   }
 
-  .btn-open-dropzone {
+  .btn.btn-open-dropzone {
     z-index: 2;
     padding-top: 3px;
     padding-bottom: 3px;

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

@@ -47,36 +47,11 @@
 .grw-sidebar-content-container {
 }
 
-/*
-  * header
-  */
-.grw-subnav {
-  overflow: unset;
-}
-
 .grw-modal-head {
   font-size: 1em;
   border-bottom: 1px solid $grw-line-gray;
 }
 
-header {
-  padding-top: 0.5rem;
-  padding-bottom: 0.5rem;
-
-  line-height: 1em;
-  // the container of h1
-  div.title-container {
-    padding-right: 5px;
-    padding-left: 5px;
-    margin-right: auto;
-  }
-
-  h1 {
-    @include variable-font-size(28px);
-    line-height: 1.1em;
-  }
-}
-
 .main {
   margin-top: 1rem;
 }

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

@@ -221,7 +221,7 @@ body.kibela {
 
     .card-footer {
       background: white;
-      border-top: 1px solid $border
+      border-top: 1px solid $border;
     }
   }
 

+ 5 - 0
src/client/styles/scss/_me.scss

@@ -0,0 +1,5 @@
+.user-settings-page {
+  .grw-header.sticky-top {
+    height: unset;
+  }
+}

+ 1 - 1
src/client/styles/scss/_navbar.scss

@@ -7,7 +7,7 @@
     padding: 0 1rem;
   }
 
-  .personal-dropdown > .dropdown-toggle::after {
+  #personal-dropdown::after {
     // hide caret
     content: none;
   }

+ 4 - 3
src/client/styles/scss/_on-edit.scss

@@ -25,6 +25,7 @@ body.on-edit {
   }
 
   // hide unnecessary elements
+  header,
   .grw-subnav,
   .row.row-alerts,
   .row.page-list,
@@ -71,8 +72,8 @@ body.on-edit {
     }
   }
 
-  // show compact subnav
-  .grw-compact-subnav {
+  // show revision path
+  .grw-revision-path-for-edit {
     display: block !important;
   }
 
@@ -96,7 +97,7 @@ body.on-edit {
 
     background: none;
 
-    > .grw-title-bar {
+    > .grw-subnav-container {
       width: 100%; //   for crowi layout
       padding: 0; //    for crowi layout
       pointer-events: initial; // enable pointer-events

+ 8 - 8
src/client/styles/scss/_override-bootstrap-variables.scss

@@ -4,14 +4,14 @@
 
 //== Colors
 //
-$primary: #112744;
-$secondary: #6c757d;
-$info: #009fbb;
-$success: #00bb83;
-$warning: #ffa32b;
-$danger: #ff0a54;
-$light: #dee2e6;
-$dark: #343a40;
+$primary: $growi-blue !default;
+$secondary: #6c757d !default;
+$info: #009fbb !default;
+$success: #00bb83 !default;
+$warning: #ffa32b !default;
+$danger: #ff0a54 !default;
+$light: #e4e7ea !default;
+$dark: #3e4d6c !default;
 
 //== Typography
 //

+ 150 - 140
src/client/styles/scss/_override-bootstrap.scss

@@ -1,183 +1,193 @@
-* {
-  outline: none !important;
-}
-
+// increase specificity with ':root' for GROWI theming
 :root {
   font-size: $font-size-root;
   > body {
     font-family: $font-family-sans-serif;
   }
-}
 
-h1 {
-  font-size: 36px;
-  line-height: 48px;
-}
+  * {
+    outline: none !important;
+  }
 
-h2 {
-  font-size: 24px;
-  line-height: 36px;
-}
+  h1 {
+    font-size: 36px;
+    line-height: 48px;
+  }
 
-h3 {
-  font-size: 21px;
-  line-height: 30px;
-}
+  h2 {
+    font-size: 24px;
+    line-height: 36px;
+  }
 
-h4 {
-  font-size: 18px;
-  line-height: 22px;
-}
+  h3 {
+    font-size: 21px;
+    line-height: 30px;
+  }
 
-h5 {
-  font-size: 16px;
-  line-height: 18px;
-}
+  h4 {
+    font-size: 18px;
+    line-height: 22px;
+  }
 
-h6 {
-  font-size: 12px;
-  line-height: 14px;
-}
+  h5 {
+    font-size: 16px;
+    line-height: 18px;
+  }
 
-.small {
-  font-size: 65%;
-  line-height: 10px;
-}
+  h6 {
+    font-size: 12px;
+    line-height: 14px;
+  }
 
-// Navs
-.nav-tabs {
-  .nav-item {
-    margin-right: 0.15rem;
-    a.active {
-      cursor: default;
-    }
+  .small {
+    font-size: 65%;
+    line-height: 10px;
   }
-}
 
-// card (substitute panel of bootstrap3)
-.card {
-  margin-bottom: 20px;
-  border-radius: $card-border-radius;
-}
+  code {
+    padding: 2px 4px;
+    font-size: 90%;
+  }
 
-.card-header {
-  font-weight: 700;
-  text-transform: none;
-  border-radius: $card-border-radius;
-}
+  // Navs
+  .nav-tabs {
+    .nav-item {
+      margin-right: 0.15rem;
+      a.active {
+        cursor: default;
+      }
+    }
+  }
 
-.card-header:first-child {
-  border-radius: $card-border-radius;
-}
+  // card (substitute panel of bootstrap3)
+  .card {
+    margin-bottom: 20px;
+    border-radius: $card-border-radius;
+  }
 
-// Well (substitute Well of bootstrap3)
-.card.well {
-  min-height: 20px;
-  padding: $card-spacer-y $card-spacer-x;
-  border-radius: 3px;
-}
+  .card-header {
+    font-weight: 700;
+    text-transform: none;
+    border-radius: $card-border-radius;
+  }
 
-// Dropdowns
-.dropdown {
-  .dropdown-toggle.btn.disabled {
-    cursor: not-allowed;
+  .card-header:first-child {
+    border-radius: $card-border-radius;
   }
 
-  // hide caret
-  .dropdown-toggle.dropdown-toggle-no-caret::after {
-    content: none;
+  // Well (substitute Well of bootstrap3)
+  .card.well {
+    min-height: 20px;
+    padding: $card-spacer-y $card-spacer-x;
+    border-radius: 3px;
   }
-}
 
-// agile-admin style
-.dropdown-menu {
-  padding-bottom: 8px;
-  margin-top: 0px;
-  border: 1px solid $border;
-  border-radius: $radius;
-  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.05) !important;
-}
+  // Dropdowns
+  .dropdown {
+    .dropdown-toggle.btn.disabled {
+      cursor: not-allowed;
+    }
 
-.dropdown-menu > li > a {
-  width: 100%;
-  padding: 9px 20px;
-}
+    // hide caret
+    .dropdown-toggle.dropdown-toggle-no-caret::after {
+      content: none;
+    }
+  }
 
-.dropdown-menu > li > a:focus,
-.dropdown-menu > li > a:hover {
-  background: $extralight;
-}
+  // agile-admin style
+  .dropdown-menu {
+    padding-bottom: 8px;
+    margin-top: 0px;
+    border: 1px solid $border;
+    border-radius: $radius;
+    box-shadow: 0 3px 12px rgba(0, 0, 0, 0.05) !important;
+  }
+
+  .dropdown-menu > li > a {
+    width: 100%;
+    padding: 9px 20px;
+  }
+
+  .dropdown-menu > li > a:focus,
+  .dropdown-menu > li > a:hover {
+    background: $extralight;
+  }
 
-// btn-light (substitute for btn-default of bootstrap3)  (agile-admin style)
-.btn-light,
-.btn-light.disabled {
-  color: $bodytext;
-  $this-color: $btn-default-bgcolor;
-  background: $this-color;
-  border: 1px solid $this-color;
-
-  &:hover,
-  &:focus,
-  &.focus {
+  // btn-light (substitute for btn-default of bootstrap3)  (agile-admin style)
+  .btn-light,
+  .btn-light.disabled {
     color: $bodytext;
+    $this-color: $btn-default-bgcolor;
     background: $this-color;
     border: 1px solid $this-color;
-    opacity: 0.8;
-  }
 
-  &:active &.active {
-    box-shadow: none;
+    &:hover,
+    &:focus,
+    &.focus {
+      color: $bodytext;
+      background: $this-color;
+      border: 1px solid $this-color;
+      opacity: 0.8;
+    }
+
+    &:active &.active {
+      box-shadow: none;
+    }
   }
-}
 
-.btn-light.btn-outline {
-  background-color: transparent;
+  .btn-light.btn-outline {
+    background-color: transparent;
 
-  &:hover,
-  &:focus,
-  &.focus {
-    background: $btn-default-bgcolor;
+    &:hover,
+    &:focus,
+    &.focus {
+      background: $btn-default-bgcolor;
+    }
   }
-}
 
-//Modals
-.modal-content {
-  box-shadow: 0 0.3rem 1rem rgba(0, 0, 0, 0.1);
-}
+  //Modals
+  .modal-content {
+    box-shadow: 0 0.3rem 1rem rgba(0, 0, 0, 0.1);
+  }
 
-.modal-header {
-  border-bottom: 1px solid #e5e5e5;
-}
+  .modal-header {
+    border-bottom: 1px solid #e5e5e5;
+  }
 
-.modal-footer {
-  border-top: 1px solid #e5e5e5;
-}
+  .modal-footer {
+    border-top: 1px solid #e5e5e5;
+  }
 
-// col-form-label (substitute for control-label of bootstrap3)
-.col-form-label {
-  text-align: right;
-}
+  // col-form-label (substitute for control-label of bootstrap3)
+  .col-form-label {
+    text-align: right;
+  }
 
-// label
-label {
-  font-weight: 700;
-}
+  // label
+  label {
+    font-weight: 700;
+  }
 
-// disabled button (reproduction from bootstrap3.)
-// see https://cccabinet.jpn.org/bootstrap4/components/buttons#disabled-state
-.btn.disabled,
-.btn[disabled],
-fieldset[disabled] .btn {
-  cursor: not-allowed;
-}
+  // disabled button (reproduction from bootstrap3.)
+  // see https://cccabinet.jpn.org/bootstrap4/components/buttons#disabled-state
+  .btn.disabled,
+  .btn[disabled],
+  fieldset[disabled] .btn {
+    cursor: not-allowed;
+  }
 
-// progress bar
-.progress {
-  margin-bottom: 18px;
-  overflow: hidden;
-}
+  // progress bar
+  .progress {
+    margin-bottom: 18px;
+    overflow: hidden;
+  }
 
-// badge
-.badge {
-  letter-spacing: 0.05em;
+  // badge
+  .badge {
+    letter-spacing: 0.05em;
+    &.badge-warning {
+      // badge-warning text is white color in bootstrap3
+      color: white;
+    }
+  }
 }

+ 0 - 39
src/client/styles/scss/_page_growi.scss

@@ -1,39 +0,0 @@
-.growi {
-  header {
-    ul.authors {
-      padding-left: 1.5em;
-      margin: 0;
-
-      li {
-        font-size: 12px;
-        list-style: none;
-      }
-
-      .picture {
-        width: 22px;
-        height: 22px;
-        border: 1px solid #ccc;
-
-        &.picture-xs {
-          width: 14px;
-          height: 14px;
-        }
-      }
-    }
-    .grw-compact-subnavbar {
-      h2 {
-        font-size: 20px;
-        line-height: 1.1em;
-        @include media-breakpoint-down(md) {
-          font-size: 18px;
-        }
-        @include media-breakpoint-down(sm) {
-          font-size: 14px;
-        }
-        @include media-breakpoint-down(xs) {
-          font-size: 12px;
-        }
-      }
-    }
-  }
-}

+ 0 - 10
src/client/styles/scss/_page_header.scss

@@ -1,10 +0,0 @@
-#page-header {
-  &:hover {
-    .btn-copy,
-    .btn-edit,
-    .btn-edit-tags {
-      // change button opacity
-      opacity: unset;
-    }
-  }
-}

+ 97 - 0
src/client/styles/scss/_subnav.scss

@@ -0,0 +1,97 @@
+$easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
+
+@mixin setTransitionForCompactMode() {
+  // set transition-duration (normal -> compact)
+  transition: all 300ms $easeInOutCubic;
+}
+
+/*
+ * layout for sticky
+ */
+.grw-header.sticky-top {
+  // Adjust to be on top of the growi subnavigation
+  z-index: $zindex-sticky - 100;
+  height: 80px;
+  pointer-events: none; // disable pointer events for sticky
+
+  .grw-subnav {
+    overflow: unset;
+    pointer-events: all; // enable pointer events
+  }
+}
+
+/*
+ * Compact Mode Switching
+ */
+.grw-subnavbar {
+  &.grw-subnavbar-compact {
+    @include setTransitionForCompactMode();
+
+    h1 {
+      @include variable-font-size(18px);
+      @include setTransitionForCompactMode();
+    }
+  }
+}
+
+/*
+ * Sticky Mode Switching
+ */
+.grw-subnavbar {
+  &.grw-subnavbar-sticky {
+    // set transition-duration (init -> sticky)
+    transition: all 400ms linear !important;
+  }
+}
+
+/*
+ * Styles
+ */
+
+.grw-header {
+  .title {
+    padding: 0.5rem 15px;
+
+    line-height: 1em;
+
+    @include variable-font-size(28px);
+    line-height: 1.1em;
+  }
+}
+
+.grw-subnavbar {
+  &:hover {
+    .btn-copy,
+    .btn-edit,
+    .btn-edit-tags {
+      // change button opacity
+      opacity: unset;
+    }
+  }
+
+  h1 {
+    @include variable-font-size(28px);
+    line-height: 1.1em;
+  }
+
+  ul.authors {
+    padding-left: 1.5em;
+    margin: 0;
+
+    li {
+      font-size: 12px;
+      list-style: none;
+    }
+
+    .picture {
+      width: 22px;
+      height: 22px;
+      border: 1px solid #ccc;
+
+      &.picture-xs {
+        width: 14px;
+        height: 14px;
+      }
+    }
+  }
+}

+ 21 - 17
src/client/styles/scss/_tag.scss

@@ -1,31 +1,35 @@
-.tag-viewer {
-  .manage-tags {
-    font-size: 10px;
-    cursor: pointer;
+.tags-page {
+  .grw-header.sticky-top {
+    height: unset;
   }
 
-  .tag-icon:not(:first-child) {
-    margin-left: 5px;
-  }
+  .tag-viewer {
+    .manage-tags {
+      font-size: 10px;
+      cursor: pointer;
+    }
 
-  .btn-edit-tags,
-  .tag-icon {
-    font-size: 10px;
-  }
+    .tag-icon:not(:first-child) {
+      margin-left: 5px;
+    }
 
-  .tag-name {
-    margin-left: 1px;
-    font-size: 10px;
+    .btn.btn-edit-tags,
+    .tag-icon {
+      font-size: 10px;
+    }
+
+    .tag-name {
+      margin-left: 1px;
+      font-size: 10px;
+    }
   }
-}
 
-#tags-page {
   .list-tag-count {
     background: rgba(0, 0, 0, 0.08);
   }
 }
 
-#editTagModal {
+#edit-tag-modal {
   .form-control {
     height: auto;
   }

+ 54 - 29
src/client/styles/scss/_user.scss

@@ -1,4 +1,49 @@
-.user-page-header {
+$easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
+
+@mixin setTransitionForCompactMode() {
+  // set transition-duration (normal -> compact)
+  transition: all 300ms $easeInOutCubic;
+}
+
+.grw-header.grw-header-user-page {
+  height: 150px;
+}
+
+/*
+ * Compact Mode Switching
+ */
+.grw-subnavbar.grw-subnavbar-user-page {
+  &.grw-subnavbar-compact {
+    .grw-user-page-path {
+      margin-bottom: 0;
+      font-size: 14px;
+
+      @include setTransitionForCompactMode();
+    }
+    .picture {
+      width: 62px;
+      height: 62px;
+
+      @include setTransitionForCompactMode();
+    }
+    h1 {
+      font-size: 1.5em;
+      line-height: 30px;
+
+      @include setTransitionForCompactMode();
+    }
+    .users-meta {
+      margin-left: 15px;
+
+      @include setTransitionForCompactMode();
+    }
+  }
+}
+
+/*
+ * Styles
+ */
+.grw-subnavbar-user-page {
   #revision-path {
     margin-bottom: 0;
   }
@@ -14,19 +59,16 @@
   }
 
   .picture {
-    width: 64px;
-    height: 64px;
+    width: 72px;
+    height: 72px;
   }
 
-  .user-page-meta {
+  ul.user-page-meta {
+    padding-left: 0;
     color: #999;
 
-    ul {
-      padding-left: 0;
-
-      li {
-        list-style: none;
-      }
+    li {
+      list-style: none;
     }
 
     .user-page-username {
@@ -46,8 +88,8 @@
     }
   }
 
-  .btn-like,
-  .btn-bookmark {
+  .btn.btn-like,
+  .btn.btn-bookmark {
     &.btn-lg {
       padding: 8px;
       font-size: 1.5em;
@@ -55,23 +97,6 @@
   }
 }
 
-// affix
-.user-page-header.affix {
-  .users-meta {
-    margin-left: 15px;
-  }
-
-  h1 {
-    font-size: 1.5em;
-    line-height: 30px;
-  }
-
-  .picture {
-    width: 48px;
-    height: 48px;
-  }
-}
-
 .draft-list-item {
   .icon-container {
     .icon-copy,

+ 0 - 7
src/client/styles/scss/_user_growi.scss

@@ -1,11 +1,4 @@
 .growi .user-page {
-  // affix
-  .user-page-header.affix {
-    #revision-path {
-      display: none;
-    }
-  }
-
   .revision-toc {
     position: sticky;
     top: 105px;

+ 13 - 5
src/client/styles/scss/_vendor.scss

@@ -1,12 +1,20 @@
-// import bootstrap
-@import '~bootstrap/scss/bootstrap';
+// import bootstrap configurations
+@import '~bootstrap/scss/functions';
+@import '~bootstrap/scss/variables';
+@import '~bootstrap/scss/mixins';
+@import '~bootstrap/scss/utilities';
+
+// increase specificity with ':root' for GROWI theming
+:root {
+  // import bootstrap
+  @import '~bootstrap/scss/bootstrap';
+  // import toastr styles
+  @import '~toastr/build/toastr';
+}
 
 // import react-bootstrap-typeahead
 @import '~react-bootstrap-typeahead/css/Typeahead';
 
-// import toastr styles
-@import '~toastr/build/toastr';
-
 // import CodeMirror styles
 @import '~codemirror/lib/codemirror.css';
 @import '~codemirror/theme/elegant.css';

+ 6 - 5
src/client/styles/scss/atoms/_buttons.scss

@@ -1,4 +1,4 @@
-.btn-circle {
+.btn.btn-circle {
   width: 30px;
   height: 30px;
   padding: 6px 0;
@@ -26,12 +26,13 @@
   border-radius: 35px;
 }
 
-.btn-like,
-.btn-bookmark {
+.btn.btn-like,
+.btn.btn-bookmark {
   font-size: 1.2em;
-  line-height: 0.8em;
+  line-height: 1em;
 
-  &.active {
+  &.active,
+  &:hover {
     // header buttons are always white for active
     color: white !important;
   }

+ 3 - 3
src/client/styles/scss/style-app.scss

@@ -20,7 +20,7 @@
 @import 'atoms/spinners';
 @import 'atoms/custom_control';
 
-// crowi component
+// growi component
 @import 'admin';
 @import 'attachments';
 @import 'comment';
@@ -41,16 +41,16 @@
 @import 'layout_kibela';
 @import 'layout_variable';
 @import 'login';
+@import 'me';
 @import 'navbar';
 @import 'navbar_kibela';
 @import 'notification';
 @import 'on-edit';
 @import 'page_list';
 @import 'page';
-@import 'page_header';
-@import 'page_growi';
 @import 'search';
 @import 'shortcuts';
+@import 'subnav';
 @import 'tag';
 @import 'user';
 @import 'user_growi';

+ 18 - 19
src/client/styles/scss/theme/_apply-colors-dark.scss

@@ -1,10 +1,3 @@
-.logo {
-  .logo-mark,
-  .logo-text {
-    fill: white;
-  }
-}
-
 /*
  * Button
  */
@@ -111,15 +104,6 @@ textarea.form-control {
   // border: 1px solid $border;
 }
 
-/*
- * GROWI header
- */
-header.affix {
-  .logo-mark {
-    fill: white;
-  }
-}
-
 /*
  * GROWI page list
  */
@@ -139,9 +123,24 @@ header.affix {
 /*
  * GROWI subnavigation
  */
-.grw-compact-subnavbar {
-  background-color: rgba(darken($bgcolor-global, 90%), 0.9);
-  box-shadow: 0 0 2px darken($bgcolor-global, 5%);
+.admin-page,
+.user-settings-page,
+.tags-page {
+  .grw-header {
+    background-color: rgba(darken($bgcolor-global, 90%), 0.9);
+  }
+}
+
+.grw-subnavbar {
+  background-color: rgba(darken($bgcolor-global, 90%), 1);
+
+  &.grw-subnavbar-sticky {
+    background-color: rgba(darken($bgcolor-global, 90%), 0.9);
+    box-shadow: 0 3px 2px -2px darken($bgcolor-global, 5%);
+  }
+}
+
+.grw-subnavbar-sticky {
 }
 
 /*

+ 15 - 12
src/client/styles/scss/theme/_apply-colors-light.scss

@@ -11,15 +11,6 @@
   background-color: darken($bgcolor-global, 5%);
 }
 
-/*
- * GROWI header
- */
-header.affix {
-  .logo-mark {
-    fill: theme-color('primary');
-  }
-}
-
 /*
  * GROWI search-top
  */
@@ -35,9 +26,21 @@ header.affix {
 /*
  * GROWI subnavigation
  */
-.grw-compact-subnavbar {
-  background-color: rgba(darken($bgcolor-global, 6%), 0.9);
-  box-shadow: 0 0 2px darken($bgcolor-global, 40%);
+.admin-page,
+.user-settings-page,
+.tags-page {
+  .grw-header {
+    background-color: rgba(darken($bgcolor-global, 6%), 0.9);
+  }
+}
+
+.grw-subnavbar {
+  background-color: rgba(darken($bgcolor-global, 5%), 1);
+
+  &.grw-subnavbar-sticky {
+    background-color: rgba(darken($bgcolor-global, 6%), 0.9);
+    box-shadow: 0 3px 2px -2px darken($bgcolor-global, 40%);
+  }
 }
 
 /*

+ 15 - 22
src/client/styles/scss/theme/_apply-colors.scss

@@ -8,8 +8,12 @@ $body-color: $color-global;
 $link-color: $color-link;
 $link-hover-color: $color-link-hover;
 
+@import '~bootstrap/scss/functions';
+@import '~bootstrap/scss/variables';
+@import '~bootstrap/scss/mixins';
 @import 'reboot-bootstrap-colors';
 @import 'reboot-bootstrap-theme-colors';
+@import 'reboot-toastr-colors';
 
 // Link buttons
 .btn-link {
@@ -28,9 +32,6 @@ $link-hover-color: $color-link-hover;
     color: $color-dropdown-link-active;
     background-color: $bgcolor-dropdown-link-active;
   }
-  &:hover:not(.active) {
-    color: $color-dropdown-link-hover;
-  }
 }
 
 // Form
@@ -78,21 +79,12 @@ $link-hover-color: $color-link-hover;
 }
 
 .grw-navbar {
-  // TODO: coloring
-  // background: $bgcolor-navbar;
-  // .nav-item > .nav-link {
-  //   color: white;
-  // }
-  background: darken($bgcolor-global, 8%);
+  background: $bgcolor-navbar;
   .nav-item > .nav-link {
-    color: #333;
+    color: $color-link-nabvar;
   }
 }
 
-.grw-title-bar {
-  background: darken($bgcolor-global, 2%);
-}
-
 .grw-sidebar {
   .grw-logo {
     background-color: darken($bgcolor-navbar, 10%);
@@ -214,13 +206,6 @@ $link-hover-color: $color-link-hover;
   }
 }
 
-/*
- * GROWI header
- */
-header.affix {
-  background: rgba(darken($bgcolor-global, 2%), 0.9);
-}
-
 /*
  * GROWI on-edit
  */
@@ -298,6 +283,14 @@ body.on-edit {
   }
 }
 
+/*
+ * react bootstrap typeahead
+ */
+mark.rbt-highlight-text {
+  // Temporarily the highlight color is black
+  color: black;
+}
+
 /*
  * GROWI page attachments
  */
@@ -317,4 +310,4 @@ body.on-edit {
       }
     }
   }
-}
+}

+ 5 - 12
src/client/styles/scss/theme/_reboot-bootstrap-colors.scss

@@ -1,6 +1,3 @@
-@import '~bootstrap/scss/functions';
-@import '~bootstrap/scss/variables';
-@import '~bootstrap/scss/mixins';
 //
 //
 // Apply partially
@@ -190,17 +187,13 @@ body {
 //
 
 a {
-  :not(.badge) {
-    color: $link-color;
-    // text-decoration: $link-decoration;
-    background-color: transparent; // Remove the gray background on active links in IE 10.
-  }
+  color: $link-color;
+  text-decoration: $link-decoration;
+  background-color: transparent; // Remove the gray background on active links in IE 10.
 
   @include hover() {
-    &:not(.list-group-item) {
-      color: $link-hover-color;
-      // text-decoration: $link-hover-decoration;
-    }
+    color: $link-hover-color;
+    text-decoration: $link-hover-decoration;
   }
 }
 

+ 15 - 0
src/client/styles/scss/theme/_reboot-toastr-colors.scss

@@ -0,0 +1,15 @@
+.toast-success {
+  background-color: $success;
+}
+
+.toast-error {
+  background-color: $danger;
+}
+
+.toast-info {
+  background-color: $info;
+}
+
+.toast-warning {
+  background-color: $warning;
+}

+ 116 - 6
src/client/styles/scss/theme/antarctic.scss

@@ -1,8 +1,118 @@
-// import colors
-@import '../../agile-admin/inverse/colors/antarctic';
+@import '../variables';
+@import '../override-bootstrap-variables';
 
-// apply agile-admin theme
-@import '../../agile-admin/inverse/style';
+// == Define Bootstrap theme colors
+//
 
-// override
-@import 'override-agileadmin';
+// colors for overriding bootstrap $theme-colors
+// $secondary: #;
+// $info: #;
+// $success: #;
+// $warning: #;
+// $danger: #;
+// $light: #;
+// $dark: #;
+
+.growi:not(.login-page) {
+  // add background-image
+  #page-wrapper,
+  .page-editor-preview-container {
+    background-image: url('/images/themes/antarctic/bg.svg');
+    background-attachment: fixed;
+    background-position: center center;
+    background-size: cover;
+  }
+}
+
+.growi.login-page {
+  #page-wrapper {
+    background-image: url('/images/themes/antarctic/topimage.svg');
+    background-attachment: fixed;
+    background-position: center center;
+    background-size: cover;
+  }
+}
+
+$themecolor: #000080;
+$themelight: #f0f8ff;
+$accentcolor: #ffd700;
+
+.grw-navbar {
+  border-bottom: $accentcolor 4px solid;
+}
+
+//== Light Mode
+//
+html[light] {
+  $primary: $themecolor;
+
+  // Background colors
+  $bgcolor-global: $themelight;
+  $bgcolor-navbar: #334455;
+  $bgcolor-inline-code: #f9f2f4;
+  $bgcolor-card: #f5f5f5;
+
+  // Font colors
+  $color-global: black;
+  $color-reversal: #eeeeee;
+  // $color-header: #2b2b2b;
+  $color-link: lighten($primary, 20%);
+  $color-link-hover: lighten($color-link, 20%);
+  $color-link-wiki: lighten($primary, 20%);
+  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
+  $color-link-nabvar: $color-reversal;
+  $color-inline-code: #c7254e;
+
+  // Logo colors
+  $fillcolor-logo-mark: lighten(desaturate($bgcolor-navbar, 10%), 15%);
+
+  // Border colors
+  $border-color-theme: #ccc; // former: `$navbar-border: #ccc;`
+
+  // Dropdown colors
+  $bgcolor-dropdown-link-active: $growi-blue;
+  $color-dropdown-link-active: $color-reversal;
+  $color-dropdown-link-hover: $color-global;
+
+  @import 'apply-colors';
+  @import 'apply-colors-light';
+}
+
+//== Dark Mode
+//
+html[dark] {
+  $primary: #d65a31;
+
+  $basecolor: #222831;
+
+  // Background colors
+  $bgcolor-global: $basecolor;
+  $bgcolor-navbar: #151515;
+  $bgcolor-inline-code: darken($basecolor, 5%);
+  $bgcolor-card: darken($basecolor, 5%);
+
+  // Font colors
+  $color-global: #eeeeee;
+  $color-reversal: #333333;
+  // $color-header: desaturate($primary, 20%);
+  $color-link: $primary;
+  $color-link-hover: lighten($color-link, 10%);
+  $color-link-wiki: lighten($basecolor, 50%);
+  $color-link-wiki-hover: darken($color-link-wiki, 5%);
+  $color-link-nabvar: $color-global;
+  $color-inline-code: #c7254e;
+
+  // Logo colors
+  $fillcolor-logo-mark: #444;
+
+  // Border colors
+  $border-color-theme: black; // former: `$navbar-border: #ccc;`
+
+  // Dropdown colors
+  $bgcolor-dropdown-link-active: $primary;
+  $color-dropdown-link-active: $color-global;
+  $color-dropdown-link-hover: $color-reversal;
+
+  @import 'apply-colors';
+  @import 'apply-colors-dark';
+}

+ 4 - 2
src/client/styles/scss/theme/default.scss

@@ -15,7 +15,7 @@
 
 //== Light Mode
 //
-html:not([dark]) {
+html[light] {
   $primary: #112744;
 
   // Background colors
@@ -32,6 +32,7 @@ html:not([dark]) {
   $color-link-hover: lighten($color-link, 20%);
   $color-link-wiki: lighten($primary, 20%);
   $color-link-wiki-hover: lighten($color-link-wiki, 20%);
+  $color-link-nabvar: $color-reversal;
   $color-inline-code: #c7254e;
 
   // Logo colors
@@ -43,7 +44,7 @@ html:not([dark]) {
   // Dropdown colors
   $bgcolor-dropdown-link-active: $growi-blue;
   $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
+  $color-dropdown-link-hover: $color-reversal;
 
   @import 'apply-colors';
   @import 'apply-colors-light';
@@ -70,6 +71,7 @@ html[dark] {
   $color-link-hover: lighten($color-link, 10%);
   $color-link-wiki: lighten($basecolor, 50%);
   $color-link-wiki-hover: darken($color-link-wiki, 5%);
+  $color-link-nabvar: $color-global;
   $color-inline-code: #c7254e;
 
   // Logo colors

+ 1 - 3
src/server/views/admin/Users_reserve.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('User_Management')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('User_Management') }}</h1>
-</header>
+<h1 class="title">{{ t('User_Management') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 2 - 3
src/server/views/admin/app.html

@@ -6,11 +6,10 @@
 {% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('App settings') }}</h1>
-</header>
+<h1 class="title">{{ t('App settings') }}</h1>
 {% endblock %}
 
+
 {% block content_main %}
 <div class="content-main admin-app row">
   {% parent %}

+ 1 - 3
src/server/views/admin/customize.html

@@ -13,9 +13,7 @@
 {% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('Customize') }}</h1>
-</header>
+<h1 class="title">{{ t('Customize') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/export.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('Export Archive Data')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('Export Archive Data') }}</h1>
-</header>
+<h1 class="title">{{ t('Export Archive Data') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/external-accounts.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('external_account_management')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('User_Management') }} / {{ t('external_account_management') }}</h1>
-</header>
+<h1 class="title">{{ t('User_Management') }} / {{ t('external_account_management') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/global-notification-detail.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('Notification settings')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('Notification settings') }}</h1>
-</header>
+<h1 class="title">{{ t('Notification settings') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/importer.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('Import Data')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('Import Data') }}</h1>
-</header>
+<h1 class="title">{{ t('Import Data') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/index.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('Management Wiki Home')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title"> {{ t('Management Wiki Home') }}</h1>
-</header>
+<h1 class="title"> {{ t('Management Wiki Home') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/markdown.html

@@ -4,9 +4,7 @@
  · {{ path }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('Markdown Settings') }}</h1>
-</header>
+<h1 class="title">{{ t('Markdown Settings') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/notification.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('Notification settings')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('Notification settings') }}</h1>
-</header>
+<h1 class="title">{{ t('Notification settings') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/search.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('Full Text Search management')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('Full Text Search management') }}</h1>
-</header>
+<h1 class="title">{{ t('Full Text Search management') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/security.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('security_settings')) }} · {% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('security_settings') }}</h1>
-</header>
+<h1 class="title">{{ t('security_settings') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/user-group-detail.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('UserGroup Management') + '/' + userGroup.name) | preventXss }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('UserGroup Management') + '/' + userGroup.name | preventXss }}</h1>
-</header>
+<h1 class="title">{{ t('UserGroup Management') + '/' + userGroup.name | preventXss }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/user-groups.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('UserGroup Management')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('UserGroup Management') }}</h1>
-</header>
+<h1 class="title">{{ t('UserGroup Management') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 3
src/server/views/admin/users.html

@@ -3,9 +3,7 @@
 {% block html_title %}{{ customizeService.generateCustomTitle(t('User_Management')) }}{% endblock %}
 
 {% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('User_Management') }}</h1>
-</header>
+<h1 class="title">{{ t('User_Management') }}</h1>
 {% endblock %}
 
 {% block content_main %}

+ 1 - 1
src/server/views/layout-crowi/base/layout.html

@@ -31,7 +31,7 @@
   </aside>
 
   <div class="row grw-subnav">
-    <div class="col-md-9 grw-title-bar">
+    <div class="col-md-9">
       {% block content_header %}
       {% endblock %}
     </div>

+ 7 - 8
src/server/views/layout-growi/base/layout.html

@@ -7,15 +7,15 @@
 {% endblock %}
 
 {% block layout_main %}
-<div class="container-fluid">
 
-  <div class="row grw-subnav">
-    <div class="col-12 grw-title-bar">
-      {% block content_header %}
-      {% endblock %}
-    </div><!-- /.grw-title-bar -->
-  </div><!-- /.row -->
+{% block content_header_wrapper %}
+<header class="sticky-top py-0 grw-header">
+  {% block content_header %}
+  {% endblock %}
+</header>
+{% endblock %}
 
+<div class="container-fluid">
   <div class="row">
     <div id="main" class="main col-md-12 {% if page %}{{ css.grant(page) }}{% endif %} {% block main_css_class %}{% endblock %}">
       {% block content_main_before %}
@@ -28,7 +28,6 @@
       {% endblock %}
     </div><!-- /.main -->
   </div><!-- /.row -->
-
 </div><!-- /.container-fluid -->
 
 <footer class="footer">

+ 8 - 6
src/server/views/layout-growi/user_page.html

@@ -5,12 +5,14 @@
   user-page
 {% endblock %}
 
-{% block content_header %}
-  {% if pageUser %}
-    <header id="grw-subnav-for-user-page" class="user-page-header" data-page-user="{{ pageUser|json }}"></header>
-  {% else %}
-    {% parent %}
-  {% endif %}
+{% block content_header_wrapper %}
+  <header class="sticky-top py-0 grw-header grw-header-user-page">
+    {% if pageUser %}
+      <div id="grw-subnav-for-user-page" class="grw-subnav" data-page-user="{{ pageUser|json }}"></div>
+    {% else %}
+      {% parent %}
+    {% endif %}
+  </header>
 {% endblock %}
 
 

+ 6 - 10
src/server/views/layout-growi/widget/header.html

@@ -1,11 +1,7 @@
-<header id="page-header">
+<div id="grw-subnav" class="grw-subnav" data-is-forbidden-page="{{ forbidden }}"></div>
 
-  <div id="grw-subnav" data-is-forbidden-page="{{ forbidden }}"></div>
-
-    {% if not page and not forbidden and ('/' === path or 'crowi' === getConfig('crowi', 'customize:behavior')) and not isUserPageList(path) and !isTrashPage() %}
-      {% if '/' === path.slice(-1) %}
-        {% include '../../widget/create_portal.html' %}
-      {% endif %}
-    {% endif %}
-
-</header>
+{% if not page and not forbidden and ('/' === path or 'crowi' === getConfig('crowi', 'customize:behavior')) and not isUserPageList(path) and !isTrashPage() %}
+  {% if '/' === path.slice(-1) %}
+    {% include '../../widget/create_portal.html' %}
+  {% endif %}
+{% endif %}

+ 2 - 2
src/server/views/layout-growi/widget/liker-and-seenusers.html

@@ -1,14 +1,14 @@
 <div class="liker-and-seenusers">
   <div class="text-right">
     {% if page.liker.length > 10 %}<span class="text-muted">..</span>{% endif %}
-    <span id="liker-list" class="mr-3" data-user-ids="{{ page.liker|slice(-10)|default([])|join(',') }}"></span>
+    <span id="liker-list" class="mr-3" data-user-ids="{{ page.liker|slice(-9)|default([])|join(',') }}"></span>
     <span class="text-info">
       <i class="icon-fw icon-like"></i><span class="liker-user-count">{{ page.liker.length|default(0) }}</span>
     </span>
   </div>
   <div class="text-right">
     {% if page.seenUsers.length > 10 %}<span class="text-muted">..</span>{% endif %}
-    <span id="seen-user-list" class="mr-3" data-user-ids="{{ page.seenUsers|slice(-10)|default([])|join(',') }}"></span>
+    <span id="seen-user-list" class="mr-3" data-user-ids="{{ page.seenUsers|slice(-9)|default([])|join(',') }}"></span>
     <span class="text-danger">
       <i class="icon-fw fa fa-paw"></i><span class="seen-user-count">{{ page.seenUsers.length|default(0) }}</span>
     </span>

+ 1 - 1
src/server/views/layout-kibela/base/layout.html

@@ -13,7 +13,7 @@
 
     <div id="main" class="main col-12 kibela-block bg-white round-corner {% if page %}{{ css.grant(page) }}{% endif %}{% block main_css_class %}{% endblock %}">
       <div class="row grw-subnav">
-        <div class="col-12 grw-title-bar">
+        <div class="col-12">
           {% block content_header %} {% endblock %}
         </div>
       </div>

+ 1 - 1
src/server/views/layout-kibela/user_page.html

@@ -7,7 +7,7 @@
 
 {% block content_header %}
   {% if pageUser %}
-    <header id="grw-subnav-for-user-page" class="user-page-header" data-page-user="{{ pageUser|json }}"></header>
+    <header id="grw-subnav-for-user-page" class="grw- subnav grw-subnav-user-page" data-page-user="{{ pageUser|json }}"></header>
   {% else %}
     {% parent %}
   {% endif %}

+ 1 - 1
src/server/views/layout/layout.html

@@ -114,7 +114,7 @@
             <i class="icon-question mr-2"></i><span class="d-none d-md-inline-block mr-2">{{ t('Help') }}</span><span class="text-muted small"><i class="icon-share-alt"></i></span>
           </a>
         </li>
-        <li id="personal-dropdown" class="nav-item dropdown"></li>
+        <li id="personal-dropdown" class="nav-item dropdown dropdown-toggle"></li>
         {% else %}
         <li id="login-user" class="nav-item"><a class="nav-link" href="/login">Login</a></li>
         {% endif %}

+ 0 - 85
src/server/views/me/api_token.html

@@ -1,85 +0,0 @@
-{% extends '../layout-growi/base/layout.html' %}
-
-
-{% block html_title %}{{ customizeService.generateCustomTitle(t('API Settings')) }}{% endblock %}
-
-
-{% block content_header %}
-<header id="page-header">
-  <h1 id="admin-title" class="title">{{ t('API Settings') }}</h1>
-</header>
-{% endblock %}
-
-{% block content_main %}
-<div class="content-main">
-
-  <ul class="nav nav-tabs">
-    <li><a href="/me"><i class="icon-user"></i> {{ t('User Information') }}</a></li>
-    <li><a href="/me/external-accounts"><i class="icon-share-alt"></i> {{ t('External Accounts') }}</a></li>
-    <li><a href="/me/password"><i class="icon-lock"></i> {{ t('Password Settings') }}</a></li>
-    <li class="active"><a href="/me/apiToken"><i class="icon-paper-plane"></i> {{ t('API Settings') }}</a></li>
-  </ul>
-
-  <div class="tab-content">
-
-  {% set message = req.flash('successMessage') %}
-  {% if message.length %}
-  <div class="alert alert-success grw-mt-10px">
-    {{ message }}
-  </div>
-  {% endif %}
-
-  {% if req.form.errors.length > 0 %}
-  <div class="alert alert-danger grw-mt-10px">
-    <ul>
-    {% for error in req.form.errors %}
-      <li>{{ error }}</li>
-    {% endfor %}
-    </ul>
-  </div>
-  {% endif %}
-
-  <div class="form-box m-t-20">
-
-    <form action="/me/apiToken" method="post" class="form-horizontal" role="form">
-    <fieldset>
-      <legend>{{ t('API Token Settings') }}</legend>
-      <div class="form-group {% if not user.password %}has-error{% endif %}">
-        <label for="" class="col-xs-3 control-label">{{ t('Current API Token') }}</label>
-        <div class="col-xs-6">
-          {% if user.apiToken %}
-            <input class="form-control" type="text" value="{{ user.apiToken }}">
-          {% else %}
-          <p class="form-control-static">
-            {{ t('page_me_apitoken.notice.apitoken_issued') }}
-          </p>
-          {% endif %}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <div class="col-xs-offset-3 col-xs-9">
-
-          <p class="alert alert-warning">
-            {{ t('page_me_apitoken.notice.update_token1') }}<br>
-            {{ t('page_me_apitoken.notice.update_token2') }}
-          </p>
-
-          <button type="submit" value="1" name="apiTokenForm[confirm]" class="btn btn-primary">{{ t('Update API Token') }}</button>
-        </div>
-      </div>
-
-    </fieldset>
-    </form>
-  </div>
-
-
-  </div>
-</div>
-{% endblock content_main %}
-
-{% block content_footer %}
-{% endblock %}
-
-{% block layout_footer %}
-{% endblock %}

+ 0 - 249
src/server/views/me/external-accounts.html

@@ -1,249 +0,0 @@
-{% extends '../layout-growi/base/layout.html' %}
-
-{% block html_title %}{{ customizeService.generateCustomTitle(t('user_management.external_account')) }}{% endblock %}
-
-{% block content_header %}
-<header id="page-header">
-  <h1 id="mypage-title" class="title">{{ t('user_management.external_account') }}</h1>
-</header>
-{% endblock %}
-
-{% block content_main %}
-<div class="content-main">
-
-  <ul class="nav nav-tabs">
-    <li><a href="/me"><i class="icon-user"></i> {{ t('User Information') }}</a></li>
-    <li class="active"><a href="/me/external-accounts"><i class="icon-share-alt"></i> {{ t('External Accounts') }}</a></li>
-    <li><a href="/me/password"><i class="icon-lock"></i> {{ t('Password Settings') }}</a></li>
-    <li><a href="/me/apiToken"><i class="icon-paper-plane"></i> {{ t('API Settings') }}</a></li>
-  </ul>
-
-  <div class="tab-content">
-
-  {% set couldntDisassociateError = req.flash('couldntDisassociateError') %}
-  {% if couldntDisassociateError != null %}
-  <div class="alert alert-danger grw-mt-10px">
-    <b>Couldn't disassociate External Account</b><br>
-    You have not set a password and have only one External Account.
-  </div>
-  {% endif %}
-
-  {% set error = req.flash('errorMessage') %}
-  {% if error.length %}
-  {% for e in error %}
-  <div class="alert alert-danger grw-mt-10px">
-    <b>Server Error occured:</b><br>
-    {{ e }}
-  </div>
-  {% endfor %}
-  {% endif %}
-
-  {% set warn = req.flash('warningMessage') %}
-  {% if warn.length %}
-  {% for w in warn %}
-  <div class="alert alert-warning grw-mt-10px">
-    {{ w }}
-  </div>
-  {% endfor %}
-  {% endif %}
-
-  {% set message = req.flash('successMessage') %}
-  {% if message.length %}
-  <div class="alert alert-success grw-mt-10px">
-    <b>{{ message }}</b>
-  </div>
-  {% endif %}
-
-
-
-  <legend class="m-t-20" style="line-height: 1.7em;">
-    <button class="btn btn-default btn-sm float-right" data-target="#create-external-account" data-toggle="modal">
-      <i class="icon-plus" aria-hidden="true"></i>
-      Add
-    </button>
-    {{ t('External Accounts') }}
-  </legend>
-
-  <div class="row">
-    <div class="col-md-12">
-      <table class="table table-bordered table-user-list">
-        <thead>
-          <tr>
-            <th width="120px">Authentication Provider</th>
-            <th>
-              <code>accountId</code>
-            </th>
-            <th width="200px">{{ t('Created') }}</th>
-            <th width="150px">{{ t('Admin') }}</th>
-          </tr>
-        </thead>
-        <tbody>
-          {% for account in externalAccounts %}
-          <tr>
-            <td>{{ account.providerType }}</td>
-            <td>
-              <strong>{{ account.accountId }}</strong>
-            </td>
-            <td>{{ account.createdAt|datetz('Y-m-d') }}</td>
-            <td class="text-center">
-              <button class="btn btn-default btn-sm btn-danger"
-                  data-toggle="modal" data-target="#diassociate-external-account" data-provider-type="{{ account.providerType }}" data-account-id="{{ account.accountId }}">
-                <i class="ti-unlink"></i>
-                {{ t('Diassociate') }}
-              </button>
-            </td>
-          </tr>
-          {% endfor %}
-        </tbody>
-      </table>
-    </div>
-  </div>
-
-  {# modal #}
-  <style>
-    .modal.create-external-account .modal-dialog {
-      width: 750px;
-    }
-  </style>
-  <div class="modal create-external-account" id="create-external-account">
-    <div class="modal-dialog">
-      <div class="modal-content">
-
-        <div class="modal-header bg-info">
-          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-          <div class="modal-title">{{ t('Create External Account') }}</div>
-        </div>
-
-        <div class="modal-body">
-
-          <ul class="nav nav-tabs passport-settings" role="tablist">
-            <li class="active">
-              <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="fa fa-sitemap"></i> LDAP</a>
-            </li>
-            <li class="tbd">
-              <a href="#passport-github" data-toggle="tab" role="tab"><i class="fa fa-github"></i> (TBD) GitHub</a>
-            </li>
-            <li class="tbd">
-              <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i class="fa fa-google"></i> (TBD) Google OAuth</a>
-            </li>
-            <li class="tbd">
-              <a href="#passport-facebook" data-toggle="tab" role="tab"><i class="fa fa-facebook"></i> (TBD) Facebook</a>
-            </li>
-            <li class="tbd">
-              <a href="#passport-twitter" data-toggle="tab" role="tab"><i class="fa fa-twitter"></i> (TBD) Twitter</a>
-            </li>
-          </ul>
-
-          <div class="tab-content passport-settings mt-4">
-            <div id="passport-ldap" class="tab-pane active" role="tabpanel" >
-              <div id="formLdapAssociationContainer">
-                {% include '../widget/passport/ldap-association-tester.html' %}
-                <div class="clearfix">
-                  <button type="button" class="btn btn-info float-right" onclick="associateLdap()">
-                    <i class="fa fa-plus-circle" aria-hidden="true"></i>
-                    {{ t('add') }}
-                  </button>
-                </div>
-              </div>
-            </div>
-
-            <div id="passport-google-oauth" class="tab-pane" role="tabpanel">
-              (TBD)
-            </div>
-
-            <div id="passport-github" class="tab-pane" role="tabpanel">
-              (TBD)
-            </div>
-
-            <div id="passport-facebook" class="tab-pane" role="tabpanel">
-              (TBD)
-            </div>
-
-            <div id="passport-twitter" class="tab-pane" role="tabpanel">
-              (TBD)
-            </div>
-
-          </div><!-- /.tab-content -->
-
-        </div><!-- /.modal-body -->
-
-      </div><!-- /.modal-content -->
-    </div><!-- /.modal-dialog -->
-
-    <script>
-      /**
-       * associate (submit the form)
-       */
-      function associateLdap() {
-        var $form = $('#formLdapAssociationContainer > form');
-        var $action = '/me/external-accounts/associateLdap';
-        $form.attr('action', $action);
-        $form.submit();
-      }
-    </script>
-
-  </div><!-- /.modal -->
-
-  <div class="modal diassociate-external-account" id="diassociate-external-account">
-    <div class="modal-dialog">
-      <div class="modal-content">
-
-        <div class="modal-header">
-          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-          <div class="modal-title">{{ t('Diassociate External Account') }}</div>
-        </div>
-
-        <div class="modal-body">
-          <div class="row">
-            <div class="col-md-12">
-              <p><b>
-                Are you sure to diassociate the
-                <span class="diassociate-provider-type"></span> account
-                <code class="diassociate-account-id"></code>?
-              </b></p>
-            </div>
-          </div>
-        </div>
-
-        <div class="modal-footer">
-          <form action="/me/external-accounts/disassociate" method="post">
-            <input type="hidden" name="_csrf" value="{{ csrf() }}">
-            <input type="hidden" name="providerType">
-            <input type="hidden" name="accountId">
-            <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">
-              {{ t('Cancel') }}
-            </button>
-            <button type="submit" class="btn btn-sm btn-danger">
-              <i class="ti-unlink"></i>
-              {{ t('Diassociate') }}
-            </button>
-          </form>
-        </div>
-      </div><!-- /.modal-content -->
-    </div><!-- /.modal-dialog -->
-
-    <script>
-      $('#diassociate-external-account').on('show.bs.modal', function (event) {
-        var modal = $(this);
-        var button = $(event.relatedTarget); // Button that triggered the modal
-        // get data-*
-        var providerType = button.data('provider-type');
-        var accountId = button.data('account-id');
-        // set labels
-        modal.find('.diassociate-provider-type').text(providerType);
-        modal.find('.diassociate-account-id').text(accountId);
-        // set hidden inputs
-        modal.find('input:hidden[name="providerType"]').val(providerType);
-        modal.find('input:hidden[name="accountId"]').val(accountId);
-      })
-    </script>
-  </div><!-- /.modal -->
-
-</div>
-{% endblock content_main %}
-
-{% block content_footer %}
-{% endblock %}
-
-{% block layout_footer %}
-{% endblock %}

+ 4 - 354
src/server/views/me/index.html

@@ -2,364 +2,14 @@
 
 {% block html_title %}{{ customizeService.generateCustomTitle(t('User Settings')) }}{% endblock %}
 
+{% block html_base_css %}user-settings-page{% endblock %}
+
 {% block content_header %}
-<header id="page-header">
-  <h1 id="mypage-title" class="title">{{ t('User Settings') }}</h1>
-</header>
+<h1 class="title">{{ t('User Settings') }}</h1>
 {% endblock %}
 
 {% block content_main %}
-<div class="content-main" id="personal-setting">
-
-  <ul class="nav nav-tabs mb-4" role="tablist">
-    <li class="nav-item">
-      <a class="nav-link active" href="/me" role="tab" data-toggle="tab"><i class="icon-user"></i> {{ t('User Information') }}</a>
-    </li>
-    <li class="nav-item">
-      <a class="nav-link" href="/me/external-accounts" role="tab" data-toggle="tab"><i class="icon-share-alt"></i> {{ t('External Accounts') }}</a>
-    </li>
-    <li class="nav-item">
-      <a class="nav-link" href="/me/password" role="tab" data-toggle="tab"><i class="icon-lock"></i> {{ t('Password Settings') }}</a>
-    </li>
-    <li class="nav-item">
-      <a class="nav-link" href="/me/apiToken" role="tab" data-toggle="tab"><i class="icon-paper-plane"></i> {{ t('API Settings') }}</a>
-    </li>
-  </ul>
-
-  <div class="tab-content">
-
-  {% set smessage = req.flash('successMessage') %}
-  {% if smessage.length %}
-  <div class="alert alert-success grw-mt-10px">
-    {{ smessage }}
-  </div>
-  {% endif %}
-
-  {% set wmessage = req.flash('warningMessage') %}
-  {% if wmessage.length %}
-  <div class="alert alert-danger grw-mt-10px">
-    {{ wmessage }}
-  </div>
-  {% endif %}
-
-  {% if req.form.errors.length > 0 %}
-  <div class="alert alert-danger grw-mt-10px">
-    <ul>
-    {% for error in req.form.errors %}
-      <li>{{ error }}</li>
-    {% endfor %}
-    </ul>
-  </div>
-  {% endif %}
-
-
-  <div class="form-box mt-3">
-    <form action="/me" method="post" role="form">
-      <fieldset>
-        <legend class="border-bottom mb-4">{{ t('Basic Info') }}</legend>
-        <div class="form-group row">
-          <label for="userForm[name]" class="col-sm-2 col-form-label">Name</label>
-          <div class="col-sm-4">
-            <input type="text" class="form-control" name="userForm[name]" value="{{ user.name }}" required>
-          </div>
-        </div>
-        <div class="form-group">
-          <div class="row">
-            <label for="userForm[email]" class="col-sm-2 col-form-label">Email</label>
-            <div class="col-sm-4">
-              <input type="email" class="form-control" name="userForm[email]" value="{{ user.email }}" aria-describedby="userForm[email]" required>
-            </div>
-          </div>
-          <div class="offset-sm-2 col-sm-10">
-            {% if getConfig('crowi', 'security:registrationWhiteList') && getConfig('crowi', 'security:registrationWhiteList').length %}
-            <p id="userForm[email]" class="form-text text-muted">
-              {{ t('page_register.form_help.email') }}
-            <ul>
-              {% for em in getConfig('crowi', 'security:registrationWhiteList') %}
-              <li><code>{{ em }}</code></li>
-              {% endfor %}
-            </ul>
-            </p>
-            {% endif %}
-          </div>
-        </div>
-        <div class="form-group row">
-          <label for="userForm[isEmailPublished]" class="col-sm-2 col-form-label">{{ t('Disclose E-mail') }}</label>
-          <div class="col-sm-4">
-            <div class="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioEmailShow"
-                name="userForm[isEmailPublished]"
-                value="{{ true }}"
-                {% if user.isEmailPublished == true %}checked="checked"{% endif %}
-                class="custom-control-input"
-              > 
-              <label class="custom-control-label" for="radioEmailShow">{{ t('Show') }}</label>
-            </div>
-            <div class="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioEmailHide"
-                name="userForm[isEmailPublished]"
-                value="{{ false }}"
-                {% if user.isEmailPublished == false %}checked="checked"{% endif %}
-                class="custom-control-input"
-              >
-              <label class="custom-control-label" for="radioEmailHide">{{ t('Hide') }}</label>
-            </div>
-          </div>
-        </div>
-        <div class="form-group row {% if not user.lang %}has-error{% endif %}">
-          <label for="userForm[lang]" class="col-sm-2 col-form-label">Language</label>
-          <div class="col-sm-4">
-            <div class="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioLangEn"
-                name="userForm[lang]"
-                value="{{ consts.language.LANG_EN_US }}"
-                class="custom-control-input"
-                {% if user.lang == consts.language.LANG_EN_US %}checked="checked"{% endif %}
-              >
-              <label class="custom-control-label" for="radioLangEn">{{ t('English') }}</label>
-            </div>
-            <div class="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioLangJa"
-                name="userForm[lang]"
-                value="{{ consts.language.LANG_JA }}"
-                class="custom-control-input"
-                {% if user.lang == consts.language.LANG_JA %}checked="checked"{% endif %}
-              >
-              <label class="custom-control-label" for="radioLangJa">{{ t('Japanese') }}</label>
-            </div>
-          </div>
-        </div>
-
-        <div class="form-group row">
-          <div class="offset-sm-2 col-sm-10">
-            <input type="hidden" name="_csrf" value="{{ csrf() }}">
-            <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
-          </div>
-        </div>
-      </fieldset>
-    </form>
-  </div>
-
-  <div class="form-box mt-3">
-
-    <!-- separeted form tag -->
-    <form action="/me/imagetype" id="formImageType" method="post" class="form" role="form"></form>
-
-    <fieldset>
-      <legend class="border-bottom mb-5">{{ t('Set Profile Image') }}</legend>
-      <div class="form-group row">
-        <div class="col-sm-4 offset-sm-1">
-          <h4>
-            <div class="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioGravatar"
-                form="formImageType"
-                name="imagetypeForm[isGravatarEnabled]"
-                value="true"
-                {% if user.isGravatarEnabled %}checked="checked"{% endif %}
-                class="custom-control-input"
-              >
-              <label class="custom-control-label custom-control-inline" for="radioGravatar">
-                <img src="https://gravatar.com/avatar/00000000000000000000000000000000?s=24" />
-                <span class="pl-1">Gravatar</span>
-              </label>
-              <a href="https://gravatar.com/">
-                <small>
-                  <i class="icon-arrow-right-circle" aria-hidden="true"></i>
-                </small>
-              </a>
-            </div>
-          </h4>
-  
-          <img src="{{ user|gravatar }}" width="64">
-        </div>
-        <div class="col-sm-7">
-          <h4>
-            <div class="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioUploadPicture"
-                form="formImageType"
-                name="imagetypeForm[isGravatarEnabled]"
-                value="false"
-                {% if !user.isGravatarEnabled  %}checked="checked"{% endif %}
-                class="custom-control-input"
-              >
-              <label for="radioUploadPicture" class="custom-control-label">{{ t('Upload Image') }}</label>
-            </div>
-          </h4>
-          <div class="form-group">
-            <div id="pictureUploadFormMessage" class=""></div>
-            <div class="row">
-              <label for="" class="col-sm-4">{{ t('Current Image') }}</label>
-              <div class="col-sm-8">
-                <p><img src="{{ user|uploadedpicture }}" class="picture picture-lg rounded-circle" id="settingUserPicture"></p>
-                <form
-                  id="remove-attachment"
-                  action="/_api/attachments.removeProfileImage"
-                  method="post"
-                  class="form-horizontal"
-                  style="{% if not user.imageAttachment %}display: none{% endif %}"
-                >
-                  <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  <button type="submit" class="btn btn-danger">{{ t('Delete Image') }}</button>
-                </form>
-              </div>
-            </div>
-          </div>
-        </h4>
-
-        <img src="{{ user|gravatar }}" width="64">
-      </div><!-- /.col-sm* -->
-
-      <div class="form-group col-md-4 col-sm-7">
-        <h4>
-          <div class="radio radio-primary">
-            <input type="radio" id="radioUploadPicture" form="formImageType" name="imagetypeForm[isGravatarEnabled]" value="false" {% if !user.isGravatarEnabled  %}checked="checked"{% endif %}>
-            <label for="radioUploadPicture">
-              {{ t('Upload Image') }}
-            </label>
-          </div>
-        </h4>
-        <div class="form-group">
-          <div id="pictureUploadFormMessage"></div>
-          <label for="" class="col-sm-4 control-label">
-            {{ t('Current Image') }}
-          </label>
-          <div class="col-sm-8">
-            <p>
-            <img src="{{ user|uploadedpicture }}" class="picture picture-lg img-circle" id="settingUserPicture"><br>
-            </p>
-            <p>
-            <form id="remove-attachment" action="/_api/attachments.removeProfileImage" method="post" class="form-horizontal"
-                style="{% if not user.imageAttachment %}display: none{% endif %}">
-              <input type="hidden" name="_csrf" value="{{ csrf() }}">
-              <button type="submit" class="btn btn-danger">{{ t('Delete Image') }}</button>
-            </form>
-            </p>
-          </div>
-        </div><!-- /.form-group -->
-
-        <div class="form-group">
-          <div id="profile-image-uploader"></div>
-        </div>
-      </div>
-
-      <div class="form-group">
-        <div class="offset-sm-4 col-sm-6">
-          <button type="submit" form="formImageType" class="btn btn-primary">{{ t('Update') }}</button>
-        </div>
-      </div>
-    </fieldset>
-  </div><!-- /.form-box -->
-
-  <script>
-    $("#pictureUploadForm input[name=profileImage]").on('change', function(){
-      if ($(this).val() == '') {
-        return false;
-      }
-
-      var $form = $('#pictureUploadForm');
-      var formData = new FormData();
-      formData.append('file', this.files[0]);
-      formData.append('_csrf', document.getElementsByName("_csrf")[0].value);
-
-      $('#pictureUploadFormProgress').html('<div class="speeding-wheel-sm mr-2"></div> アップロード中...');
-      $.ajax($form.attr("action"), {
-        type: 'post',
-        processData: false,
-        contentType: false,
-        data: formData
-      })
-      .then(function(data) {
-        if (data.ok) {
-          var attachment = data.attachment;
-          $('#settingUserPicture').attr('src', attachment.filePathProxied + '?time=' + (new Date()));
-          $('form#remove-attachment').show();
-          $('form#remove-attachment input[name=attachment_id]').val(attachment.id);
-          $('#pictureUploadFormMessage')
-            .addClass('alert alert-success')
-            .html('変更しました');
-        }
-        else {
-          throw new Error('statis is invalid');
-        }
-      })
-      .catch(function(err) {
-        $('#pictureUploadFormMessage')
-          .addClass('alert alert-danger')
-          .html('変更中にエラーが発生しました。');
-      })
-      // finally
-      .then(function() {
-        $('#pictureUploadFormProgress').html('');
-      });
-      return false;
-    });
-
-    $('form#remove-attachment').on('submit', function(event) {
-      // process with jQuery
-      event.preventDefault();
-
-      $.post($(this).attr('action'), $(this).serializeArray())
-      .then(function(data) {
-        if (data.ok) {
-          $('#settingUserPicture').attr('src', '/images/icons/user.svg');
-          $('form#remove-attachment').hide();
-        }
-        else {
-          throw new Error('statis is invalid');
-        }
-      })
-      .catch(function(err) {
-        $('#pictureUploadFormMessage')
-          .addClass('alert alert-danger')
-          .html('変更中にエラーが発生しました。');
-      })
-    });
-  </script>
-
-  </div> {# end of .tab-contents #}
-
-  {#
-  <div class="form-box">
-    <form action="/me/username" method="post" class="form-horizontal" role="form">
-      <fieldset>
-        <legend>ユーザーID (ユーザー名) の変更</legend>
-      <div class="form-group">
-        <label for="userNameForm[username]" class="col-sm-2 control-label">ユーザーID</label>
-        <div class="col-sm-4">
-          <input class="form-control" type="text" name="userNameForm[username]" value="{{ user.username }}" required>
-          <p class="help-block">すべてのマイページの</p>
-        </div>
-      </div>
-
-      <div class="form-group">
-        <div class="col-sm-offset-2 col-sm-10">
-          <p class="alert alert-warning">
-          ユーザーIDを変更すると、<code>/user/{{ user.username }}</code> 以下のページがすべて <code>/user/新しいユーザーID</code> の下に移動されます。<br>
-          また、これまでのページにリダイレクトは設定されず、この操作の取り消しもできません。<br>
-          実行には十分に注意をしてください。
-          </p>
-          <button type="submit" class="btn btn-warning">ユーザーIDの変更を実行する</button>
-        </div>
-      </div>
-    </fieldset>
-    </form>
-  </div>
-  #}
-
-  </div>
-</div>
+<div class="content-main" id="personal-setting"></div>
 {% endblock content_main %}
 
 {% block content_footer %}

+ 11 - 11
src/server/views/modal/shortcuts.html

@@ -1,16 +1,16 @@
 <div class="modal" id="shortcuts-modal" tabindex="-1">
-  <div class="modal-dialog">
+  <div class="modal-dialog modal-lg">
     <div class="modal-content">
 
       <div class="modal-header">
-        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
         <div class="modal-title">{{ t('Shortcuts') }}</div>
+        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
       </div>
 
       <div class="modal-body">
 
         <div class="row">
-          <div class="col-sm-6">
+          <div class="col-md-6">
             <h3><strong>{{ t('modal_shortcuts.global.title') }}</strong></h3>
 
             <table class="table">
@@ -38,9 +38,9 @@
                 </td>
               </tr>
             </table>
-          </div><!-- /.col-sm-6 -->
+          </div><!-- /.col-md-6 -->
 
-          <div class="col-sm-6">
+          <div class="col-md-6">
             <h3><strong>{{ t('modal_shortcuts.editor.title') }}</strong></h3>
 
             <table class="table">
@@ -50,7 +50,7 @@
               </tr>
               <tr>
                 <th>{{ t('modal_shortcuts.editor.Outdent') }}:</th>
-                <td><span class="key key-long">Shift</span> + <span class="key key-longer">Tab</span></td>
+                <td class="text-nowrap"><span class="key key-long">Shift</span> + <span class="key key-longer">Tab</span></td>
               </tr>
               <tr>
                 <th>{{ t('modal_shortcuts.editor.Save Page') }}:</th>
@@ -61,16 +61,16 @@
                 <td><span class="key cmd-key"></span> + <span class="key">D</span></td>
               </tr>
             </table>
-          </div><!-- /.col-sm-6 -->
+          </div><!-- /.col-md-6 -->
 
         </div><!-- /.row -->
 
         <div class="row">
-          <div class="col-sm-6">
+          <div class="col-md-6">
             <h3><strong></strong></h3>
-          </div><!-- /.col-sm-6 -->
+          </div><!-- /.col-md-6 -->
 
-          <div class="col-sm-6">
+          <div class="col-md-6">
             <h3><strong>{{ t('modal_shortcuts.commentform.title') }}</strong></h3>
 
             <table class="table">
@@ -83,7 +83,7 @@
                 <td><span class="key cmd-key"></span> + <span class="key">D</span></td>
               </tr>
             </table>
-          </div><!-- /.col-sm-6 -->
+          </div><!-- /.col-md-6 -->
 
         </div><!-- /.row -->
 

+ 8 - 9
src/server/views/tags.html

@@ -1,16 +1,15 @@
 {% extends 'layout/layout.html' %}
 
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Tags')) }}{% endblock %}
+
+{% block html_base_css %}tags-page{% endblock %}
+
 {% block layout_main %}
+<header class="sticky-top py-0 grw-header">
+  <h1 class="title">{{ t('Tags') }}</h1>
+</header>
+
 <div class="container-fluid">
-  <div class="row grw-subnav">
-    <div class="col-xs-12 grw-title-bar">
-      {% block content_header %}
-      <header id="page-header">
-        <h1 id="admin-title" class="title">{{ t('Tags') }}</h1>
-      </header>
-      {% endblock %}
-    </div>
-  </div>
   <div class="row">
     <div id="main" class="main mt-3 col-md-12 tags-page">
       <div class="" id="tags-page"></div>

+ 27 - 25
src/server/views/widget/page_tabs.html

@@ -11,31 +11,33 @@
   </li>
 
   {% if !isTrashPage() %}
-  <li class="nav-item grw-main-nav-item-left grw-nav-item-edit">
-    <a
-      {% if user %} href="#edit" data-toggle="tab" class="nav-link edit-button" {% endif %}
-      {% if not user %}
-        class="edit-button edit-button-disabled"
-        data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
-      {% endif %}
-    >
-      <i class="icon-note"></i> {{ t('Edit') }}
-    </a>
-  </li>
-  {% if isHackmdSetup() %}
-  <li class="nav-item grw-main-nav-item-left grw-nav-tab-hackmd">
-    <a
-      {% if user %} href="#hackmd" data-toggle="tab" class="nav-link edit-button" {% endif %}
-      {% if not user %}
-        class="edit-button edit-button-disabled"
-        data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
-      {% endif %}
-    >
-      <i class="fa fa-file-text-o"></i> {{ t('HackMD') }}
-    </a>
-  </li>
-  {% endif %}
-    <div class="grw-compact-subnav d-none ml-2">
+    <li class="nav-item grw-main-nav-item-left grw-nav-item-edit">
+      <a
+        {% if user %} href="#edit" data-toggle="tab" class="nav-link edit-button" {% endif %}
+        {% if not user %}
+          class="edit-button edit-button-disabled"
+          data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
+        {% endif %}
+      >
+        <i class="icon-note"></i> {{ t('Edit') }}
+      </a>
+    </li>
+
+    {% if isHackmdSetup() %}
+    <li class="nav-item grw-main-nav-item-left grw-nav-tab-hackmd">
+      <a
+        {% if user %} href="#hackmd" data-toggle="tab" class="nav-link edit-button" {% endif %}
+        {% if not user %}
+          class="edit-button edit-button-disabled"
+          data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
+        {% endif %}
+      >
+        <i class="fa fa-file-text-o"></i> {{ t('HackMD') }}
+      </a>
+    </li>
+    {% endif %}
+
+    <div class="grw-revision-path-for-edit d-none ml-2">
       <h4 id="revision-path" class="mb-0"></h4>
       <div id="tag-label"></div>
     </div>