Просмотр исходного кода

Merge branch 'support/apply-bootstrap4' into imprv/admin-navigation-responsive

ryohek 6 лет назад
Родитель
Сommit
9c5e6d32c7
43 измененных файлов с 382 добавлено и 318 удалено
  1. 10 1
      CHANGES.md
  2. 2 2
      bin/github-actions/update-readme.sh
  3. 4 6
      docker/README.md
  4. 8 6
      resource/locales/en-US/sandbox-bootstrap4.md
  5. 9 0
      resource/locales/en-US/translation.json
  6. 8 6
      resource/locales/ja/sandbox-bootstrap4.md
  7. 9 0
      resource/locales/ja/translation.json
  8. 5 3
      src/client/js/app.jsx
  9. 2 2
      src/client/js/components/Admin/Security/BasicSecuritySetting.jsx
  10. 2 2
      src/client/js/components/Admin/Security/GitHubSecuritySetting.jsx
  11. 2 2
      src/client/js/components/Admin/Security/GoogleSecuritySetting.jsx
  12. 76 49
      src/client/js/components/Admin/Security/SecuritySetting.jsx
  13. 2 2
      src/client/js/components/Admin/Security/TwitterSecuritySetting.jsx
  14. 4 4
      src/client/js/components/Admin/UserManagement.jsx
  15. 1 1
      src/client/js/components/Admin/Users/ExternalAccountTable.jsx
  16. 7 3
      src/client/js/components/Admin/Users/UserInviteModal.jsx
  17. 112 110
      src/client/js/components/Admin/Users/UserTable.jsx
  18. 2 2
      src/client/js/components/HeaderSearchBox.jsx
  19. 0 30
      src/client/js/components/Page/PagePath.jsx
  20. 1 1
      src/client/js/components/PageEditorByHackmd.jsx
  21. 2 2
      src/client/js/components/PageList/PageListMeta.jsx
  22. 19 7
      src/client/js/components/StaffCredit/StaffCredit.jsx
  23. 1 1
      src/client/js/components/TagsList.jsx
  24. 5 1
      src/client/styles/scss/_admin.scss
  25. 6 2
      src/client/styles/scss/_layout_kibela.scss
  26. 1 1
      src/client/styles/scss/_on-edit.scss
  27. 6 36
      src/client/styles/scss/_staff_credit.scss
  28. 4 0
      src/client/styles/scss/_wiki.scss
  29. 19 0
      src/client/styles/scss/theme/_apply-colors-dark.scss
  30. 5 5
      src/client/styles/scss/theme/kibela.scss
  31. 11 5
      src/migrations/20191102223901-drop-pages-indices.js
  32. 3 2
      src/server/crowi/express-init.js
  33. 1 1
      src/server/routes/installer.js
  34. 13 14
      src/server/service/import.js
  35. 1 1
      src/server/views/admin/Users_reserve.html
  36. 1 1
      src/server/views/layout-growi/page_list.html
  37. 4 0
      src/server/views/layout-growi/user_page.html
  38. 2 1
      src/server/views/layout-kibela/base/layout.html
  39. 1 1
      src/server/views/layout-kibela/page_list.html
  40. 6 2
      src/server/views/layout-kibela/user_page.html
  41. 2 0
      src/server/views/modal/rename.html
  42. 1 1
      src/server/views/widget/page_content.html
  43. 2 2
      src/server/views/widget/page_list.html

+ 10 - 1
CHANGES.md

@@ -5,10 +5,19 @@
 * Support: Upgrade libs
     * bootstrap
 
-## v3.7.5
+## v3.7.6
 
 *
 
+## v3.7.5
+
+* Fix: Draw.io diagrams rendered twice
+* Fix: Behavior of password reset modal is strange
+* Fix: Import GROWI Archive doesn't restore some data correctly
+* Fix: Attachments list on root page and users top pages
+* Fix: Trash page shouldn't be editable
+* Fix: Rendering Timeline on /trash
+
 ## v3.7.4
 
 * Fix: Broken by displaying user image

+ 2 - 2
bin/github-actions/update-readme.sh

@@ -2,5 +2,5 @@
 
 cd docker
 
-sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`3\.6\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/docker\/Dockerfile.\+\)$/\1${RELEASE_VERSION}\2\3${RELEASE_VERSION}\4/" README.md
-sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`3\.6-nocdn\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/docker\/Dockerfile.\+\)$/\1${RELEASE_VERSION}-nocdn\2\3${RELEASE_VERSION}\4/" README.md
+sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`3\.7\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/docker\/Dockerfile.\+\)$/\1${RELEASE_VERSION}\2\3${RELEASE_VERSION}\4/" README.md
+sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`3\.7-nocdn\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/docker\/Dockerfile.\+\)$/\1${RELEASE_VERSION}-nocdn\2\3${RELEASE_VERSION}\4/" README.md

+ 4 - 6
docker/README.md

