Преглед изворни кода

Merge remote-tracking branch 'origin/support/apply-bootstrap4' into support/antarctic-theme

itizawa пре 6 година
родитељ
комит
0bb11f5d56
69 измењених фајлова са 579 додато и 342 уклоњено
  1. 0 5
      src/client/js/app.jsx
  2. 3 3
      src/client/js/components/Admin/Users/UserInviteModal.jsx
  3. 1 1
      src/client/js/components/BookmarkButton.jsx
  4. 1 1
      src/client/js/components/HeaderSearchBox.jsx
  5. 20 11
      src/client/js/components/InstallerForm.jsx
  6. 22 26
      src/client/js/components/LikeButton.jsx
  7. 4 4
      src/client/js/components/Me/ApiSettings.jsx
  8. 40 14
      src/client/js/components/Me/AssociateModal.jsx
  9. 19 15
      src/client/js/components/Me/BasicInfoSettings.jsx
  10. 2 2
      src/client/js/components/Me/ExternalAccountLinkedMe.jsx
  11. 16 18
      src/client/js/components/Me/PasswordSettings.jsx
  12. 8 8
      src/client/js/components/Me/PersonalSettings.jsx
  13. 9 7
      src/client/js/components/Me/ProfileImageSettings.jsx
  14. 17 10
      src/client/js/components/Navbar/GrowiSubNavigation.jsx
  15. 12 6
      src/client/js/components/Navbar/PageCreator.jsx
  16. 3 1
      src/client/js/components/Navbar/PersonalDropdown.jsx
  17. 11 6
      src/client/js/components/Navbar/RevisionAuthor.jsx
  18. 1 1
      src/client/js/components/Page/RevisionPath.jsx
  19. 1 1
      src/client/js/components/Page/TagEditor.jsx
  20. 2 2
      src/client/js/components/PageAttachment/Attachment.jsx
  21. 2 2
      src/client/js/components/PageComment/CommentEditor.jsx
  22. 3 3
      src/client/js/components/PageComment/DeleteCommentModal.jsx
  23. 3 3
      src/client/js/components/PageEditor/OptionsSelector.jsx
  24. 1 1
      src/client/js/components/SavePageControls/GrantSelector.jsx
  25. 16 6
      src/client/js/components/SearchPage/DeletePageListModal.jsx
  26. 3 2
      src/client/js/components/SearchPage/SearchResult.jsx
  27. 1 1
      src/client/js/components/SlackNotification.jsx
  28. 9 1
      src/client/js/components/StaffCredit/Contributor.js
  29. 1 0
      src/client/js/services/AdminGeneralSecurityContainer.js
  30. 9 5
      src/client/js/services/PageContainer.js
  31. 54 0
      src/client/styles/scss/_layout_kibela.scss
  32. 18 62
      src/client/styles/scss/_login.scss
  33. 2 2
      src/client/styles/scss/_override-bootstrap-variables.scss
  34. 5 0
      src/client/styles/scss/_override-bootstrap.scss
  35. 15 0
      src/client/styles/scss/_page_growi.scss
  36. 58 13
      src/client/styles/scss/atoms/_buttons.scss
  37. 8 0
      src/client/styles/scss/theme/_apply-colors-dark.scss
  38. 8 0
      src/client/styles/scss/theme/_apply-colors-light.scss
  39. 6 2
      src/client/styles/scss/theme/_apply-colors.scss
  40. 39 0
      src/client/styles/scss/theme/_layout_kibela_variable.scss
  41. 9 5
      src/client/styles/scss/theme/_reboot-bootstrap-colors.scss
  42. 12 0
      src/client/styles/scss/theme/default.scss
  43. 1 0
      src/client/styles/scss/theme/kibela.scss
  44. 30 0
      src/migrations/2020040216038-remove-deleteduser-from-relationgroup.js
  45. 6 4
      src/server/models/bookmark.js
  46. 6 1
      src/server/models/user-group-relation.js
  47. 2 2
      src/server/routes/apiv3/notification-setting.js
  48. 1 0
      src/server/routes/apiv3/user-group.js
  49. 1 0
      src/server/routes/apiv3/users.js
  50. 20 12
      src/server/views/invited.html
  51. 0 1
      src/server/views/layout-crowi/page.html
  52. 0 1
      src/server/views/layout-crowi/page_list.html
  53. 1 1
      src/server/views/layout-kibela/page_list.html
  54. 1 1
      src/server/views/layout-kibela/widget/header.html
  55. 9 9
      src/server/views/login.html
  56. 3 3
      src/server/views/modal/create_page.html
  57. 7 6
      src/server/views/modal/put_back.html
  58. 3 1
      src/server/views/modal/rename.html
  59. 6 6
      src/server/views/widget/forbidden_content.html
  60. 0 6
      src/server/views/widget/header-button-bookmark.html
  61. 0 6
      src/server/views/widget/header-button-like.html
  62. 0 13
      src/server/views/widget/header-buttons-lg.html
  63. 0 12
      src/server/views/widget/header-buttons.html
  64. 1 1
      src/server/views/widget/modal/page-api-error-messages.html
  65. 1 1
      src/server/views/widget/not_creatable_content.html
  66. 1 1
      src/server/views/widget/not_found_content.html
  67. 3 3
      src/server/views/widget/not_found_tabs.html
  68. 1 1
      src/server/views/widget/page_attachments.html
  69. 1 0
      src/server/views/widget/page_content.html

+ 0 - 5
src/client/js/app.jsx

@@ -23,8 +23,6 @@ import PageAttachment from './components/PageAttachment';
 import PageStatusAlert from './components/PageStatusAlert';
 import RevisionPath from './components/Page/RevisionPath';
 import TagLabels from './components/Page/TagLabels';
-import BookmarkButton from './components/BookmarkButton';
-import LikeButton from './components/LikeButton';
 import PagePathAutoComplete from './components/PagePathAutoComplete';
 import RecentCreated from './components/RecentCreated/RecentCreated';
 import MyDraftList from './components/MyDraftList/MyDraftList';
@@ -90,11 +88,8 @@ if (pageContainer.state.pageId != null) {
     'page-timeline': <PageTimeline />,
     'page-comment-write': <CommentEditorLazyRenderer />,
     'revision-toc': <TableOfContents />,
-    'like-button': <LikeButton pageId={pageContainer.state.pageId} isLiked={pageContainer.state.isLiked} />,
     'seen-user-list': <UserPictureList userIds={pageContainer.state.seenUserIds} />,
     'liker-list': <UserPictureList userIds={pageContainer.state.likerUserIds} />,
-    'bookmark-button': <BookmarkButton pageId={pageContainer.state.pageId} crowi={appContainer} />,
-    'bookmark-button-lg': <BookmarkButton pageId={pageContainer.state.pageId} crowi={appContainer} size="lg" />,
     'rename-page-name-input': <PagePathAutoComplete crowi={appContainer} initializedPath={pageContainer.state.path} />,
     'duplicate-page-name-input': <PagePathAutoComplete crowi={appContainer} initializedPath={pageContainer.state.path} />,
   });

+ 3 - 3
src/client/js/components/Admin/Users/UserInviteModal.jsx