@@ -10,12 +10,10 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`3.6.0`, `3.6`, `3`, `latest`, (Dockerfile)](https://github.com/weseek/growi/blob/v3.6.0/docker/Dockerfile)
-* [`3.6.0-nocdn`, `3.6-nocdn`, `3-nocdn`, `latest-nocdn`, (Dockerfile)](https://github.com/weseek/growi/blob/v3.6.0/docker/Dockerfile)
-* [`3.5.25`, `3.5`, `3`, (Dockerfile)](https://github.com/weseek/growi-docker/blob/v3.5.25/Dockerfile)
-* [`3.5.25-nocdn`, `3.5-nocdn`, `3-nocdn` (Dockerfile)](https://github.com/weseek/growi-docker/blob/v3.5.25/nocdn/Dockerfile)
-* [`3.4.7`, `3.4`, `3`, (Dockerfile)](https://github.com/weseek/growi-docker/blob/v3.4.7/Dockerfile)
-* [`3.4.7-nocdn`, `3.4-nocdn`, `3-nocdn` (Dockerfile)](https://github.com/weseek/growi-docker/blob/v3.4.7/nocdn/Dockerfile)
+* [`3.7.0`, `3.7`, `3`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v3.7.0/docker/Dockerfile)
+* [`3.7.0-nocdn`, `3.7-nocdn`, `3-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v3.7.0/docker/Dockerfile)
+* [`3.6.10`, `3.6` (Dockerfile)](https://github.com/weseek/growi/blob/v3.6.10/docker/Dockerfile)
+* [`3.6.10-nocdn`, `3.6-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v3.6.10/docker/Dockerfile)
 
 
 What is GROWI?

+ 8 - 6
resource/locales/en-US/sandbox-bootstrap3.md → resource/locales/en-US/sandbox-bootstrap4.md

@@ -1,11 +1,13 @@
 # Labels
 
-<span class="label label-default">Default</span>
-<span class="label label-primary">Primary</span>
-<span class="label label-success">Success</span>
-<span class="label label-info">Info</span>
-<span class="label label-warning">Warning</span>
-<span class="label label-danger">Danger</span>
+<span class="badge badge-primary">Primary</span>
+<span class="badge badge-secondary">Secondary</span>
+<span class="badge badge-success">Success</span>
+<span class="badge badge-info">Info</span>
+<span class="badge badge-warning">Warning</span>
+<span class="badge badge-danger">Danger</span>
+<span class="badge badge-light">Light</span>
+<span class="badge badge-dark">Dark</span>
 
 # Alerts
 

+ 9 - 0
resource/locales/en-US/translation.json

@@ -111,6 +111,13 @@
   "Specified users only": "Specified users only",
   "Just me": "Just me",
   "Only inside the group": "Only inside the group",
+  "page_list_and_search_results": "Page list / Search results",
+  "scope_of_page_disclosure": "Scope of page disclosure",
+  "set_point": "Set point",
+  "always_displayed": "Always displayed",
+  "always_hidden": "Always hidden",
+  "displayed_or_hidden": "Displayed / Hidden",
+  "page_access_and_delete_rights": "Page access / Delete rights",
   "Reselect the group": "Reselect the group",
   "Shareable link": "Shareable link",
   "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
@@ -182,9 +189,11 @@
   "Update API Token": "Update API Token",
   "header_search_box": {
     "label": {
+      "All pages": "All pages",
       "This tree": "This tree"
     },
     "item_label": {
+      "All pages": "All pages",
       "This tree": "Only children of this tree"
     }
   },

+ 8 - 6
resource/locales/ja/sandbox-bootstrap3.md → resource/locales/ja/sandbox-bootstrap4.md

@@ -1,11 +1,13 @@
 # Labels
 
-<span class="label label-default">Default</span>
-<span class="label label-primary">Primary</span>
-<span class="label label-success">Success</span>
-<span class="label label-info">Info</span>
-<span class="label label-warning">Warning</span>
-<span class="label label-danger">Danger</span>
+<span class="badge badge-primary">Primary</span>
+<span class="badge badge-secondary">Secondary</span>
+<span class="badge badge-success">Success</span>
+<span class="badge badge-info">Info</span>
+<span class="badge badge-warning">Warning</span>
+<span class="badge badge-danger">Danger</span>
+<span class="badge badge-light">Light</span>
+<span class="badge badge-dark">Dark</span>
 
 # Alerts
 

+ 9 - 0
resource/locales/ja/translation.json

@@ -110,6 +110,13 @@
   "Specified users": "特定ユーザーのみ",
   "Just me": "自分のみ",
   "Only inside the group": "特定グループのみ",
+  "page_list_and_search_results": "ページリスト・検索結果",
+  "scope_of_page_disclosure": "ページの公開範囲",
+  "set_point": "設定値",
+  "always_displayed": "表示 (固定)",
+  "always_hidden": "非表示 (固定)",
+  "displayed_or_hidden": "表示 / 非表示",
+  "page_access_and_delete_rights": "ページの閲覧・削除権限",
   "Reselect the group": "グループの再選択",
   "Shareable link": "このページの共有用URL",
   "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
@@ -181,9 +188,11 @@
   "Update API Token": "API Tokenを更新",
   "header_search_box": {
     "label": {
+      "All pages": "全てのページ",
       "This tree": "この階層"
     },
     "item_label": {
+      "All pages": "全てのページ",
       "This tree": "この階層下の子ページのみ"
     }
   },

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

@@ -75,8 +75,8 @@ Object.assign(componentMappings, {
   'page-status-alert': <PageStatusAlert />,
   'save-page-controls': <SavePageControls />,
 
-  'user-created-list': <RecentCreated />,
-  'user-draft-list': <MyDraftList />,
+  'page-timeline': <PageTimeline />,
+
   'personal-setting': <PersonalSettings crowi={personalContainer} />,
 });
 
@@ -86,13 +86,15 @@ if (pageContainer.state.pageId != null) {
     'page-editor-with-hackmd': <PageEditorByHackmd />,
     'page-comments-list': <PageComments />,
     'page-attachment': <PageAttachment />,
-    'page-timeline': <PageTimeline />,
     'page-comment-write': <CommentEditorLazyRenderer />,
     'revision-toc': <TableOfContents />,
     'seen-user-list': <UserPictureList userIds={pageContainer.state.seenUserIds} />,
     'liker-list': <UserPictureList userIds={pageContainer.state.likerUserIds} />,
     'rename-page-name-input': <PagePathAutoComplete crowi={appContainer} initializedPath={pageContainer.state.path} />,
     'duplicate-page-name-input': <PagePathAutoComplete crowi={appContainer} initializedPath={pageContainer.state.path} />,
+
+    'user-created-list': <RecentCreated />,
+    'user-draft-list': <MyDraftList />,
   });
 }
 if (pageContainer.state.path != null) {

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

@@ -67,8 +67,8 @@ class BasicSecurityManagement extends React.Component {
         </div>
         )}
 
-        <div className="row mb-5">
-          <div className="col-md-6 offset-md-3">
+        <div className="form-group row">
+          <div className="col-6 offset-3">
             <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isBasicEnabled"

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

@@ -68,8 +68,8 @@ class GitHubSecurityManagement extends React.Component {
           </div>
         )}
 
-        <div className="row mb-5">
-          <div className="col-12 offset-md-3 col-md-6">
+        <div className="form-group row">
+          <div className="col-6 offset-3">
             <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isGitHubEnabled"

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

@@ -68,8 +68,8 @@ class GoogleSecurityManagement extends React.Component {
           </div>
         )}
 
-        <div className="row mb-5 mt-3">
-          <div className="col-12 offset-md-3 col-md-6">
+        <div className="form-group row">
+          <div className="col-6 offset-3">
             <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isGoogleEnabled"

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

@@ -57,22 +57,82 @@ class SecuritySetting extends React.Component {
           <p>{t('Error occurred')} : {this.state.retrieveError}</p>
         </div>
           )}
-        <div className="row mb-5">
-          <div className="col-md-3 text-md-right text-nowrap py-2 mr-md-5">
+
+        <h4 className="mt-4">
+          { t('page_list_and_search_results') }
+        </h4>
+        <table className="table table-bordered col-lg-9 mb-5">
+          <thead>
+            <tr>
+              <th scope="col">{ t('scope_of_page_disclosure') }</th>
+              <th scope="col">{ t('set_point') }</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <th scope="row">{ t('Public') }</th>
+              <td>{ t('always_displayed') }</td>
+            </tr>
+            <tr>
+              <th scope="row">{ t('Anyone with the link') }</th>
+              <td>{ t('always_hidden') }</td>
+            </tr>
+            <tr>
+              <th scope="row">{ t('Just me') }</th>
+              <td>
+                <div className="custom-control custom-switch custom-checkbox-success">
+                  <input
+                    type="checkbox"
+                    className="custom-control-input"
+                    id="isShowRestrictedByOwner"
+                    checked={adminGeneralSecurityContainer.state.isShowRestrictedByOwner}
+                    onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByOwner() }}
+                  />
+                  <label className="custom-control-label" htmlFor="isShowRestrictedByOwner">
+                    {t('displayed_or_hidden')}
+                  </label>
+                </div>
+              </td>
+            </tr>
+            <tr>
+              <th scope="row">{ t('Only inside the group') }</th>
+              <td>
+                <div className="custom-control custom-switch custom-checkbox-success">
+                  <input
+                    type="checkbox"
+                    className="custom-control-input"
+                    id="isShowRestrictedByGroup"
+                    checked={adminGeneralSecurityContainer.state.isShowRestrictedByGroup}
+                    onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByGroup() }}
+                  />
+                  <label className="custom-control-label" htmlFor="isShowRestrictedByGroup">
+                    {t('displayed_or_hidden')}
+                  </label>
+                </div>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+        <h4>{t('page_access_and_delete_rights')}</h4>
+        <div className="row mb-4">
+          <div className="col-md-3 text-md-right py-2">
             <strong>{t('security_setting.Guest Users Access')}</strong>
           </div>
           <div className="col-md-6 ml-md-5">
             <div className="dropdown">
               <button
-                className={`btn btn-outline-secondary dropdown-toggle ${adminGeneralSecurityContainer.isWikiModeForced && 'disabled'}`}
+                className={`btn btn-outline-secondary dropdown-toggle text-right col-12
+                            col-md-auto ${adminGeneralSecurityContainer.isWikiModeForced && 'disabled'}`}
                 type="button"
                 id="dropdownMenuButton"
                 data-toggle="dropdown"
                 aria-haspopup="true"
                 aria-expanded="true"
               >
-                {currentRestrictGuestMode === 'Deny' && t('security_setting.guest_mode.deny')}
-                {currentRestrictGuestMode === 'Readonly' && t('security_setting.guest_mode.readonly')}
+                <span className="float-left">
+                  {currentRestrictGuestMode === 'Deny' && t('security_setting.guest_mode.deny')}
+                  {currentRestrictGuestMode === 'Readonly' && t('security_setting.guest_mode.readonly')}
+                </span>
               </button>
               <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
                 <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Deny') }}>
@@ -86,7 +146,7 @@ class SecuritySetting extends React.Component {
           </div>
         </div>
         {adminGeneralSecurityContainer.isWikiModeForced && (
-        <div className="row mb-5">
+        <div className="row mb-4">
           <div className="col-xs-offset-3 col-xs-6 text-left">
             <p className="alert alert-warning mt-2 text-left">
               <i className="icon-exclamation icon-fw">
@@ -101,60 +161,27 @@ class SecuritySetting extends React.Component {
           </div>
         </div>
           )}
-        <div className="row mb-5">
-          <strong className="col-md-3 text-md-right text-nowrap mb-2 mr-md-5" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_1') }} />
-          <div className="col-md-6">
-            <div className="custom-control custom-checkbox custom-checkbox-success ml-md-5">
-              <input
-                type="checkbox"
-                className="custom-control-input"
-                id="isShowRestrictedByOwner"
-                checked={adminGeneralSecurityContainer.state.isShowRestrictedByOwner}
-                onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByOwner() }}
-              />
-              <label className="custom-control-label" htmlFor="isShowRestrictedByOwner">
-                {t('security_setting.page_listing_1_desc')}
-              </label>
-            </div>
-          </div>
-        </div>
-
-        <div className="row mb-5">
-          <strong className="col-md-3 text-md-right text-nowrap mr-md-5 mb-2" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_2') }} />
-          <div className="col-md-6 ml-md-5">
-            <div className="custom-control custom-checkbox custom-checkbox-success">
-              <input
-                type="checkbox"
-                className="custom-control-input"
-                id="isShowRestrictedByGroup"
-                checked={adminGeneralSecurityContainer.state.isShowRestrictedByGroup}
-                onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByGroup() }}
-              />
-              <label className="custom-control-label" htmlFor="isShowRestrictedByGroup">
-                {t('security_setting.page_listing_2_desc')}
-              </label>
-            </div>
-          </div>
-        </div>
 
-        <div className="row mb-5">
-          <div className="col-md-3 text-md-right mr-md-5 mb-2">
+        <div className="row mb-4">
+          <div className="col-md-3 text-md-right mb-2">
             <strong>{t('security_setting.complete_deletion')}</strong>
           </div>
           <div className="col-md-6 ml-md-5">
             <div className="dropdown">
               <button
-                className="btn btn-outline-secondary dropdown-toggle"
+                className="btn btn-outline-secondary dropdown-toggle text-right col-12 col-md-auto"
                 type="button"
                 id="dropdownMenuButton"
                 data-toggle="dropdown"
                 aria-haspopup="true"
                 aria-expanded="true"
               >