@@ -87,7 +87,7 @@ class UserInviteModal extends React.Component {
         <div>
           <button
             type="button"
-            className="fcbtn btn btn-outline-light rounded-pill mr-2"
+            className="btn btn-outline-danger rounded-pill mr-2"
             onClick={this.onToggleModal}
           >
             Cancel
@@ -95,7 +95,7 @@ class UserInviteModal extends React.Component {
 
           <button
             type="button"
-            className="fcbtn btn btn-outline-primary rounded-pill btn-1b"
+            className="btn btn-outline-primary rounded-pill"
             onClick={this.handleSubmit}
             disabled={!this.validEmail()}
           >
@@ -116,7 +116,7 @@ class UserInviteModal extends React.Component {
         </label>
         <button
           type="button"
-          className="fcbtn btn btn-primary"
+          className="btn btn-outline-primary"
           onClick={this.onToggleModal}
         >
           Close

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

@@ -78,7 +78,7 @@ export default class BookmarkButton extends React.Component {
         href="#"
         title="Bookmark"
         onClick={this.handleClick}
-        className={`btn btn-circle btn-outline-warning border-0 ${addedClassName}`}
+        className={`btn btn-circle btn-outline-warning btn-bookmark border-0 ${addedClassName}`}
       >
         <i className="icon-star"></i>
       </button>

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

@@ -85,7 +85,7 @@ class HeaderSearchBox extends React.Component {
             placeholder="Search ..."
           />
           <div className="btn-group-submit-search">
-            <span className="btn-link" onClick={this.search}>
+            <span className="btn-link text-decoration-none" onClick={this.search}>
               <i className="icon-magnifier"></i>
             </span>
           </div>

+ 20 - 11
src/client/js/components/InstallerForm.jsx

@@ -90,8 +90,10 @@ class InstallerForm extends React.Component {
               </div>
             </div>
 
-            <div className={`input-group${hasErrorClass}`}>
-              <span className="input-group-addon"><i className="icon-user" /></span>
+            <div className={`input-group mb-3${hasErrorClass}`}>
+              <div className="input-group-prepend">
+                <span className="input-group-text"><i className="icon-user" /></span>
+              </div>
               <input
                 type="text"
                 className="form-control"
@@ -104,8 +106,10 @@ class InstallerForm extends React.Component {
             </div>
             <p className="form-text">{ unavailableUserId }</p>
 
-            <div className="input-group">
-              <span className="input-group-addon"><i className="icon-tag" /></span>
+            <div className="input-group mb-3">
+              <div className="input-group-prepend">
+                <span className="input-group-text"><i className="icon-tag" /></span>
+              </div>
               <input
                 type="text"
                 className="form-control"
@@ -116,8 +120,10 @@ class InstallerForm extends React.Component {
               />
             </div>
 
-            <div className="input-group">
-              <span className="input-group-addon"><i className="icon-envelope" /></span>
+            <div className="input-group mb-3">
+              <div className="input-group-prepend">
+                <span className="input-group-text"><i className="icon-envelope" /></span>
+              </div>
               <input
                 type="email"
                 className="form-control"
@@ -128,8 +134,10 @@ class InstallerForm extends React.Component {
               />
             </div>
 
-            <div className="input-group">
-              <span className="input-group-addon"><i className="icon-lock" /></span>
+            <div className="input-group mb-3">
+              <div className="input-group-prepend">
+                <span className="input-group-text"><i className="icon-lock" /></span>
+              </div>
               <input
                 type="password"
                 className="form-control"
@@ -142,9 +150,10 @@ class InstallerForm extends React.Component {
             <input type="hidden" name="_csrf" value={this.props.csrf} />
 
             <div className="input-group mt-4 mb-3 d-flex justify-content-center">
-              <button type="submit" className="fcbtn btn btn-success btn-1b btn-register">
-                <span className="btn-label"><i className="icon-user-follow" /></span>
-                <span className="btn-label-text">{ this.props.t('Create') }</span>
+              <button type="submit" className="btn-fill btn btn-register px-0 py-2" id="register">
+                <div className="eff"></div>
+                <span className="btn-label p-3"><i className="icon-user-follow" /></span>
+                <span className="btn-label-text p-3">{ this.props.t('Create') }</span>
               </button>
             </div>
 

+ 22 - 26
src/client/js/components/LikeButton.jsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
+import { toastError } from '../util/apiNotification';
 import { createSubscribedElement } from './UnstatedUtils';
 import AppContainer from '../services/AppContainer';
 
@@ -10,29 +11,33 @@ class LikeButton extends React.Component {
     super(props);
 
     this.state = {
-      isLiked: !!props.isLiked,
+      isLiked: props.isLiked,
     };
 
     this.handleClick = this.handleClick.bind(this);
   }
 
-  handleClick(event) {
-    event.preventDefault();
-
-    const { appContainer } = this.props;
-    const pageId = this.props.pageId;
-
-    if (!this.state.isLiked) {
-      appContainer.apiPost('/likes.add', { page_id: pageId })
-        .then((res) => {
-          this.setState({ isLiked: true });
-        });
+  async handleClick() {
+    const { appContainer, pageId } = this.props;
+    const { isLiked } = this.state;
+
+    if (!isLiked) {
+      try {
+        await appContainer.apiPost('/likes.add', { page_id: pageId });
+        this.setState({ isLiked: true });
+      }
+      catch (err) {
+        toastError(err);
+      }
     }
     else {
-      appContainer.apiPost('/likes.remove', { page_id: pageId })
-        .then((res) => {
-          this.setState({ isLiked: false });
-        });
+      try {
+        await appContainer.apiPost('/likes.remove', { page_id: pageId });
+        this.setState({ isLiked: false });
+      }
+      catch (err) {
+        toastError(err);
+      }
     }
   }
 
@@ -46,20 +51,11 @@ class LikeButton extends React.Component {
       return <div></div>;
     }
 
-    const btnSizeClassName = this.props.size ? `btn-${this.props.size}` : 'btn-md';
-    const addedClassNames = [
-      this.state.isLiked ? 'active' : '',
-      btnSizeClassName,
-    ];
-    const addedClassName = addedClassNames.join(' ');
-
     return (
       <button
         type="button"
-        href="#"
-        title="Like"
         onClick={this.handleClick}
-        className={`btn btn-circle btn-outline-info border-0 ${addedClassName}`}
+        className={`btn btn-circle btn-outline-info btn-like border-0 ${this.state.isLiked ? 'active' : ''}`}
       >
         <i className="icon-like"></i>
       </button>

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

@@ -43,8 +43,8 @@ class ApiSettings extends React.Component {
         </div>
 
         <div className="row mb-3">
-          <label htmlFor="apiToken" className="col-xs-3 text-right">{t('Current API Token')}</label>
-          <div className="col-xs-6">
+          <label htmlFor="apiToken" className="col-3 text-right">{t('Current API Token')}</label>
+          <div className="col-6">
             {personalContainer.state.apiToken != null
             ? (
               <input
@@ -65,7 +65,7 @@ class ApiSettings extends React.Component {
 
 
         <div className="row">
-          <div className="col-xs-offset-3 col-xs-6">
+          <div className="offset-3 col-6">
 
             <p className="alert alert-warning">
               { t('page_me_apitoken.notice.update_token1') }<br />
@@ -76,7 +76,7 @@ class ApiSettings extends React.Component {
         </div>
 
         <div className="row my-3">
-          <div className="col-xs-offset-4 col-xs-5">
+          <div className="offset-4 col-5">
             <button
               type="button"
               className="btn btn-primary"

+ 40 - 14
src/client/js/components/Me/AssociateModal.jsx

@@ -70,28 +70,54 @@ class AssociateModal extends React.Component {
     const { t } = this.props;
 
     return (
-      <Modal isOpen={this.props.isOpen} toggle={this.props.onClose}>
+      <Modal isOpen={this.props.isOpen} toggle={this.props.onClose} className="mw-100 m-4">
         <ModalHeader className="bg-info" toggle={this.props.onClose}>
           { t('Create External Account') }
         </ModalHeader>
         <ModalBody>
           <ul className="nav nav-tabs passport-settings mb-2" role="tablist">
-            <li className="active">
-              <a href="#passport-ldap" data-toggle="tab" role="tab"><i className="fa fa-sitemap"></i> LDAP</a>
+            <li className="nav-item active">
+              <a href="#passport-ldap" className="nav-link active" data-toggle="tab" role="tab">
+                <i className="fa fa-sitemap"></i> LDAP
+              </a>
+            </li>
+            <li className="nav-item">
+              <a href="#github-tbd" className="nav-link" data-toggle="tab" role="tab">
+                <i className="fa fa-github"></i> (TBD) GitHub
+              </a>
+            </li>
+            <li className="nav-item">
+              <a href="#google-tbd" className="nav-link" data-toggle="tab" role="tab">
+                <i className="fa fa-google"></i> (TBD) Google OAuth
+              </a>
+            </li>
+            <li className="nav-item">
+              <a href="#facebook-tbd" className="nav-link" data-toggle="tab" role="tab">
+                <i className="fa fa-facebook"></i> (TBD) Facebook
+              </a>
+            </li>
+            <li className="nav-item">
+              <a href="#twitter-tbd" className="nav-link" data-toggle="tab" role="tab">
+                <i className="fa fa-twitter"></i> (TBD) Twitter
+              </a>
             </li>
-            <li className="tbd disabled"><a><i className="fa fa-github"></i> (TBD) GitHub</a></li>
-            <li className="tbd disabled"><a><i className="fa fa-google"></i> (TBD) Google OAuth</a></li>
-            <li className="tbd disabled"><a><i className="fa fa-facebook"></i> (TBD) Facebook</a></li>
-            <li className="tbd disabled"><a><i className="fa fa-twitter"></i> (TBD) Twitter</a></li>
           </ul>
-          <LdapAuthTest
-            username={this.state.username}
-            password={this.state.password}
-            onChangeUsername={this.onChangeUsername}
-            onChangePassword={this.onChangePassword}
-          />
+          <div className="tab-content">
+            <div id="passport-ldap" className="tab-pane active">
+              <LdapAuthTest
+                username={this.state.username}
+                password={this.state.password}
+                onChangeUsername={this.onChangeUsername}
+                onChangePassword={this.onChangePassword}
+              />
+            </div>
+            <div id="github-tbd" className="tab-pane" role="tabpanel">TBD</div>
+            <div id="google-tbd" className="tab-pane" role="tabpanel">TBD</div>
+            <div id="facebook-tbd" className="tab-pane" role="tabpanel">TBD</div>
+            <div id="twitter-tbd" className="tab-pane" role="tabpanel">TBD</div>
+          </div>
         </ModalBody>
-        <ModalFooter>
+        <ModalFooter className="border-top-0">
           <button type="button" className="btn btn-info mt-3" onClick={this.onClickAddBtn}>
             <i className="fa fa-plus-circle" aria-hidden="true"></i>
             {t('add')}

+ 19 - 15
src/client/js/components/Me/BasicInfoSettings.jsx

@@ -45,7 +45,7 @@ class BasicInfoSettings extends React.Component {
     return (
       <Fragment>
 
-        <div className="row mb-3">
+        <div className="row form-group mb-3">
           <label htmlFor="userForm[name]" className="col-sm-2 text-right">{t('Name')}</label>
           <div className="col-sm-4 text-left">
             <input
@@ -58,7 +58,7 @@ class BasicInfoSettings extends React.Component {
           </div>
         </div>
 
-        <div className="row mb-3">
+        <div className="row form-group mb-3">
           <label htmlFor="userForm[email]" className="col-sm-2 text-right">{t('Email')}</label>
           <div className="col-sm-4 text-left">
             <input
@@ -82,59 +82,63 @@ class BasicInfoSettings extends React.Component {
         </div>
 
         <div className="row mb-3">
-          <label className="col-xs-2 text-right">{t('Disclose E-mail')}</label>
-          <div className="col-xs-6">
-            <div className="radio radio-primary radio-inline">
+          <label className="col-sm-2 text-right">{t('Disclose E-mail')}</label>
+          <div className="col-6">
+            <div className="custom-control custom-radio custom-control-inline">
               <input
                 type="radio"
                 id="radioEmailShow"
+                className="custom-control-input"
                 name="userForm[isEmailPublished]"
                 checked={personalContainer.state.isEmailPublished}
                 onChange={() => { personalContainer.changeIsEmailPublished(true) }}
               />
-              <label htmlFor="radioEmailShow">{t('Show')}</label>
+              <label className="custom-control-label" htmlFor="radioEmailShow">{t('Show')}</label>
             </div>
-            <div className="radio radio-primary radio-inline">
+            <div className="custom-control custom-radio custom-control-inline">
               <input
                 type="radio"
                 id="radioEmailHide"
+                className="custom-control-input"
                 name="userForm[isEmailPublished]"
                 checked={!personalContainer.state.isEmailPublished}
                 onChange={() => { personalContainer.changeIsEmailPublished(false) }}
               />
-              <label htmlFor="radioEmailHide">{t('Hide')}</label>
+              <label className="custom-control-label" htmlFor="radioEmailHide">{t('Hide')}</label>
             </div>
           </div>
         </div>
 
         <div className="row mb-3">
-          <label className="col-xs-2 text-right">{t('Language')}</label>
-          <div className="col-xs-6">
-            <div className="radio radio-primary radio-inline">
+          <label className="col-sm-2 col-form-label text-right">{t('Language')}</label>
+          <div className="col-6">
+            <div className="custom-control custom-radio custom-control-inline">
               <input
                 type="radio"
                 id="radioLangEn"
+                className="custom-control-input"
                 name="userForm[lang]"
                 checked={personalContainer.state.lang === 'en-US'}
                 onChange={() => { personalContainer.changeLang('en-US') }}
               />
-              <label htmlFor="radioLangEn">{t('English')}</label>
+              <label className="custom-control-label" htmlFor="radioLangEn">{t('English')}</label>
             </div>
-            <div className="radio radio-primary radio-inline">
+            <div className="custom-control custom-radio custom-control-inline">
               <input
                 type="radio"
                 id="radioLangJa"
+                className="custom-control-input"
                 name="userForm[lang]"
                 checked={personalContainer.state.lang === 'ja'}
                 onChange={() => { personalContainer.changeLang('ja') }}
               />
-              <label htmlFor="radioLangJa">{t('Japanese')}</label>
+              <label className="custom-control-label" htmlFor="radioLangJa">{t('Japanese')}</label>
             </div>
           </div>
         </div>
 
         <div className="row my-3">
-          <div className="col-xs-offset-4 col-xs-5">
+          <div className="offset-4 col-5">
             <button type="button" className="btn btn-primary" onClick={this.onClickSubmit} disabled={personalContainer.state.retrieveError != null}>
               {t('Update')}
             </button>

+ 2 - 2
src/client/js/components/Me/ExternalAccountLinkedMe.jsx

@@ -67,9 +67,9 @@ class ExternalAccountLinkedMe extends React.Component {
 
     return (
       <Fragment>
-        <div className="container-fluid">
+        <div className="container-fluid p-0 my-4">
           <h2 className="border-bottom">
-            <button type="button" className="btn btn-default btn-sm pull-right" onClick={this.openAssociateModal}>
+            <button type="button" className="btn btn-light btn-sm pull-right" onClick={this.openAssociateModal}>
               <i className="icon-plus" aria-hidden="true" />
             Add
             </button>

+ 16 - 18
src/client/js/components/Me/PasswordSettings.jsx

@@ -76,8 +76,8 @@ class PasswordSettings extends React.Component {
         {(personalContainer.state.isPasswordSet)
         && (
           <div className="row mb-3">
-            <label htmlFor="oldPassword" className="col-xs-3 text-right">{ t('personal_settings.current_password') }</label>
-            <div className="col-xs-6">
+            <label htmlFor="oldPassword" className="col-3 text-right">{ t('personal_settings.current_password') }</label>
+            <div className="col-6">
               <input
                 className="form-control"
                 type="password"
@@ -89,8 +89,8 @@ class PasswordSettings extends React.Component {
           </div>
         )}
         <div className="row mb-3">
-          <label htmlFor="newPassword" className="col-xs-3 text-right">{t('personal_settings.new_password') }</label>
-          <div className="col-xs-6">
+          <label htmlFor="newPassword" className="col-3 text-right">{t('personal_settings.new_password') }</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="password"
@@ -101,10 +101,10 @@ class PasswordSettings extends React.Component {
           </div>
         </div>
         <div className={`row mb-3 ${isIncorrectConfirmPassword && 'has-error'}`}>
-          <label htmlFor="newPasswordConfirm" className="col-xs-3 text-right">{t('personal_settings.new_password_confirm') }</label>
-          <div className="col-xs-6">
+          <label htmlFor="newPasswordConfirm" className="col-3 text-right">{t('personal_settings.new_password_confirm') }</label>
+          <div className="col-6">
             <input
-              className="form-control col-xs-4"
+              className="form-control"
               type="password"
               name="newPasswordConfirm"
               value={this.state.newPasswordConfirm}
@@ -115,17 +115,15 @@ class PasswordSettings extends React.Component {
           </div>
         </div>
 
-        <div className="row my-3">
-          <div className="col-xs-offset-4 col-xs-5">
-            <button
-              type="button"
-              className="btn btn-primary"
-              onClick={this.onClickSubmit}
-              disabled={this.state.retrieveError != null || isIncorrectConfirmPassword}
-            >
-              {t('Update')}
-            </button>
-          </div>
+        <div className="my-3 text-center">
+          <button
+            type="button"
+            className="btn btn-primary"
+            onClick={this.onClickSubmit}
+            disabled={this.state.retrieveError != null || isIncorrectConfirmPassword}
+          >
+            {t('Update')}
+          </button>
         </div>
       </React.Fragment>
     );

+ 8 - 8
src/client/js/components/Me/PersonalSettings.jsx

@@ -19,17 +19,17 @@ class PersonalSettings extends React.Component {
         <div className="m-t-10">
           <div className="personal-settings">
             <ul className="nav nav-tabs" role="tablist">
-              <li className="active">
-                <a href="#user-settings" data-toggle="tab" role="tab"><i className="icon-user"></i> { t('User Information') }</a>
+              <li className="nav-item">
+                <a className="nav-link active" href="#user-settings" data-toggle="tab" role="tab"><i className="icon-user"></i> { t('User Information') }</a>
               </li>
-              <li>
-                <a href="#external-accounts" data-toggle="tab" role="tab"><i className="icon-share-alt"></i> { t('External Accounts') }</a>
+              <li className="nav-item">
+                <a className="nav-link" href="#external-accounts" data-toggle="tab" role="tab"><i className="icon-share-alt"></i> { t('External Accounts') }</a>
               </li>
-              <li>
-                <a href="#password-settings" data-toggle="tab" role="tab"><i className="icon-lock"></i> { t('Password Settings') }</a>
+              <li className="nav-item">
+                <a className="nav-link" href="#password-settings" data-toggle="tab" role="tab"><i className="icon-lock"></i> { t('Password Settings') }</a>
               </li>
-              <li>
-                <a href="#apiToken" data-toggle="tab" role="tab"><i className="icon-paper-plane"></i> { t('API Settings') }</a>
+              <li className="nav-item">
+                <a className="nav-link" href="#apiToken" data-toggle="tab" role="tab"><i className="icon-paper-plane"></i> { t('API Settings') }</a>
               </li>
             </ul>
             <div className="tab-content p-t-10">

+ 9 - 7
src/client/js/components/Me/ProfileImageSettings.jsx

@@ -102,18 +102,19 @@ class ProfileImageSettings extends React.Component {
     return (
       <React.Fragment>
         <div className="row">
-          <div className="col-md-2 col-sm-offset-1 col-sm-4">
+          <div className="col-md-2 offset-1 col-sm-4">
             <h4>
-              <div className="radio radio-primary">
+              <div className="custom-control custom-radio radio-primary">
                 <input
                   type="radio"
                   id="radioGravatar"
+                  className="custom-control-input"
                   form="formImageType"
                   name="imagetypeForm[isGravatarEnabled]"
                   checked={isGravatarEnabled}
                   onChange={() => { personalContainer.changeIsGravatarEnabled(true) }}
                 />
-                <label htmlFor="radioGravatar">
+                <label className="custom-control-label" htmlFor="radioGravatar">
                   <img src="https://gravatar.com/avatar/00000000000000000000000000000000?s=24" /> Gravatar
                 </label>
                 <a href="https://gravatar.com/">
@@ -127,16 +128,17 @@ class ProfileImageSettings extends React.Component {
 
           <div className="col-md-4 col-sm-7">
             <h4>
-              <div className="radio radio-primary">
+              <div className="custom-control custom-radio radio-primary">
                 <input
                   type="radio"
                   id="radioUploadPicture"
+                  className="custom-control-input"
                   form="formImageType"
                   name="imagetypeForm[isGravatarEnabled]"
                   checked={!isGravatarEnabled}
                   onChange={() => { personalContainer.changeIsGravatarEnabled(false) }}
                 />
-                <label htmlFor="radioUploadPicture">
+                <label className="custom-control-label" htmlFor="radioUploadPicture">
                   { t('Upload Image') }
                 </label>
               </div>
@@ -146,7 +148,7 @@ class ProfileImageSettings extends React.Component {
                 { t('Current Image') }
               </label>
               <div className="col-sm-8">
-                {uploadedPictureSrc && (<p><img src={uploadedPictureSrc} className="picture picture-lg img-circle" id="settingUserPicture" /></p>)}
+                {uploadedPictureSrc && (<p><img src={uploadedPictureSrc} className="picture picture-lg rounded-circle" id="settingUserPicture" /></p>)}
                 {isUploadedPicture && <button type="button" className="btn btn-danger" onClick={this.onClickDeleteBtn}>{ t('Delete Image') }</button>}
               </div>
             </div>
@@ -169,7 +171,7 @@ class ProfileImageSettings extends React.Component {
         />
 
         <div className="row my-3">
-          <div className="col-xs-offset-4 col-xs-5">
+          <div className="offset-4 col-5">
             <button type="button" className="btn btn-primary" onClick={this.onClickSubmit} disabled={personalContainer.state.retrieveError != null}>
               {t('Update')}
             </button>

+ 17 - 10
src/client/js/components/Navbar/GrowiSubNavigation.jsx

@@ -18,8 +18,9 @@ const GrowiSubNavigation = (props) => {
   const isPageForbidden = document.querySelector('#grw-subnav').getAttribute('data-is-forbidden-page');
   const { appContainer, pageContainer } = props;
   const {
-    path, createdAt, creator, updatedAt, revisionAuthor,
+    pageId, path, createdAt, creator, updatedAt, revisionAuthor, isCompactMode,
   } = 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) {
@@ -27,7 +28,7 @@ const GrowiSubNavigation = (props) => {
       <div className="d-flex align-items-center">
         <div className="title-container mr-auto">
           <h1>
-            <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} />
+            <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageId} pagePath={pageContainer.state.path} />
           </h1>
         </div>
       </div>
@@ -35,28 +36,34 @@ const GrowiSubNavigation = (props) => {
   }
 
   return (
-    <div className="d-flex align-items-center">
+    <div className={`d-flex align-items-center ${compactClassName}`}>
 
       {/* Page Path */}
       <div className="title-container mr-auto">
         <h1>
-          <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} />
+          <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageId} pagePath={pageContainer.state.path} />
         </h1>
         <TagLabels />
       </div>
 
       {/* Header Button */}
-      <div className="ml-1">
-        <LikeButton pageId={pageContainer.state.pageId} isLiked={pageContainer.state.isLiked} />
+      <div className="mr-2">
+        <LikeButton pageId={pageId} />
       </div>
       <div>
-        <BookmarkButton pageId={pageContainer.state.pageId} crowi={appContainer} />
+        <BookmarkButton pageId={pageId} crowi={appContainer} />
       </div>
 
       {/* Page Authors */}
-      <ul className="authors hidden-sm hidden-xs text-nowrap">
-        {creator != null && <li><PageCreator creator={creator} createdAt={createdAt} /></li>}
-        {revisionAuthor != null && <li className="mt-1"><RevisionAuthor revisionAuthor={revisionAuthor} updatedAt={updatedAt} /></li>}
+      <ul className="authors text-nowrap d-none d-lg-block">
+        {creator != null && <li><PageCreator creator={creator} createdAt={createdAt} isCompactMode={isCompactMode} /></li>}
+        { revisionAuthor != null
+          && (
+            <li className="mt-1">
+              <RevisionAuthor revisionAuthor={revisionAuthor} updatedAt={updatedAt} isCompactMode={isCompactMode} />
+            </li>
+          )
+        }
       </ul>
 
     </div>

+ 12 - 6
src/client/js/components/Navbar/PageCreator.jsx

@@ -5,17 +5,18 @@ import UserPicture from '../User/UserPicture';
 import { userPageRoot } from '../../../../lib/util/path-utils';
 
 const PageCreator = (props) => {
-  const { creator, createdAt } = props;
+  const { creator, createdAt, isCompactMode } = props;
+  const creatInfo = isCompactMode
+    ? (<div>Created in <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';
 
   return (
     <div className="d-flex align-items-center">
       <div className="mr-2" href={userPageRoot(creator)} data-toggle="tooltip" data-placement="bottom" title={creator.name}>
-        <UserPicture user={creator} size="sm" />
-      </div>
-      <div>
-        <div>Created by <a href={userPageRoot(creator)}>{creator.name}</a></div>
-        <div className="text-muted">{createdAt}</div>
+        <UserPicture user={creator} size={pictureSize} />
       </div>
+      {creatInfo}
     </div>
   );
 };
@@ -24,6 +25,11 @@ PageCreator.propTypes = {
 
   creator: PropTypes.object.isRequired,
   createdAt: PropTypes.string.isRequired,
+  isCompactMode: PropTypes.bool,
+};
+
+PageCreator.defaultProps = {
+  isCompactMode: false,
 };
 
 

+ 3 - 1
src/client/js/components/Navbar/PersonalDropdown.jsx

@@ -56,7 +56,9 @@ const PersonalDropdown = (props) => {
   return (
     <>
       {/* Button */}
-      <a className="nav-link dropdown-toggle waves-effect waves-light" data-toggle="dropdown">
+      {/* remove .dropdown-toggle for hide caret */}
+      {/* See https://stackoverflow.com/a/44577512/13183572 */}
+      <a className="nav-link waves-effect waves-light" data-toggle="dropdown">
         <UserPicture user={user} withoutLink />&nbsp;{user.name}
       </a>
 

+ 11 - 6
src/client/js/components/Navbar/RevisionAuthor.jsx

@@ -5,17 +5,18 @@ import UserPicture from '../User/UserPicture';
 import { userPageRoot } from '../../../../lib/util/path-utils';
 
 const RevisionAuthor = (props) => {
-  const { revisionAuthor, updatedAt } = 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>);
+  const pictureSize = isCompactMode ? 'xs' : 'sm';
 
   return (
     <div className="d-flex align-items-center">
       <div className="mr-2" href={userPageRoot(revisionAuthor)} data-toggle="tooltip" data-placement="bottom" title={revisionAuthor.name}>
-        <UserPicture user={revisionAuthor} size="sm" />
-      </div>
-      <div>
-        <div>Updated by  <a href={userPageRoot(revisionAuthor)}>{revisionAuthor.name}</a></div>
-        <div className="text-muted">{updatedAt}</div>
+        <UserPicture user={revisionAuthor} size={pictureSize} />
       </div>
+      {updateInfo}
     </div>
   );
 };
@@ -24,7 +25,11 @@ RevisionAuthor.propTypes = {
 
   revisionAuthor: PropTypes.object.isRequired,
   updatedAt: PropTypes.string.isRequired,
+  isCompactMode: PropTypes.bool,
 };
 
+RevisionAuthor.defaultProps = {
+  isCompactMode: false,
+};
 
 export default RevisionAuthor;

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

@@ -152,7 +152,7 @@ class RevisionPath extends React.Component {
     });
 
     return (
-      <span className="d-flex align-items-center">
+      <span className="d-flex align-items-center flex-wrap">
 
         {rootElement}
         {afterElements}

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

@@ -54,7 +54,7 @@ export default class TagEditor extends React.Component {
           <TagsInput tags={this.state.tags} onTagsUpdated={this.onTagsUpdatedByTagsInput} />
         </ModalBody>
         <ModalFooter>
-          <Button variant="primary" onClick={this.handleSubmit}>
+          <Button color="primary" onClick={this.handleSubmit}>
             Done
           </Button>
         </ModalFooter>

+ 2 - 2
src/client/js/components/PageAttachment/Attachment.jsx

@@ -31,10 +31,10 @@ export default class Attachment extends React.Component {
 
     let fileInUse = '';
     if (this.props.inUse) {
-      fileInUse = <span className="attachment-in-use label label-info">In Use</span>;
+      fileInUse = <span className="attachment-in-use badge badge-pill badge-info">In Use</span>;
     }
 
-    const fileType = <span className="attachment-filetype label label-default">{attachment.fileFormat}</span>;
+    const fileType = <span className="attachment-filetype badge badge-pill badge-secondary">{attachment.fileFormat}</span>;
 
     const btnDownload = (this.props.isUserLoggedIn)
       ? (

+ 2 - 2
src/client/js/components/PageComment/CommentEditor.jsx

@@ -224,7 +224,7 @@ class CommentEditor extends React.Component {
 
     const errorMessage = <span className="text-danger text-right mr-2">{this.state.errorMessage}</span>;
     const cancelButton = (
-      <Button outline color="danger" size="xs" className="fcbtn rounded-pill" onClick={this.toggleEditor}>
+      <Button outline color="danger" size="xs" className="btn btn-outline-danger rounded-pill" onClick={this.toggleEditor}>
         Cancel
       </Button>
     );
@@ -232,7 +232,7 @@ class CommentEditor extends React.Component {
       <Button
         outline
         color="primary"
-        className="fcbtn rounded-pill btn-1b"
+        className="btn btn-outline-primary rounded-pill"
         onClick={this.postHandler}
       >
         Comment

+ 3 - 3
src/client/js/components/PageComment/DeleteCommentModal.jsx

@@ -45,12 +45,12 @@ export default class DeleteCommentModal extends React.Component {
         </ModalHeader>
         <ModalBody>
           <UserPicture user={comment.creator} size="xs" /> <strong><Username user={comment.creator}></Username></strong> wrote on {commentDate}:
-          <p className="well well-sm comment-body mt-2">{commentBody}</p>
+          <p className="card well comment-body mt-2 p-2">{commentBody}</p>
         </ModalBody>
         <ModalFooter>
           <span className="text-danger">{this.props.errorMessage}</span>&nbsp;
-          <Button onClick={this.props.cancel} bsClass="btn btn-sm">Cancel</Button>
-          <Button onClick={this.props.confirmedToDelete} bsClass="btn btn-sm btn-danger">
+          <Button onClick={this.props.cancel}>Cancel</Button>
+          <Button color="danger" onClick={this.props.confirmedToDelete}>
             <i className="icon icon-fire"></i>
             Delete
           </Button>

+ 3 - 3
src/client/js/components/PageEditor/OptionsSelector.jsx

@@ -112,7 +112,7 @@ class OptionsSelector extends React.Component {
       <div className="my-0 form-group">
         <label>Theme:</label>
         <div className="btn-group btn-group-sm dropup">
-          <button className="btn btn-white dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+          <button className="btn btn-light dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
             {selectedTheme}
           </button>
           <div className="dropdown-menu" aria-labelledby="dropdownMenuLink">
@@ -139,7 +139,7 @@ class OptionsSelector extends React.Component {
       <div className="my-0 form-group">
         <label>Keymap:</label>
         <div className="btn-group btn-group-sm dropup">
-          <button className="btn btn-white dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+          <button className="btn btn-light dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
             {selectedKeymapMode}
           </button>
           <div className="dropdown-menu" aria-labelledby="dropdownMenuLink">
@@ -162,7 +162,7 @@ class OptionsSelector extends React.Component {
           toggle={this.onToggleConfigurationDropdown}
         >
 
-          <DropdownToggle color="white" caret>
+          <DropdownToggle color="light" caret>
             <i className="icon-settings"></i>
           </DropdownToggle>
 

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

@@ -166,7 +166,7 @@ class GrantSelector extends React.Component {
     return (
       <div className="form-group grw-grant-selector mb-0">
         <UncontrolledDropdown direction="up" size="sm">
-          <DropdownToggle color="white" caret className="d-flex justify-content-between align-items-center" disabled={this.props.disabled}>
+          <DropdownToggle color="light" caret className="d-flex justify-content-between align-items-center" disabled={this.props.disabled}>
             {dropdownToggleLabelElm}
           </DropdownToggle>
           <DropdownMenu>

+ 16 - 6
src/client/js/components/SearchPage/DeletePageListModal.jsx

@@ -41,19 +41,24 @@ export default class DeletePageListModal extends React.Component {
           <div className="d-flex justify-content-between">
             <span className="text-danger">{this.props.errorMessage}</span>
             <span className="d-flex align-items-center">
-              <div className="custom-control custom-checkbox custom-checkbox-danger">
-                <input type="checkbox" className="custom-control-input" id="customCheck-delete-completely" />
+              <div className="custom-control custom-checkbox custom-checkbox-danger mr-2">
+                <input
+                  type="checkbox"
+                  className="custom-control-input"
+                  id="customCheck-delete-completely"
+                  checked={this.props.isDeleteCompletely}
+                  onChange={this.props.toggleDeleteCompletely}
+                />
                 <label
                   className="custom-control-label text-danger"
                   htmlFor="customCheck-delete-completely"
-                  onClick={this.props.toggleDeleteCompletely}
                 >
                   Delete completely
                 </label>
               </div>
-              <span className="ml-2">
-                <Button color="secondary" onClick={this.props.confirmedToDelete}><i className="icon-trash"></i>Delete</Button>
-              </span>
+              <Button color={this.props.isDeleteCompletely ? 'danger' : 'light'} onClick={this.props.confirmedToDelete}>
+                <i className="icon-trash"></i>Delete
+              </Button>
             </span>
           </div>
         </ModalFooter>
@@ -63,11 +68,16 @@ export default class DeletePageListModal extends React.Component {
 
 }
 
+DeletePageListModal.defaultProps = {
+  isDeleteCompletely: false, // for when undefined is passed
+};
+
 DeletePageListModal.propTypes = {
   isShown: PropTypes.bool.isRequired,
   pages: PropTypes.array,
   errorMessage: PropTypes.string,
   cancel: PropTypes.func.isRequired, //                 for cancel evnet handling
+  isDeleteCompletely: PropTypes.bool,
   confirmedToDelete: PropTypes.func.isRequired, //      for confirmed event handling
   toggleDeleteCompletely: PropTypes.func.isRequired, // for delete completely check event handling
 };

+ 3 - 2
src/client/js/components/SearchPage/SearchResult.jsx

@@ -202,12 +202,12 @@ class SearchResult extends React.Component {
     if (this.state.deletionMode) {
       deletionModeButtons = (
         <div className="btn-group">
-          <button type="button" className="btn btn-rounded btn-light btn-sm" onClick={() => { return this.handleDeletionModeChange() }}>
+          <button type="button" className="btn btn-light btn-sm rounded-pill-weak" onClick={() => { return this.handleDeletionModeChange() }}>
             <i className="icon-ban" /> Cancel
           </button>
           <button
             type="button"
-            className="btn btn-rounded btn-danger btn-sm"
+            className="btn btn-danger btn-sm rounded-pill-weak"
             onClick={() => { return this.showDeleteConfirmModal() }}
             disabled={this.state.selectedPages.size === 0}
           >
@@ -305,6 +305,7 @@ class SearchResult extends React.Component {
           errorMessage={this.state.errorMessageForDeleting}
           cancel={this.closeDeleteConfirmModal}
           confirmedToDelete={this.deleteSelectedPages}
+          isDeleteCompletely={this.state.isDeleteCompletely}
           toggleDeleteCompletely={this.toggleDeleteCompletely}
         />
       </div> // content-main

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

@@ -36,7 +36,7 @@ export default class SlackNotification extends React.Component {
   render() {
     return (
       <div className="input-group input-group-sm input-group-slack extended-setting">
-        <label className="input-group-addon">
+        <label className="input-group-addon bg-light">
           <img id="slack-mark-white" alt="slack-mark" src="/images/icons/slack/mark-monochrome_white.svg" width="18" height="18" />
           <img id="slack-mark-black" alt="slack-mark" src="/images/icons/slack/mark-monochrome_black.svg" width="18" height="18" />
 

+ 9 - 1
src/client/js/components/StaffCredit/Contributor.js

@@ -9,6 +9,7 @@ const contributors = [
           { position: 'Founder', name: 'yuki-takei' },
           { position: 'Soncho 1st', name: 'mizozobu' },
           { position: 'Soncho 2nd', name: 'yusuketk' },
+          { position: 'Paladin', name: 'itizawa' },
         ],
       },
       {
@@ -19,7 +20,6 @@ const contributors = [
           { name: 'TatsuyaIse' },
           { name: 'shinoka7' },
           { name: 'SeiyaTashiro' },
-          { name: 'itizawa' },
           { name: 'TsuyoshiSuzukief' },
           { name: 'Yuchan4342' },
           { name: 'ryu-sato' },
@@ -28,6 +28,14 @@ const contributors = [
           { name: 'kaishuu0123' },
           { name: 'kouki-o' },
           { name: 'Angola' },
+          { name: 'Yohei-Shiina' },
+          { name: 'shukmos' },
+          { name: 'sooouh' },
+          { name: 'ryouhek' },
+          { name: 'ryuichi-e' },
+          { name: 'N1koge' },
+          { name: 'Ertai87' },
+          { name: 'kaoritokashiki' },
         ],
       },
     ],

+ 1 - 0
src/client/js/services/AdminGeneralSecurityContainer.js

@@ -39,6 +39,7 @@ export default class AdminGeneralSecurityContainer extends Container {
     const response = await this.appContainer.apiv3.get('/security-setting/');
     const { generalSetting, generalAuth } = response.data.securityParams;
     this.setState({
+      currentRestrictGuestMode: generalSetting.restrictGuestMode,
       currentPageCompleteDeletionAuthority: generalSetting.pageCompleteDeletionAuthority,
       isShowRestrictedByOwner: !generalSetting.hideRestrictedByOwner,
       isShowRestrictedByGroup: !generalSetting.hideRestrictedByGroup,

+ 9 - 5
src/client/js/services/PageContainer.js

@@ -5,7 +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;
 
 /**
  * Service container related to Page
@@ -38,7 +41,7 @@ export default class PageContainer extends Container {
       revisionAuthor: JSON.parse(mainContent.getAttribute('data-page-revision-author')),
       path: mainContent.getAttribute('data-path'),
       tocHtml: '',
-      isLiked: false,
+      isLiked: mainContent.getAttribute('data-page-is-liked'),
       seenUserIds: [],
       likerUserIds: [],
       createdAt: mainContent.getAttribute('data-page-created-at'),
@@ -55,6 +58,7 @@ 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,
     };
 
     this.initStateMarkdown();
@@ -64,6 +68,10 @@ export default class PageContainer extends Container {
     this.save = this.save.bind(this);
     this.addWebSocketEventHandlers = this.addWebSocketEventHandlers.bind(this);
     this.addWebSocketEventHandlers();
+
+    window.addEventListener('scroll', throttle(300, () => {
+      this.setState({ isCompactMode: window.pageYOffset > scrollAmountForFixed });
+    }));
   }
 
   /**
@@ -89,10 +97,6 @@ export default class PageContainer extends Container {
   }
 
   initStateOthers() {
-    const likeButtonElem = document.getElementById('like-button');
-    if (likeButtonElem != null) {
-      this.state.isLiked = likeButtonElem.dataset.liked === 'true';
-    }
 
     const seenUserListElem = document.getElementById('seen-user-list');
     if (seenUserListElem != null) {

+ 54 - 0
src/client/styles/scss/_layout_kibela.scss

@@ -1,3 +1,5 @@
+@import '../scss/theme/layout_kibela_variable';
+
 body.kibela {
   .icon-link,
   .CodeMirror-hint-active,
@@ -11,6 +13,10 @@ body.kibela {
     background: #fefffe !important;
   }
 
+  .bg-primary {
+    background-color: $primary !important;
+  }
+
   .logo {
     background: transparent;
 
@@ -180,6 +186,54 @@ body.kibela {
     }
   }
 
+  /* Modal */
+  .modal-content {
+    background-color: $themelight;
+
+    .modal-header.bg-primary {
+      color: white;
+
+      .close {
+        color: white;
+      }
+    }
+  }
+
+  /* Inline Code */
+  :not(.hljs) > code:not(.hljs) {
+    background-color: $bgcolor-inline-code;
+    color: $color-inline-code;
+  }
+
+  /* Card */
+  .card {
+    border: 1px solid $border;
+
+    .card-header {
+      background-color: $lightthemecolor;
+      border-bottom: 1px solid $border;
+    }
+
+    .card-body {
+      background-color: $themelight;
+    }
+
+    .card-footer {
+      background: white;
+      border-top: 1px solid $border
+    }
+  }
+
+  /* button */
+  .btn {
+    border-radius: $radius;
+  }
+
+  .btn-primary {
+    background: $primary;
+    border: 1px solid $primary;
+  }
+
   /* edit */
   .CodeMirror {
     border: solid 1.2px #d8d8d8;

+ 18 - 62
src/client/styles/scss/_login.scss

@@ -129,50 +129,7 @@
   }
 
   // button style
-
-  .fcbtn {
-    position: relative;
-    overflow: hidden;
-    color: white;
-    text-align: center;
-    cursor: pointer;
-    background-color: rgba(lighten(black, 20%), 0.4);
-    border: none;
-
-    .btn-label {
-      position: relative;
-      z-index: 1;
-      color: white;
-      text-decoration: none;
-    }
-
-    .btn-label-text {
-      position: relative;
-      z-index: 1;
-      color: white;
-      text-decoration: none;
-    }
-
-    // effect
-    .eff {
-      position: absolute;
-      top: -50px;
-      left: 0px;
-      z-index: 0;
-      width: 100%;
-      height: 100%;
-      transition: all 0.5s ease;
-    }
-
-    &:hover {
-      .eff {
-        top: 0;
-      }
-    }
-  }
-
-  // login
-  .fcbtn.login {
+  .btn-fill.login {
     .btn-label {
       background-color: rgba($danger, 0.4);
     }
@@ -182,7 +139,7 @@
   }
 
   // google
-  .fcbtn#google {
+  .btn-fill#google {
     .btn-label {
       background-color: rgba(#24292e, 0.4);
     }
@@ -193,7 +150,7 @@
   }
 
   // github
-  .fcbtn#github {
+  .btn-fill#github {
     .btn-label {
       background-color: rgba(lighten(black, 20%), 0.4);
     }
@@ -204,7 +161,7 @@
   }
 
   // facebook
-  .fcbtn#facebook {
+  .btn-fill#facebook {
     .btn-label {
       background-color: rgba(#29487d, 0.4);
     }
@@ -215,7 +172,7 @@
   }
 
   // twitter
-  .fcbtn#twitter {
+  .btn-fill#twitter {
     .btn-label {
       background-color: rgba(#1da1f2, 0.4);
     }
@@ -226,7 +183,7 @@
   }
 
   // oidc
-  .fcbtn#oidc {
+  .btn-fill#oidc {
     .btn-label {
       background-color: rgba(#24292e, 0.4);
     }
@@ -237,7 +194,7 @@
   }
 
   // saml
-  .fcbtn#saml {
+  .btn-fill#saml {
     .btn-label {
       background-color: rgba(#55a79a, 0.4);
     }
@@ -248,7 +205,7 @@
   }
 
   // basic
-  .fcbtn#basic {
+  .btn-fill#basic {
     .btn-label {
       background-color: rgba(#24292e, 0.4);
     }
@@ -257,6 +214,16 @@
       background-color: #555;
     }
   }
+  // register
+  .btn-fill#register {
+    .btn-label {
+      background-color: rgba($success, 0.4);
+    }
+
+    .eff {
+      background-color: rgba(#3f7263, 0.5);
+    }
+  }
 
   // external-auth
   .btn-collapse-external-auth {
@@ -274,17 +241,6 @@
     }
   }
 
-  // register
-  .fcbtn#register {
-    .btn-label {
-      background-color: rgba($success, 0.4);
-    }
-
-    .eff {
-      background-color: rgba(#3f7263, 0.5);
-    }
-  }
-
   // footer link text
   .link-growi-org {
     font-size: smaller;

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

@@ -6,9 +6,9 @@
 //
 $primary: #112744;
 $secondary: #6c757d;
-$info: #0d8ea5;
+$info: #009fbb;
 $success: #00bb83;
-$warning: #ee773b;
+$warning: #ffa32b;
 $danger: #ff0a54;
 $light: #dee2e6;
 $dark: #343a40;

+ 5 - 0
src/client/styles/scss/_override-bootstrap.scss

@@ -171,3 +171,8 @@ fieldset[disabled] .btn {
   margin-bottom: 18px;
   overflow: hidden;
 }
+
+// badge
+.badge {
+  letter-spacing: 0.05em;
+}

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

@@ -20,5 +20,20 @@
         }
       }
     }
+    .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;
+        }
+      }
+    }
   }
 }

+ 58 - 13
src/client/styles/scss/atoms/_buttons.scss

@@ -26,25 +26,24 @@
   border-radius: 35px;
 }
 
-#like-button,
-#bookmark-button {
-  & button {
-    font-size: 1.2em;
-    line-height: 0.8em;
-
-    &:not(:hover):not(.active) {
-      background-color: transparent;
-    }
+.btn-like,
+.btn-bookmark {
+  font-size: 1.2em;
+  line-height: 0.8em;
+
+  &.active {
+    // header buttons are always white for active
+    color: white !important;
+  }
+
+  &:not(:hover):not(.active) {
+    background-color: transparent;
   }
 }
 
 .btn-copy,
 .btn-edit {
   opacity: 0.3;
-
-  &:hover {
-    background-color: $light;
-  }
 }
 
 .btn-edit-tags {
@@ -54,3 +53,49 @@
     opacity: 0.7;
   }
 }
+
+.rounded-pill-weak {
+  border-radius: 60px;
+}
+
+// fill button style
+.btn-fill {
+  position: relative;
+  overflow: hidden;
+  color: white;
+  text-align: center;
+  cursor: pointer;
+  background-color: rgba(lighten(black, 20%), 0.4);
+  border: none;
+
+  .btn-label {
+    position: relative;
+    z-index: 1;
+    color: white;
+    text-decoration: none;
+  }
+
+  .btn-label-text {
+    position: relative;
+    z-index: 1;
+    color: white;
+    text-decoration: none;
+  }
+
+  // effect
+  .eff {
+    position: absolute;
+    top: -50px;
+    left: 0px;
+    z-index: 0;
+    width: 100%;
+    height: 100%;
+    transition: all 0.5s ease;
+  }
+
+  &:hover {
+    .eff {
+      top: 0;
+    }
+  }
+}

+ 8 - 0
src/client/styles/scss/theme/_apply-colors-dark.scss

@@ -136,6 +136,14 @@ 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%);
+}
+
 /*
  * GROWI search page
  */

+ 8 - 0
src/client/styles/scss/theme/_apply-colors-light.scss

@@ -32,6 +32,14 @@ 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%);
+}
+
 /*
  * GROWI page list
  */

+ 6 - 2
src/client/styles/scss/theme/_apply-colors.scss

@@ -25,7 +25,11 @@ $link-hover-color: $color-link-hover;
   color: $color-global;
   &.active,
   &:active {
-    @include gradient-bg($dropdown-link-active-bg);
+    color: $color-dropdown-link-active;
+    background-color: $bgcolor-dropdown-link-active;
+  }
+  &:hover:not(.active) {
+    color: $color-dropdown-link-hover;
   }
 }
 
@@ -313,4 +317,4 @@ body.on-edit {
       }
     }
   }
-}
+}

+ 39 - 0
src/client/styles/scss/theme/_layout_kibela_variable.scss

@@ -0,0 +1,39 @@
+$radius: .25em;
+
+$bgcolor-theme: rgb(18, 86, 163);
+$themelight: #f4f5f6;
+$subthemecolor: rgb(90, 149, 216);
+$lightthemecolor: rgba(181, 203, 247, 0.61);
+
+$bgcolor-navbar: $bgcolor-theme;
+$bgcolor-global: $themelight;
+$bgcolor-global: $themelight;
+
+$color-header: $bgcolor-theme;
+$color-global: #3c4a60;
+$linktext: rgb(74, 109, 204);
+$linktext-hover: lighten($linktext, 12%);
+$sidebar-text: $bgcolor-theme;
+
+$primary: $bgcolor-theme;
+$info: lighten($bgcolor-theme, 20%);
+
+$fillcolor-logo-mark: lighten($bgcolor-theme, 20%);
+$color-link-wiki: lighten($bgcolor-theme, 20%);
+$color-link-wiki-hover: lighten($color-link-wiki, 20%);
+$color-inline-code: $subthemecolor;
+$bgcolor-inline-code: lighten($subthemecolor, 70%);
+$border: $lightthemecolor;
+
+// change color of highlighted header in wiki (default: orange)
+.wiki {
+  .code-line.revision-head.highlighted {
+    color: $themelight;
+    background-color: lighten($bgcolor-theme, 20%);
+
+    .icon-note,
+    .icon-link {
+      color: $themelight;
+    }
+  }
+}

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

@@ -190,13 +190,17 @@ body {
 //
 
 a {
-  color: $link-color;
-  // text-decoration: $link-decoration;
-  background-color: transparent; // Remove the gray background on active links in IE 10.
+  :not(.badge) {
+    color: $link-color;
+    // text-decoration: $link-decoration;
+    background-color: transparent; // Remove the gray background on active links in IE 10.
+  }
 
   @include hover() {
-    color: $link-hover-color;
-    // text-decoration: $link-hover-decoration;
+    &:not(.list-group-item) {
+      color: $link-hover-color;
+      // text-decoration: $link-hover-decoration;
+    }
   }
 }
 

+ 12 - 0
src/client/styles/scss/theme/default.scss

@@ -26,6 +26,7 @@ html:not([dark]) {
 
   // Font colors
   $color-global: #333333;
+  $color-reversal: #eeeeee;
   // $color-header: #2b2b2b;
   $color-link: lighten($primary, 20%);
   $color-link-hover: lighten($color-link, 20%);
@@ -39,6 +40,11 @@ html:not([dark]) {
   // 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';
 }
@@ -58,6 +64,7 @@ html[dark] {
 
   // Font colors
   $color-global: #eeeeee;
+  $color-reversal: #333333;
   // $color-header: desaturate($primary, 20%);
   $color-link: $primary;
   $color-link-hover: lighten($color-link, 10%);
@@ -71,6 +78,11 @@ html[dark] {
   // 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';
 }

+ 1 - 0
src/client/styles/scss/theme/kibela.scss

@@ -1,5 +1,6 @@
 // import colors
 @import '../../agile-admin/inverse/colors/kibela';
+@import 'layout_kibela_valiable';
 
 // apply agile-admin theme
 @import '../../agile-admin/inverse/style';

+ 30 - 0
src/migrations/2020040216038-remove-deleteduser-from-relationgroup.js

@@ -0,0 +1,30 @@
+require('module-alias/register');
+const logger = require('@alias/logger')('growi:migrate:remove-deleteduser-from-relationgroup');
+
+const mongoose = require('mongoose');
+const config = require('@root/config/migrate');
+
+const { getModelSafely } = require('@commons/util/mongoose-utils');
+
+module.exports = {
+  async up(db) {
+    logger.info('Apply migration');
+    mongoose.connect(config.mongoUri, config.mongodb.options);
+
+    const User = getModelSafely('User') || require('@server/models/user')();
+    const UserGroupRelation = getModelSafely('UserGroupRelation') || require('@server/models/user-group-relation')();
+
+    const deletedUsers = await User.find({ status: 4 }); // deleted user
+    const requests = await UserGroupRelation.remove({ relatedUser: deletedUsers });
+
+    if (requests.size === 0) {
+      return logger.info('This migration terminates without any changes.');
+    }
+    logger.info('Migration has successfully applied');
+
+  },
+
+  down(db) {
+    // do not rollback
+  },
+};

+ 6 - 4
src/server/models/bookmark.js

@@ -45,10 +45,12 @@ module.exports = function(crowi) {
     const Bookmark = this;
     const User = crowi.model('User');
 
-    return Bookmark.populate(bookmarks, [
-      { path: 'page' },
-      { path: 'lastUpdateUser', model: 'User', select: User.USER_PUBLIC_FIELDS },
-    ]);
+    return Bookmark.populate(bookmarks, {
+      path: 'page',
+      populate: {
+        path: 'lastUpdateUser', model: 'User', select: User.USER_PUBLIC_FIELDS, populate: User.IMAGE_POPULATION,
+      },
+    });
   };
 
   // bookmark チェック用

+ 6 - 1
src/server/models/user-group-relation.js

@@ -85,10 +85,15 @@ class UserGroupRelation {
    * @memberof UserGroupRelation
    */
   static findAllRelationForUserGroup(userGroup) {
+    const User = UserGroupRelation.crowi.model('User');
     debug('findAllRelationForUserGroup is called', userGroup);
     return this
       .find({ relatedGroup: userGroup })
-      .populate('relatedUser')
+      .populate({
+        path: 'relatedUser',
+        select: User.USER_PUBLIC_FIELDS,
+        populate: User.IMAGE_POPULATION,
+      })
       .exec();
   }
 

+ 2 - 2
src/server/routes/apiv3/notification-setting.js

@@ -14,9 +14,9 @@ const removeNullPropertyFromObject = require('../../../lib/util/removeNullProper
 
 const validator = {
   slackConfiguration: [
-    body('webhookUrl').isString().trim(),
+    body('webhookUrl').if(value => value != null).isString().trim(),
     body('isIncomingWebhookPrioritized').isBoolean(),
-    body('slackToken').isString().trim(),
+    body('slackToken').if(value => value != null).isString().trim(),
   ],
   userNotification: [
     body('pathPattern').isString().trim(),

+ 1 - 0
src/server/routes/apiv3/user-group.js

@@ -587,6 +587,7 @@ module.exports = (crowi) => {
         populate: {
           path: 'lastUpdateUser',
           select: User.USER_PUBLIC_FIELDS,
+          populate: User.IMAGE_POPULATION,
         },
       });
 

+ 1 - 0
src/server/routes/apiv3/users.js

@@ -182,6 +182,7 @@ module.exports = (crowi) => {
         },
         {
           sort: sortOutput,
+          populate: User.IMAGE_POPULATION,
           page,
           limit: PAGE_ITEMS,
         },

+ 20 - 12
src/server/views/invited.html

@@ -26,7 +26,7 @@
 
   <div class="row">
 
-    <div class="login-header col-sm-offset-4 col-sm-4">
+    <div class="login-header offset-4 col-sm-4">
       <div class="logo">{% include 'widget/logo.html' %}</div>
       <h1>GROWI</h1>
 
@@ -50,7 +50,7 @@
       </div>
     </div>
 
-    <div class="login-dialog grw-pt-10px p-b-10 col-sm-offset-4 col-sm-4" id="login-dialog">
+    <div class="login-dialog grw-pt-10px p-b-10 offset-4 col-sm-4" id="login-dialog">
       <p class="alert alert-success">
         <strong>アカウントの作成</strong><br>
         <small>招待を受け取ったメールアドレスでアカウントを作成します</small>
@@ -59,12 +59,15 @@
       <form role="form" action="/login/activateInvited" method="post" id="invited-form">
 
         <div class="input-group">
-          <span class="input-group-addon"><i class="icon-envelope"></i></span>
+          <div class="input-group-prepend">
+            <span class="input-group-text"><i class="icon-envelope"></i></span>
+          </div>
           <input type="text" class="form-control" disabled value="{{ user.email }}">
         </div>
-
         <div class="input-group" id="input-group-username">
-          <span class="input-group-addon"><i class="icon-user"></i></span>
+          <div class="input-group-prepend">
+            <span class="input-group-text"><i class="icon-user"></i></span>
+          </div>
           <input type="text" class="form-control" placeholder="{{ t('User ID') }}" name="invitedForm[username]" value="{{ req.body.invitedForm.username }}" required>
         </div>
         <p class="help-block">
@@ -72,21 +75,26 @@
         </p>
 
         <div class="input-group">
-          <span class="input-group-addon"><i class="icon-tag"></i></span>
+          <div class="input-group-prepend">
+            <span class="input-group-text"><i class="icon-tag"></i></span>
+          </div>
           <input type="text" class="form-control" placeholder="{{ t('Name') }}" name="invitedForm[name]" value="{{ req.body.invitedForm.name }}" required>
         </div>
 
 
         <div class="input-group">
-          <span class="input-group-addon"><i class="icon-lock"></i></span>
+          <div class="input-group-prepend">
+            <span class="input-group-text"><i class="icon-lock"></i></span>
+          </div>
           <input type="password" class="form-control" placeholder="{{ t('Password') }}" name="invitedForm[password]" required>
         </div>
 
-        <input type="hidden" name="_csrf" value="{{ csrf() }}">
-        <div class="input-group mt-5 m-b-20 d-flex justify-content-center">
-          <button type="submit" class="fcbtn btn btn-success btn-1b btn-register">
-            <span class="btn-label"><i class="icon-user-follow"></i></span>
-            {{ t('Create') }}
+        <div class="input-group justify-content-center d-flex mt-5">
+          <input type="hidden" name="_csrf" value="{{ csrf() }}">
+          <button type="submit" class="btn btn-fill login px-0 py-2" id="register">
+            <div class="eff"></div>
+            <span class="btn-label p-3"><i class="icon-user-follow"></i></span>
+            <span class="btn-label-text p-3">{{ t('Create') }}</span>
           </button>
         </div>
 

+ 0 - 1
src/server/views/layout-crowi/page.html

@@ -14,7 +14,6 @@
           <div id="tag-label"></div>
         {% endif %}
       </div>
-      {% include '../widget/header-buttons.html' %}
     </div>
   </header>
 

+ 0 - 1
src/server/views/layout-crowi/page_list.html

@@ -20,7 +20,6 @@
         <div id="tag-label"></div>
       {% endif %}
     </div>
-    {% include '../widget/header-buttons.html' %}
   </div>
 
 </header>

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

@@ -33,7 +33,7 @@
 </div>
 
   <div class="row page-list bg-white round-corner grw-pt-10px mb-5 {% if page.isPortal() %}mt-5{% endif %}">
-    <div class="col-xs-12">
+    <div class="col">
       {% include '../widget/page_list_and_timeline_kibela.html' %}
     </div>
   </div>

+ 1 - 1
src/server/views/layout-kibela/widget/header.html

@@ -11,7 +11,7 @@
         <div id="tag-label"></div>
       {% endif %}
     </div>
-    {% if page %} {% include '../../widget/header-buttons.html' %}
+    {% if page %}
 
     <ul class="authors hidden-sm hidden-xs text-nowrap grw-pt-10px">
       <li>

+ 9 - 9
src/server/views/login.html

@@ -141,7 +141,7 @@
 
               <div class="input-group justify-content-center d-flex mt-5">
                 <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                <button type="submit" class="btn fcbtn login px-0 py-2">
+                <button type="submit" class="btn btn-fill login px-0 py-2">
                   <div class="eff"></div>
                   <span class="btn-label p-3"><i class="icon-login"></i></span>
                   <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
@@ -167,7 +167,7 @@
                 {% if getConfig('crowi', 'security:passport-google:isEnabled') %}
                 <div class="input-group justify-content-center d-flex mt-5">
                   <form role="form" action="/passport/google" class="d-inline-flex flex-column">
-                    <button type="submit" class="btn fcbtn px-0 py-2" id="google">
+                    <button type="submit" class="btn btn-fill px-0 py-2" id="google">
                       <div class="eff"></div>
                       <span class="btn-label p-3"><i class="fa fa-google"></i></span>
                       <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
@@ -180,7 +180,7 @@
                 <div class="input-group justify-content-center d-flex mt-5">
                   <form role="form" action="/passport/github" class="d-inline-flex flex-column">
                     <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn fcbtn px-0 py-2" id="github">
+                    <button type="submit" class="btn btn-fill px-0 py-2" id="github">
                       <div class="eff"></div>
                       <span class="btn-label p-3"><i class="fa fa-github"></i></span>
                       <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
@@ -193,7 +193,7 @@
                 <div class="input-group justify-content-center d-flex mt-5">
                   <form role="form" action="/passport/facebook" class="d-inline-flex flex-column">
                     <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn fcbtn px-0 py-2" id="facebook">
+                    <button type="submit" class="btn btn-fill px-0 py-2" id="facebook">
                       <div class="eff"></div>
                       <span class="btn-label p-3"><i class="fa fa-facebook"></i></span>
                       <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
@@ -206,7 +206,7 @@
                 <div class="input-group justify-content-center d-flex mt-5">
                   <form role="form" action="/passport/twitter" class="d-inline-flex flex-column">
                     <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn fcbtn px-0 py-2" id="twitter">
+                    <button type="submit" class="btn btn-fill px-0 py-2" id="twitter">
                       <div class="eff"></div>
                       <span class="btn-label p-3"><i class="fa fa-twitter"></i></span>
                       <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
@@ -219,7 +219,7 @@
                 <div class="input-group justify-content-center d-flex mt-5">
                   <form role="form" action="/passport/oidc" class="d-inline-flex flex-column">
                     <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn fcbtn px-0 py-2" id="oidc">
+                    <button type="submit" class="btn btn-fill px-0 py-2" id="oidc">
                       <div class="eff"></div>
                       <span class="btn-label p-3"><i class="fa fa-openid"></i></span>
                       <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
@@ -232,7 +232,7 @@
                 <div class="input-group justify-content-center d-flex mt-5">
                   <form role="form" action="/passport/saml" class="d-inline-flex flex-column">
                     <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn fcbtn px-0 py-2" id="saml">
+                    <button type="submit" class="btn btn-fill px-0 py-2" id="saml">
                       <div class="eff"></div>
                       <span class="btn-label p-3"><i class="fa fa-key"></i></span>
                       <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
@@ -245,7 +245,7 @@
                 <div class="input-group justify-content-center d-flex mt-5">
                   <form role="form" action="/passport/basic" class="d-inline-flex flex-column">
                     <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn fcbtn px-0 py-2" id="basic">
+                    <button type="submit" class="btn btn-fill px-0 py-2" id="basic">
                       <div class="eff"></div>
                       <span class="btn-label p-3"><i class="fa fa-lock"></i></span>
                       <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
@@ -352,7 +352,7 @@
 
               <div class="input-group justify-content-center mt-5">
                 <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                <button type="submit" class="btn fcbtn px-0 py-2" id="register">
+                <button type="submit" class="btn btn-fill px-0 py-2" id="register">
                   <div class="eff"></div>
                   <span class="btn-label p-3"><i class="icon-user-follow"></i></span>
                   <span class="btn-label-text p-3">{{ t('Sign up') }}</span>

+ 3 - 3
src/server/views/modal/create_page.html

@@ -21,7 +21,7 @@
                   <input type="text" data-prefix="/{{ now|datetz('Y/m/d') }}/" class="page-today-input2 form-control" id="page-today-input2" name="" placeholder="{{ t('Input page name (optional)') }}">
                 </div>
                 <div class="create-page-button-container">
-                  <button type="submit" class="fcbtn btn btn-outline-primary rounded-pill btn-1b"><i class="icon-fw icon-doc"></i>{{ t('Create') }}</button>
+                  <button type="submit" class="btn btn-outline-primary rounded-pill"><i class="icon-fw icon-doc"></i>{{ t('Create') }}</button>
                 </div>
               </div>
             </fieldset>
@@ -41,7 +41,7 @@
                   {% endif %}
                 </div>
                 <div class="create-page-button-container">
-                  <button type="submit" class="fcbtn btn btn-outline-primary rounded-pill btn-1b"><i class="icon-fw icon-doc"></i>{{ t('Create') }}</button>
+                  <button type="submit" class="btn btn-outline-primary rounded-pill"><i class="icon-fw icon-doc"></i>{{ t('Create') }}</button>
                 </div>
               </div>
             </fieldset>
@@ -72,7 +72,7 @@
 
               </div>
               <div class="create-page-button-container my-auto">
-                <a id="link-to-template" href="{{ page.path || path }}" class="fcbtn btn btn-outline-primary rounded-pill btn-1b disabled">
+                <a id="link-to-template" href="{{ page.path || path }}" class="btn btn-outline-primary rounded-pill disabled">
                   <i class="icon-fw icon-doc"></i>
                   <span id="create-template-button-link">{{ t('Edit') }}</span>
                 </a>

+ 7 - 6
src/server/views/modal/put_back.html

@@ -5,19 +5,20 @@
       <form role="form" id="revert-delete-page-form" onsubmit="return false;">
 
         <div class="modal-header bg-info">
-          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
           <div class="modal-title"><i class="icon-action-undo"></i> {{ t('modal_putback.label.Put Back Page') }}</div>
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
         </div>
         <div class="modal-body">
           <div class="form-group">
             <label for="">Put back page:</label><br>
             <code>{{ page.path }}</code>
           </div>
-          <div class="checkbox checkbox-warning">
-            <input name="recursively" id="cbPutbackRecursively" value="1" type="checkbox" checked>
-            <label for="cbPutbackRecursively">{{ t('modal_putback.label.recursively') }}</label>
-            <p class="help-block"> {{ t('modal_putback.help.recursively', page.path) }}
-            </p>
+          <div class="custom-control custom-checkbox custom-checkbox-warning">
+            <input class="custom-control-input" name="recursively" id="cbPutbackRecursively" value="1" type="checkbox" checked>
+            <label class="custom-control-label" for="cbPutbackRecursively">
+              {{ t('modal_putback.label.recursively') }}
+              <p class="help-block mt-0">{{ t('modal_putback.help.recursively', page.path) }}</p>
+            </label>
           </div>
         </div>
         <div class="modal-footer">

+ 3 - 1
src/server/views/modal/rename.html

@@ -62,9 +62,11 @@
               <input type="hidden" name="path" value="{{ page.path }}">
               <input type="hidden" name="page_id" value="{{ page._id.toString() }}">
               <input type="hidden" name="revision_id" value="{{ page.revision._id.toString() }}">
-              <button type="submit" class="btn btn-primary">Rename</button>
             </div>
           </div>
+          <div class="d-flex justify-content-end">
+            <button type="submit" class="btn btn-primary">Rename</button>
+          </div>
         </div>
 
       </form>

+ 6 - 6
src/server/views/widget/forbidden_content.html

@@ -1,5 +1,5 @@
 <div class="row not-found-message-row mb-4">
-  <div class="col-md-12">
+  <div class="col-lg-12">
     <h2 class="text-muted">
       <i class="icon-ban" aria-hidden="true"></i>
       Forbidden
@@ -7,22 +7,22 @@
   </div>
 </div>
 
-<div id="content-main" class="content-main content-main-not-found page-list"
+<div id="content-main" class="content-main page-list"
   data-path="{{ path | preventXss }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   >
 
   <div class="row row-alerts">
-    <div class="col-xs-12">
-        <p class="alert alert-inverse alert-grant"> <!-- TODO remove inverse and grant -->
+    <div class="col-sm-12">
+        <p class="alert alert-primary py-3 px-4">
           <i class="icon-fw icon-lock" aria-hidden="true"></i> Browsing of this page is restricted
         </p>
     </div>
   </div>
 
-  <ul class="nav nav-tabs hidden-print">
+  <ul class="nav nav-tabs hidden-print" role="tablist">
     <li class="nav-item grw-nav-main-left-tab">
-      <a class="nav-link active" href="#revision-body" data-toggle="tab">
+      <a class="nav-link active" role="tab" href="#revision-body" data-toggle="tab">
         <i class="icon-notebook"></i> List
       </a>
     </li>

+ 0 - 6
src/server/views/widget/header-button-bookmark.html

@@ -1,6 +0,0 @@
-{# This widget will be rendered by React #}
-{% if not size == null %}
-  <span id="bookmark-button-{{size}}"></span>
-{% else %}
-  <span id="bookmark-button"></span>
-{% endif %}

+ 0 - 6
src/server/views/widget/header-button-like.html

@@ -1,6 +0,0 @@
-{# This widget will be rendered by React #}
-{% if not size == null %}
-  <span id="like-button-{{size}}" data-liked="{% if page.isLiked(user) %}true{% else %}false{% endif %}"></span>
-{% else %}
-  <span id="like-button" data-liked="{% if page.isLiked(user) %}true{% else %}false{% endif %}"></span>
-{% endif %}

+ 0 - 13
src/server/views/widget/header-buttons-lg.html

@@ -1,13 +0,0 @@
-{% if page %}
-  {% set opts = { size: 'lg' }  %}
-  <div>
-    {% if user %}
-      {% include 'header-button-like.html' with opts %}
-    {% endif %}
-  </div>
-  <div class="ml-1">
-    {% if user %}
-      {% include 'header-button-bookmark.html' with opts %}
-    {% endif %}
-  </div>
-{% endif %}

+ 0 - 12
src/server/views/widget/header-buttons.html

@@ -1,12 +0,0 @@
-{% if page %}
-  <div>
-    {% if user %}
-      {% include 'header-button-like.html' %}
-    {% endif %}
-  </div>
-  <div class="ml-1">
-    {% if user %}
-      {% include 'header-button-bookmark.html' %}
-    {% endif %}
-  </div>
-{% endif %}

+ 1 - 1
src/server/views/widget/modal/page-api-error-messages.html

@@ -6,7 +6,7 @@
     <strong><i class="icon-fw icon-ban"></i>{{ t('page_api_error.user_not_admin') }}</strong>
   </span>
   <span class="text-danger msg msg-already_exists">
-    <strong><i class="icon-fw icon-ban"></i>{{ t('page_api_error.already_exists') }}</strong>
+    <strong><i class="icon-fw icon-ban"></i>{{ t('page_api_error.already_exists') }}</strong><br>
     <small id="linkToNewPage"></small>
   </span>
   <span class="text-warning msg msg-outdated">

+ 1 - 1
src/server/views/widget/not_creatable_content.html

@@ -7,7 +7,7 @@
   </div>
 </div>
 
-<div id="content-main" class="content-main content-main-not-found page-list"
+<div id="content-main" class="content-main page-list"
   data-path="{{ path | preventXss }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   >

+ 1 - 1
src/server/views/widget/not_found_content.html

@@ -7,7 +7,7 @@
   </div>
 </div>
 
-<div id="content-main" class="content-main content-main-not-found page-list"
+<div id="content-main" class="content-main page-list"
   data-path="{{ path | preventXss }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   {% if templateTags %}

+ 3 - 3
src/server/views/widget/not_found_tabs.html

@@ -1,6 +1,6 @@
-<ul class="nav nav-tabs hidden-print">
+<ul class="nav nav-tabs hidden-print" role="tablist">
   <li class="nav-item grw-nav-main-left-tab">
-    <a class="nav-link active" href="#revision-body" data-toggle="tab">
+    <a class="nav-link active" role="tab" href="#revision-body" data-toggle="tab">
       <i class="icon-notebook"></i> List
     </a>
   </li>
@@ -8,7 +8,7 @@
   {% if !isTrashPage() and !page.isDeleted() %}
   <li class="nav-item grw-nav-main-left-tab">
     <a
-      {% if user %} href="#edit" data-toggle="tab" class="edit-button" {% endif %}
+      {% if user %} href="#edit" role="tab" 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('Create') }}

+ 1 - 1
src/server/views/widget/page_attachments.html

@@ -1,5 +1,5 @@
 <div class="row page-attachments-row hidden-print">
-  <div class="col-xs-12">
+  <div class="col-12">
     <div class="mt-4 mb-4">
       <div class="page-attachments" id="page-attachment"></div>
 

+ 1 - 0
src/server/views/widget/page_content.html

@@ -9,6 +9,7 @@
   data-page-revision-id-hackmd-synced="{% if revisionHackmdSynced %}{{ revisionHackmdSynced.toString() }}{% endif %}"
   data-page-id-on-hackmd="{% if pageIdOnHackmd %}{{ pageIdOnHackmd.toString() }}{% endif %}"
   data-page-has-draft-on-hackmd="{% if hasDraftOnHackmd %}{{ hasDraftOnHackmd.toString() }}{% endif %}"
+  data-page-is-liked="{% if page.isLiked(user) %}true{% else %}false{% endif %}"
   data-page-is-seen="{% if page and page.isSeenUser(user) %}1{% else %}0{% endif %}"
   data-slack-channels="{{ slack|default('') }}"
   data-page-created-at="{% if page %}{{ page.createdAt|datetz('Y/m/d H:i:s') }}{% endif %}"