-                {currentPageCompleteDeletionAuthority === 'anyOne' && t('security_setting.anyone')}
-                {currentPageCompleteDeletionAuthority === 'adminOnly' && t('security_setting.admin_only')}
-                {(currentPageCompleteDeletionAuthority === 'adminAndAuthor' || currentPageCompleteDeletionAuthority == null)
-                    && t('security_setting.admin_and_author')}
+                <span className="float-left">
+                  {currentPageCompleteDeletionAuthority === 'anyOne' && t('security_setting.anyone')}
+                  {currentPageCompleteDeletionAuthority === 'adminOnly' && t('security_setting.admin_only')}
+                  {(currentPageCompleteDeletionAuthority === 'adminAndAuthor' || currentPageCompleteDeletionAuthority == null)
+                      && t('security_setting.admin_and_author')}
+                </span>
               </button>
               <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
                 <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('anyOne') }}>
@@ -174,7 +201,7 @@ class SecuritySetting extends React.Component {
           </div>
         </div>
         <div className="row my-3">
-          <div className="offset-3 col-5">
+          <div className="text-center text-md-left offset-md-3 col-md-5">
             <button type="button" className="btn btn-primary" disabled={this.state.retrieveError != null} onClick={this.putSecuritySetting}>
               {t('Update')}
             </button>

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

@@ -68,8 +68,8 @@ class TwitterSecurityManagement extends React.Component {
           </div>
         )}
 
-        <div className="row mb-5">
-          <div className="offset-md-3 col-md-6">
+        <div className="form-group row">
+          <div className="col-6 offset-3">
             <div className="custom-control custom-switch custom-checkbox-success">
               <input
                 id="isTwitterEnabled"

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

@@ -159,8 +159,8 @@ class UserManagement extends React.Component {
         <h2>{t('User_Management')}</h2>
         <div className="border-top border-bottom">
 
-          <div className="d-flex justify-content-start align-items-center my-2">
-            <div className="d-flex align-items-baseline">
+          <div className="row d-flex justify-content-start align-items-center my-2">
+            <div className="col-md-4 d-flex align-items-center my-2">
               <i className="icon-magnifier mr-1"></i>
               <span className="search-typeahead">
                 <input
@@ -172,7 +172,7 @@ class UserManagement extends React.Component {
               </span>
             </div>
 
-            <div className="mx-5">
+            <div className="col-md-6 my-2">
               <div className="form-inline">
                 {this.renderCheckbox('all', 'All', 'primary')}
                 {this.renderCheckbox('registered', 'Approval Pending', 'info')}
@@ -188,7 +188,7 @@ class UserManagement extends React.Component {
               </div>
             </div>
 
-            <div>
+            <div className="col-md-2 my-2">
               <button
                 type="button"
                 className="btn btn-outline-secondary btn-sm"

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

@@ -80,7 +80,7 @@ class ExternalAccountTable extends React.Component {
                   <td>
                     {ea.user.password
                       ? (
-                        <span className="label label-info">
+                        <span className="badge badge-info">
                           {t('admin:user_management.set')}
                         </span>
                       )

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

@@ -131,9 +131,13 @@ class UserInviteModal extends React.Component {
         {userList.map((user) => {
           const copyText = `Email:${user.email} Password:${user.password} `;
           return (
-            <CopyToClipboard key={user.email} text={copyText} onCopy={this.showToaster}>
-              <li key={user.email} className="btn">Email: <strong className="mr-3">{user.email}</strong> Password: <strong>{user.password}</strong></li>
-            </CopyToClipboard>
+            <div className="my-1">
+              <CopyToClipboard key={user.email} text={copyText} onCopy={this.showToaster}>
+                <li key={user.email} className="btn btn-outline-secondary">
+                Email: <strong className="mr-3">{user.email}</strong> Password: <strong>{user.password}</strong>
+                </li>
+              </CopyToClipboard>
+            </div>
           );
         })}
       </ul>

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

@@ -89,122 +89,124 @@ class UserTable extends React.Component {
 
     return (
       <Fragment>
-        <table className="table table-default table-bordered table-user-list">
-          <thead>
-            <tr>
-              <th width="100px">#</th>
-              <th>
-                <div className="d-flex align-items-center">
-                  <div className="mr-3">
-                    {t('status')}
+        <div className="table-responsive text-nowrap">
+          <table className="table table-default table-bordered table-user-list">
+            <thead>
+              <tr>
+                <th width="100px">#</th>
+                <th>
+                  <div className="d-flex align-items-center">
+                    <div className="mr-3">
+                      {t('status')}
+                    </div>
+                    <SortIcons
+                      isSelected={adminUsersContainer.state.sort === 'status'}
+                      isAsc={isCurrentSortOrderAsc}
+                      onClick={(sortOrder) => {
+                        this.sortIconsClickedHandler('status', sortOrder);
+                      }}
+                    />
                   </div>
-                  <SortIcons
-                    isSelected={adminUsersContainer.state.sort === 'status'}
-                    isAsc={isCurrentSortOrderAsc}
-                    onClick={(sortOrder) => {
-                      this.sortIconsClickedHandler('status', sortOrder);
-                    }}
-                  />
-                </div>
-              </th>
-              <th>
-                <div className="d-flex align-items-center">
-                  <div className="mr-3">
-                    <code>username</code>
+                </th>
+                <th>
+                  <div className="d-flex align-items-center">
+                    <div className="mr-3">
+                      <code>username</code>
+                    </div>
+                    <SortIcons
+                      isSelected={adminUsersContainer.state.sort === 'username'}
+                      isAsc={isCurrentSortOrderAsc}
+                      onClick={(sortOrder) => {
+                        this.sortIconsClickedHandler('username', sortOrder);
+                      }}
+                    />
                   </div>
-                  <SortIcons
-                    isSelected={adminUsersContainer.state.sort === 'username'}
-                    isAsc={isCurrentSortOrderAsc}
-                    onClick={(sortOrder) => {
-                      this.sortIconsClickedHandler('username', sortOrder);
-                    }}
-                  />
-                </div>
-              </th>
-              <th>
-                <div className="d-flex align-items-center">
-                  <div className="mr-3">
-                    {t('Name')}
+                </th>
+                <th>
+                  <div className="d-flex align-items-center">
+                    <div className="mr-3">
+                      {t('Name')}
+                    </div>
+                    <SortIcons
+                      isSelected={adminUsersContainer.state.sort === 'name'}
+                      isAsc={isCurrentSortOrderAsc}
+                      onClick={(sortOrder) => {
+                        this.sortIconsClickedHandler('name', sortOrder);
+                      }}
+                    />
                   </div>
-                  <SortIcons
-                    isSelected={adminUsersContainer.state.sort === 'name'}
-                    isAsc={isCurrentSortOrderAsc}
-                    onClick={(sortOrder) => {
-                      this.sortIconsClickedHandler('name', sortOrder);
-                    }}
-                  />
-                </div>
-              </th>
-              <th>
-                <div className="d-flex align-items-center">
-                  <div className="mr-3">
-                    {t('Email')}
+                </th>
+                <th>
+                  <div className="d-flex align-items-center">
+                    <div className="mr-3">
+                      {t('Email')}
+                    </div>
+                    <SortIcons
+                      isSelected={adminUsersContainer.state.sort === 'email'}
+                      isAsc={isCurrentSortOrderAsc}
+                      onClick={(sortOrder) => {
+                        this.sortIconsClickedHandler('email', sortOrder);
+                      }}
+                    />
                   </div>
-                  <SortIcons
-                    isSelected={adminUsersContainer.state.sort === 'email'}
-                    isAsc={isCurrentSortOrderAsc}
-                    onClick={(sortOrder) => {
-                      this.sortIconsClickedHandler('email', sortOrder);
-                    }}
-                  />
-                </div>
-              </th>
-              <th width="100px">
-                <div className="d-flex align-items-center">
-                  <div className="mr-3">
-                    {t('Created')}
+                </th>
+                <th width="100px">
+                  <div className="d-flex align-items-center">
+                    <div className="mr-3">
+                      {t('Created')}
+                    </div>
+                    <SortIcons
+                      isSelected={adminUsersContainer.state.sort === 'createdAt'}
+                      isAsc={isCurrentSortOrderAsc}
+                      onClick={(sortOrder) => {
+                        this.sortIconsClickedHandler('createdAt', sortOrder);
+                      }}
+                    />
                   </div>
-                  <SortIcons
-                    isSelected={adminUsersContainer.state.sort === 'createdAt'}
-                    isAsc={isCurrentSortOrderAsc}
-                    onClick={(sortOrder) => {
-                      this.sortIconsClickedHandler('createdAt', sortOrder);
-                    }}
-                  />
-                </div>
-              </th>
-              <th width="150px">
-                <div className="d-flex align-items-center">
-                  <div className="mr-3">
-                    {t('Last_Login')}
+                </th>
+                <th width="150px">
+                  <div className="d-flex align-items-center">
+                    <div className="mr-3">
+                      {t('Last_Login')}
+                    </div>
+                    <SortIcons
+                      isSelected={adminUsersContainer.state.sort === 'lastLoginAt'}
+                      isAsc={isCurrentSortOrderAsc}
+                      onClick={(sortOrder) => {
+                        this.sortIconsClickedHandler('lastLoginAt', sortOrder);
+                      }}
+                    />
                   </div>
-                  <SortIcons
-                    isSelected={adminUsersContainer.state.sort === 'lastLoginAt'}
-                    isAsc={isCurrentSortOrderAsc}
-                    onClick={(sortOrder) => {
-                      this.sortIconsClickedHandler('lastLoginAt', sortOrder);
-                    }}
-                  />
-                </div>
-              </th>
-              <th width="70px"></th>
-            </tr>
-          </thead>
-          <tbody>
-            {adminUsersContainer.state.users.map((user) => {
-              return (
-                <tr key={user._id}>
-                  <td>
-                    <UserPicture user={user} className="picture rounded-circle" />
-                  </td>
-                  <td>{this.getUserStatusLabel(user.status)} {this.getUserAdminLabel(user.admin)}</td>
-                  <td>
-                    <strong>{user.username}</strong>
-                  </td>
-                  <td>{user.name}</td>
-                  <td>{user.email}</td>
-                  <td>{dateFnsFormat(new Date(user.createdAt), 'yyyy-MM-dd')}</td>
-                  <td>
-                    {user.lastLoginAt && <span>{dateFnsFormat(new Date(user.lastLoginAt), 'yyyy-MM-dd HH:mm')}</span>}
-                  </td>
-                  <td>
-                    <UserMenu user={user} />
-                  </td>
-                </tr>
-              );
-            })}
-          </tbody>
-        </table>
+                </th>
+                <th width="70px"></th>
+              </tr>
+            </thead>
+            <tbody>
+              {adminUsersContainer.state.users.map((user) => {
+                return (
+                  <tr key={user._id}>
+                    <td>
+                      <UserPicture user={user} className="picture rounded-circle" />
+                    </td>
+                    <td>{this.getUserStatusLabel(user.status)} {this.getUserAdminLabel(user.admin)}</td>
+                    <td>
+                      <strong>{user.username}</strong>
+                    </td>
+                    <td>{user.name}</td>
+                    <td>{user.email}</td>
+                    <td>{dateFnsFormat(new Date(user.createdAt), 'yyyy-MM-dd')}</td>
+                    <td>
+                      {user.lastLoginAt && <span>{dateFnsFormat(new Date(user.lastLoginAt), 'yyyy-MM-dd HH:mm')}</span>}
+                    </td>
+                    <td>
+                      <UserMenu user={user} />
+                    </td>
+                  </tr>
+                );
+              })}
+            </tbody>
+          </table>
+        </div>
       </Fragment>
     );
   }

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

@@ -60,7 +60,7 @@ class HeaderSearchBox extends React.Component {
     const { t, appContainer } = this.props;
     const scopeLabel = this.state.isScopeChildren
       ? t('header_search_box.label.This tree')
-      : 'All pages';
+      : t('header_search_box.label.All pages');
 
     const config = appContainer.getConfig();
     const isReachable = config.isSearchServiceReachable;
@@ -73,7 +73,7 @@ class HeaderSearchBox extends React.Component {
               {scopeLabel}
             </button>
             <div className="dropdown-menu">
-              <button className="dropdown-item" type="button" onClick={this.onClickAllPages}>All pages</button>
+              <button className="dropdown-item" type="button" onClick={this.onClickAllPages}>{ t('header_search_box.item_label.All pages') }</button>
               <button className="dropdown-item" type="button" onClick={this.onClickChildren}>{ t('header_search_box.item_label.This tree') }</button>
             </div>
           </div>

+ 0 - 30
src/client/js/components/Page/PagePath.jsx

@@ -1,30 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PagePath extends React.Component {
-
-  linkPath(path) {
-    return path;
-  }
-
-  render() {
-    const page = this.props.page;
-    const shortPath = this.getShortPath(page.path);
-    const pathPrefix = page.path.replace(new RegExp(`${shortPath}(/)?$`), '');
-
-    return (
-      <span className="page-path">
-        {pathPrefix}
-        <strong>{shortPath}</strong>
-      </span>
-    );
-  }
-
-}
-
-PagePath.propTypes = {
-  page: PropTypes.object.isRequired,
-};
-
-PagePath.defaultProps = {
-};

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

@@ -262,7 +262,7 @@ class PageEditorByHackmd extends React.Component {
               <div className="card-header bg-warning"><i className="icon-fw icon-info"></i> {t('hackmd.draft_outdated')}</div>
               <div className="card-body text-center">
                 {t('hackmd.based_on_revision')}&nbsp;
-                <a href={`?revision=${revisionIdHackmdSynced}`}><span className="label label-default">{revisionIdHackmdSynced.substr(-8)}</span></a>
+                <a href={`?revision=${revisionIdHackmdSynced}`}><span className="badge badge-secondary">{revisionIdHackmdSynced.substr(-8)}</span></a>
 
                 <div className="text-center mt-3">
                   <button

+ 2 - 2
src/client/js/components/PageList/PageListMeta.jsx

@@ -19,13 +19,13 @@ export default class PageListMeta extends React.Component {
     // portal check
     let portalLabel;
     if (this.isPortalPath(page.path)) {
-      portalLabel = <span className="label label-info">PORTAL</span>;
+      portalLabel = <span className="badge badge-info">PORTAL</span>;
     }
 
     // template check
     let templateLabel;
     if (templateChecker(page.path)) {
-      templateLabel = <span className="label label-info">TMPL</span>;
+      templateLabel = <span className="badge badge-info">TMPL</span>;
     }
 
     let commentCount;

+ 19 - 7
src/client/js/components/StaffCredit/StaffCredit.jsx

@@ -2,9 +2,15 @@ import React from 'react';
 import { GlobalHotKeys } from 'react-hotkeys';
 
 import loggerFactory from '@alias/logger';
+import {
+  Modal, ModalBody,
+} from 'reactstrap';
 
 import contributors from './Contributor';
 
+// Unit is px / milli sec
+const scrollSpeed = 0.3;
+
 /**
  * Page staff credit component
  *
@@ -39,6 +45,10 @@ export default class StaffCredit extends React.Component {
           isShown: true,
           userCommand: [],
         });
+        const target = $('.credit-curtain');
+        const scrollTargetHeight = target.children().innerHeight();
+        const duration = scrollTargetHeight / scrollSpeed;
+        target.animate({ scrollTop: scrollTargetHeight }, duration, 'linear');
       }
       else {
         // add UserCommand
@@ -96,12 +106,10 @@ export default class StaffCredit extends React.Component {
         );
       });
       return (
-        <div className="text-center credit-curtain" onClick={this.deleteCredit}>
-          <div className="credit-body">
-            <h1 className="staff-credit-mb-10">GROWI Contributors</h1>
-            <div className="clearfix"></div>
-            {credit}
-          </div>
+        <div className="text-center" onClick={this.deleteCredit}>
+          <h1 className="staff-credit-mb-10">GROWI Contributors</h1>
+          <div className="clearfix"></div>
+          {credit}
         </div>
       );
     }
@@ -113,7 +121,11 @@ export default class StaffCredit extends React.Component {
     const handlers = { check: (event) => { return this.check(event) } };
     return (
       <GlobalHotKeys keyMap={keyMap} handlers={handlers}>
-        {this.renderContributors()}
+        <Modal isOpen={this.state.isShown} toggle={this.deleteCredit} scrollable className="staff-credit">
+          <ModalBody className="credit-curtain">
+            {this.renderContributors()}
+          </ModalBody>
+        </Modal>
       </GlobalHotKeys>
     );
   }

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

@@ -56,7 +56,7 @@ class TagsList extends React.Component {
       return (
         <a key={data.name} href={`/_search?q=tag:${data.name}`} className="list-group-item">
           <i className="icon-tag mr-2"></i>{data.name}
-          <span className="ml-4 list-tag-count label label-default text-muted">{data.count}</span>
+          <span className="ml-4 list-tag-count badge badge-secondary text-muted">{data.count}</span>
         </a>
       );
     });

+ 5 - 1
src/client/styles/scss/_admin.scss

@@ -160,8 +160,12 @@
       background-color: rgba($info, 0.1);
     }
   }
+
+  .grw-fixed-controls-container {
+    display: none;
+  }
 }
 
 .admin-navigation > a + a {
   margin-top: 2px;
-}
+}

+ 6 - 2
src/client/styles/scss/_layout_kibela.scss

@@ -45,8 +45,8 @@ body.kibela {
     bottom: 0px;
     left: 0px;
     z-index: absolute;
-    max-width: 840px;
-    height: 8em;
+    max-width: 1024px;
+    min-height: 8em;
     margin: auto;
     border-radius: 0.35em;
   }
@@ -59,6 +59,10 @@ body.kibela {
       display: none;
     }
 
+    &.grw-subnav-user-page {
+      min-height: 128px;
+    }
+
     @media screen and (max-width: 765px) {
       padding-top: 30px;
     }

+ 1 - 1
src/client/styles/scss/_on-edit.scss

@@ -128,7 +128,7 @@ body.on-edit {
 
     .grw-grant-selector {
       .dropdown-toggle {
-        min-width: 150px;
+        min-width: 100px;
 
         // caret
         &::after {

+ 6 - 36
src/client/styles/scss/_staff_credit.scss

@@ -1,19 +1,14 @@
 // Staff Credit
-#staff-credit {
+.staff-credit {
+  // attached !important for updating from .modal-dialog class style
+  width: 80vw !important;
+  height: 80vh !important;
+  max-width: initial !important;
   // see https://css-tricks.com/old-timey-terminal-styling/
   @mixin old-timey-terminal-styling() {
     text-shadow: 0 0 10px #c8c8c8;
     background-color: black;
     background-image: radial-gradient(rgba(50, 100, 100, 0.75), black 120%);
-    &::after {
-      position: absolute;
-      top: 0;
-      left: 0;
-      width: 100vw;
-      height: 100vh;
-      content: '';
-      background: repeating-linear-gradient(0deg, rgba(black, 0.15), rgba(black, 0.15) 2px, transparent 2px, transparent 4px);
-    }
   }
 
   font-family: 'Press Start 2P', $basefont1;
@@ -33,35 +28,10 @@
 
   // see https://css-tricks.com/old-timey-terminal-styling/
   .credit-curtain {
-    position: fixed;
-    top: 10vh;
-    left: 20vh;
-    width: 80vw;
-    height: 80vh;
-    overflow-y: hidden;
-
+    padding-top: 80vh;
     @include old-timey-terminal-styling();
   }
 
-  .credit-body {
-    position: relative;
-    top: $credit-length;
-    animation-name: Credit;
-    // credit duration
-    animation-duration: 20s;
-    animation-timing-function: linear;
-  }
-
-  @keyframes Credit {
-    from {
-      top: 100%;
-    }
-    to {
-      // credit length
-      top: $credit-length;
-    }
-  }
-
   h1 {
     font-size: 3em;
   }

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

@@ -123,6 +123,10 @@ div.body {
     }
   }
 
+  pre {
+    white-space: pre-line;
+  }
+
   // only inline code blocks
   p code {
     font-family: $font-family-monospace-not-strictly;

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

@@ -19,6 +19,9 @@ input.form-control,
 textarea.form-control {
   color: lighten($color-global, 30%);
   background-color: darken($bgcolor-global, 5%);
+  &:focus {
+    background-color: $bgcolor-global;
+  }
   // FIXME: accent color
   // border: 1px solid darken($border, 30%);
 }
@@ -75,6 +78,22 @@ textarea.form-control {
   @extend .bg-dark;
 }
 
+/*
+ * Pagination
+ */
+ul.pagination {
+  li.page-item {
+    button.page-link {
+      @extend .btn-dark;
+    }
+    &.active {
+      button {
+        @extend .active;
+      }
+    }
+  }
+}
+
 .table > thead > tr > th,
 .table > tbody > tr > th,
 .table > tfoot > tr > th,

+ 5 - 5
src/client/styles/scss/theme/kibela.scss

@@ -72,11 +72,11 @@ html[light] {
 // Dark Mode ( same as Light Mode )
 html[dark] {
   // Background colors
-  $bgcolor-navbar: transparent;
+  $bgcolor-navbar: white;
   $bgcolor-navbar-active: $bgcolor-theme;
   $bgcolor-global: $themelight;
+  $bgcolor-card: $lightthemecolor;
   $bgcolor-inline-code: lighten($subthemecolor, 70%);
-  $bgcolor-card: #e3e5e7;
   $color-header: $bgcolor-theme;
   $color-global: #3c4a60;
   $color-link: rgb(74, 109, 204);
@@ -95,7 +95,7 @@ html[dark] {
   $color-list-hover: $color-reversal;
 
   // Logo colors
-  $bgcolor-logo: red;
+  $bgcolor-logo: transparent;
   $fillcolor-logo-mark: lighten($bgcolor-theme, 20%);
 
   // Icon colors
@@ -117,10 +117,10 @@ html[dark] {
   $color-dropdown-link-hover: $color-global;
 
   // alert
-  $color-alert: $color-global;
+  $color-alert: $color-reversal;
 
   // badge
-  $color-badge: $color-global;
+  $color-badge: $color-reversal;
 
   // Sidebar
   $bgcolor-sidebar: $bgcolor-theme;

+ 11 - 5
src/migrations/20191102223901-drop-pages-indices.js

@@ -4,7 +4,14 @@ const logger = require('@alias/logger')('growi:migrate:drop-pages-indices');
 const mongoose = require('mongoose');
 const config = require('@root/config/migrate');
 
-async function dropIndexIfExists(collection, indexName) {
+async function dropIndexIfExists(db, collectionName, indexName) {
+  // check existence of the collection
+  const items = await db.listCollections({ name: collectionName }, { nameOnly: true }).toArray();
+  if (items.length === 0) {
+    return;
+  }
+
+  const collection = await db.collection(collectionName);
   if (await collection.indexExists(indexName)) {
     await collection.dropIndex(indexName);
   }
@@ -15,10 +22,9 @@ module.exports = {
     logger.info('Apply migration');
     mongoose.connect(config.mongoUri, config.mongodb.options);
 
-    const collection = db.collection('pages');
-    await dropIndexIfExists(collection, 'lastUpdateUser_1');
-    await dropIndexIfExists(collection, 'liker_1');
-    await dropIndexIfExists(collection, 'seenUsers_1');
+    await dropIndexIfExists(db, 'pages', 'lastUpdateUser_1');
+    await dropIndexIfExists(db, 'pages', 'liker_1');
+    await dropIndexIfExists(db, 'pages', 'seenUsers_1');
 
     logger.info('Migration has successfully applied');
   },

+ 3 - 2
src/server/crowi/express-init.js

@@ -97,15 +97,16 @@ module.exports = function(crowi, app) {
   app.use(cookieParser());
 
   // configure express-session
+  const sessionMiddleware = expressSession(crowi.sessionConfig);
   app.use((req, res, next) => {
-    // test whether the route is listed in avoidSessionTroutes
+    // test whether the route is listed in avoidSessionRoutes
     for (const regex of avoidSessionRoutes) {
       if (regex.test(req.path)) {
         return next();
       }
     }
 
-    expressSession(crowi.sessionConfig)(req, res, next);
+    sessionMiddleware(req, res, next);
   });
 
   // passport

+ 1 - 1
src/server/routes/installer.js

@@ -38,7 +38,7 @@ module.exports = function(crowi, app) {
 
     // create /Sandbox/*
     promises.push(createPage(path.join(crowi.localeDir, lang, 'sandbox.md'), '/Sandbox', owner, lang));
-    promises.push(createPage(path.join(crowi.localeDir, lang, 'sandbox-bootstrap3.md'), '/Sandbox/Bootstrap3', owner, lang));
+    promises.push(createPage(path.join(crowi.localeDir, lang, 'sandbox-bootstrap4.md'), '/Sandbox/Bootstrap3', owner, lang));
     promises.push(createPage(path.join(crowi.localeDir, lang, 'sandbox-diagrams.md'), '/Sandbox/Diagrams', owner, lang));
     promises.push(createPage(path.join(crowi.localeDir, lang, 'sandbox-math.md'), '/Sandbox/Math', owner, lang));
 

+ 13 - 14
src/server/service/import.js

@@ -97,28 +97,27 @@ class ImportService {
    * @param {any} value value from imported document
    * @param {{ document: object, schema: object, propertyName: string }}
    * @return {any} new value for the document
+   *
+   * @see https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-cast
    */
   keepOriginal(value, { document, schema, propertyName }) {
-    let _value = value;
+    // Model
+    if (schema != null && schema.path(propertyName) != null) {
+      const schemaType = schema.path(propertyName);
+      return schemaType.cast(value);
+    }
 
     // _id
     if (propertyName === '_id' && ObjectId.isValid(value)) {
-      _value = ObjectId(value);
-    }
-    // Date
-    else if (isIsoDate(value)) {
-      _value = parseISO(value);
+      return ObjectId(value);
     }
 
-    // Model
-    if (schema != null) {
-      // ObjectID
-      if (schema[propertyName] != null && schema[propertyName].instance === 'ObjectID' && ObjectId.isValid(value)) {
-        _value = ObjectId(value);
-      }
+    // Date
+    if (isIsoDate(value)) {
+      return parseISO(value);
     }
 
-    return _value;
+    return value;
   }
 
   /**
@@ -409,7 +408,7 @@ class ImportService {
    */
   convertDocuments(collectionName, document, overwriteParams) {
     const Model = this.growiBridgeService.getModelFromCollectionName(collectionName);
-    const schema = (Model != null) ? Model.schema.paths : null;
+    const schema = (Model != null) ? Model.schema : null;
     const convertMap = this.convertMap[collectionName];
 
     const _document = {};

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

@@ -162,7 +162,7 @@
             <td>
               <img src="{{ sUser|picture }}" class="picture rounded-circle" />
               {% if sUser.admin %}
-              <span class="label label-inverse label-admin">
+              <span class="badge badge-dark label-admin">
               {{ t('administrator') }}
               </span>
               {% endif %}

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

@@ -42,7 +42,7 @@
 {% endblock %}
 
 
-{% block content_footer %}
+{% block content_main_after %}
   {% if page %}
     {% include '../widget/page_attachments.html' %}
   {% endif %}

+ 4 - 0
src/server/views/layout-growi/user_page.html

@@ -73,4 +73,8 @@
 
 {% block content_main_after %}
   {% include 'widget/comments.html' %}
+
+  {% if page %}
+    {% include '../widget/page_attachments.html' %}
+  {% endif %}
 {% endblock %}

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

@@ -13,9 +13,10 @@
 
     <div id="main" class="main col-12 kibela-block round-corner border-0 {% if page %}{{ css.grant(page) }}{% endif %}{% block main_css_class %}{% endblock %}">
       <div class="row grw-subnav d-edit-none">
-        <div class="col-9 px-0 mx-0 kibela-block">
+        <div class="col px-0 mx-0 kibela-block">
           {% block content_header %} {% endblock %}
         </div>
+        <div class="col-xl-3 col-lg-4"></div>
       </div>
       <!-- /.grw-subnav -->
 

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

@@ -40,7 +40,7 @@
 {% endblock %}
 
 
-{% block content_footer %}
+{% block content_main_after %}
   {% if page %}
     {% include '../widget/page_attachments.html' %}
   {% endif%}

+ 6 - 2
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="grw- subnav grw-subnav-user-page" 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 %}
@@ -53,7 +53,7 @@
 
   {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list mt-5 d-edit-none">
-    <div class="col-xs-12">
+    <div class="col-12">
       {% include '../widget/page_list_and_timeline_kibela.html' %}
     </div>
   </div>
@@ -64,4 +64,8 @@
 
 {% block content_main_after %}
   {% include 'widget/comments.html' %}
+
+  {% if page %}
+    {% include '../widget/page_attachments.html' %}
+  {% endif %}
 {% endblock %}

+ 2 - 0
src/server/views/modal/rename.html

@@ -29,6 +29,7 @@
 
           <hr>
 
+          {% if page.grant != 2 %}
           <div class="custom-control custom-checkbox custom-checkbox-warning">
             <input class="custom-control-input" name="recursively" id="cbRenameRecursively" value="1" type="checkbox" checked >
             <label class="custom-control-label" for="cbRenameRecursively">
@@ -36,6 +37,7 @@
               <p class="form-text text-muted mt-0">{{ t('modal_rename.help.recursive', page.path) }}</p>
             </label>
           </div>
+          {% endif %}
 
           <div class="custom-control custom-checkbox custom-checkbox-success">
             <input class="custom-control-input" name="create_redirect" id="cbRenameRedirect" value="1" type="checkbox">

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

@@ -50,7 +50,7 @@
       </div>
     {% endif %}
 
-    {% if not page.isDeleted() %}
+    {% if !isTrashPage() %}
       {# edit form #}
       <div class="tab-pane" id="edit">
         <div id="page-editor">{% if pageForm.body %}{{ pageForm.body }}{% endif %}</div>

+ 2 - 2
src/server/views/widget/page_list.html

@@ -15,11 +15,11 @@
   </a>
   <span class="page-list-meta">
     {% if page.isPortal() %}
-      <span class="label label-info">PORTAL</span>
+      <span class="badge badge-info">PORTAL</span>
     {% endif  %}
 
     {% if page.isTemplate() %}
-      <span class="label label-info">TMPL</span>
+      <span class="badge badge-info">TMPL</span>
     {% endif  %}
 
     {% if page.commentCount > 0 %}