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

Merge branch 'master' into feat/duplicate-with-subordinate-page

# Conflicts:
#	src/server/routes/index.js
takeru0001 5 лет назад
Родитель
Сommit
f65213526b
69 измененных файлов с 343 добавлено и 475 удалено
  1. 2 2
      bin/github-actions/update-readme.sh
  2. 5 5
      docker/README.md
  3. 3 1
      resource/locales/en_US/translation.json
  4. 3 1
      resource/locales/ja_JP/translation.json
  5. 4 2
      resource/locales/zh_CN/translation.json
  6. 1 1
      src/client/js/components/Admin/Security/SecuritySetting.jsx
  7. 1 1
      src/client/js/components/Drawio.jsx
  8. 10 2
      src/client/js/components/Hotkeys/Subscribers/EditPage.jsx
  9. 5 0
      src/client/js/components/Icons/LooockIcon.jsx
  10. 5 0
      src/client/js/components/Icons/PaperPlaneIcon.jsx
  11. 5 0
      src/client/js/components/Icons/ShareAltIcon.jsx
  12. 5 0
      src/client/js/components/Icons/UserIcon.jsx
  13. 1 1
      src/client/js/components/LikeButton.jsx
  14. 36 42
      src/client/js/components/Me/PersonalSettings.jsx
  15. 4 4
      src/client/js/components/MyDraftList/Draft.jsx
  16. 2 3
      src/client/js/components/MyDraftList/MyDraftList.jsx
  17. 3 3
      src/client/js/components/Navbar/GrowiSubNavigation.jsx
  18. 1 1
      src/client/js/components/Page.jsx
  19. 2 2
      src/client/js/components/Page/PageManagement.jsx
  20. 6 4
      src/client/js/components/PageAccessoriesModalControl.jsx
  21. 1 1
      src/client/js/components/PageComments.jsx
  22. 3 3
      src/client/js/components/PageContentFooter.jsx
  23. 21 9
      src/client/js/components/TableOfContents.jsx
  24. 1 1
      src/client/js/components/User/UserInfo.jsx
  25. 0 21
      src/client/js/legacy/crowi.js
  26. 15 15
      src/client/js/services/PageContainer.js
  27. 0 10
      src/client/styles/scss/_layout.scss
  28. 1 2
      src/client/styles/scss/_page-accessories-control.scss
  29. 2 27
      src/client/styles/scss/_page.scss
  30. 0 10
      src/client/styles/scss/_page_list.scss
  31. 10 0
      src/client/styles/scss/_user.scss
  32. 0 6
      src/client/styles/scss/_user_growi.scss
  33. 1 1
      src/client/styles/scss/atoms/_buttons.scss
  34. 0 1
      src/client/styles/scss/style-app.scss
  35. 2 15
      src/client/styles/scss/theme/_apply-colors-dark.scss
  36. 2 15
      src/client/styles/scss/theme/_apply-colors-light.scss
  37. 1 1
      src/client/styles/scss/theme/_apply-colors.scss
  38. 4 0
      src/client/styles/scss/theme/spring.scss
  39. 43 36
      src/server/routes/apiv3/bookmarks.js
  40. 49 8
      src/server/routes/apiv3/page.js
  41. 4 0
      src/server/routes/index.js
  42. 10 76
      src/server/routes/page.js
  43. 0 3
      src/server/views/admin/app.html
  44. 0 2
      src/server/views/admin/customize.html
  45. 0 3
      src/server/views/admin/export.html
  46. 0 5
      src/server/views/admin/external-accounts.html
  47. 0 3
      src/server/views/admin/global-notification-detail.html
  48. 0 3
      src/server/views/admin/importer.html
  49. 0 3
      src/server/views/admin/index.html
  50. 0 10
      src/server/views/admin/markdown.html
  51. 0 3
      src/server/views/admin/notification.html
  52. 0 3
      src/server/views/admin/search.html
  53. 0 3
      src/server/views/admin/security.html
  54. 0 3
      src/server/views/admin/user-group-detail.html
  55. 0 5
      src/server/views/admin/user-groups.html
  56. 0 5
      src/server/views/admin/users.html
  57. 25 19
      src/server/views/layout-growi/base/layout.html
  58. 1 5
      src/server/views/layout-growi/forbidden.html
  59. 1 5
      src/server/views/layout-growi/not_creatable.html
  60. 1 5
      src/server/views/layout-growi/not_found.html
  61. 1 3
      src/server/views/layout-growi/page.html
  62. 3 1
      src/server/views/layout-growi/page_list.html
  63. 4 11
      src/server/views/layout-growi/user_page.html
  64. 1 4
      src/server/views/layout/admin.html
  65. 15 12
      src/server/views/me/drafts.html
  66. 15 13
      src/server/views/me/index.html
  67. 0 3
      src/server/views/search.html
  68. 0 3
      src/server/views/tags.html
  69. 2 4
      src/server/views/widget/page_content.html

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

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

+ 5 - 5
docker/README.md

@@ -10,10 +10,10 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`4.1.0`, `4.1`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.1.0/docker/Dockerfile)
-* [`4.1.0-nocdn`, `4.1-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.1.0/docker/Dockerfile)
-* [`4.0.11`, `4.0`(Dockerfile)](https://github.com/weseek/growi/blob/v4.0.11/docker/Dockerfile)
-* [`4.0.11-nocdn`, `4.0-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.0.11/docker/Dockerfile)
+* [`4.2.0`, `4.2`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.2.0/docker/Dockerfile)
+* [`4.2.0-nocdn`, `4.2-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.2.0/docker/Dockerfile)
+* [`4.1.10`, `4.1` (Dockerfile)](https://github.com/weseek/growi/blob/v4.1.10/docker/Dockerfile)
+* [`4.1.10-nocdn`, `4.1-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.1.10/docker/Dockerfile)
 * [`3.8.0`, `3.8`, `3` (Dockerfile)](https://github.com/weseek/growi/blob/v3.8.0/docker/Dockerfile)
 * [`3.8.0-nocdn`, `3.8-nocdn`, `3-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v3.8.0/docker/Dockerfile)
 
@@ -39,7 +39,7 @@ The GROWI official docker image for production use which concludes several offic
 Requirements
 -------------
 
-* MongoDB (>= 3.6)
+* MongoDB (>= 4.4)
 
 ### Optional Dependencies
 

+ 3 - 1
resource/locales/en_US/translation.json

@@ -99,7 +99,6 @@
   "Input page name (optional)": "Input page name (optional)",
   "New Page": "New page",
   "Create under": "Create page under below:",
-  "Table of Contents": "Table of Contents",
   "Wiki Management Home Page": "Wiki Management Home Page",
   "App Settings": "App Settings",
   "Site URL settings": "Site URL settings",
@@ -298,6 +297,9 @@
       "no_deadline":"This page has no expiration date"
     }
   },
+  "page_table_of_contents": {
+    "empty": "Table of Contents is empty"
+  },
   "page_edit": {
     "Show active line": "Show active line",
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",

+ 3 - 1
resource/locales/ja_JP/translation.json

@@ -100,7 +100,6 @@
   "Input page name (optional)": "ページ名を入力(空欄OK)",
   "New Page": "新規ページ",
   "Create under": "ページを以下に作成",
-  "Table of Contents": "目次",
   "Wiki Management Home Page": "Wiki管理トップ",
   "App Settings": "アプリ設定",
   "Site URL settings": "サイトURL設定",
@@ -300,6 +299,9 @@
       "no_deadline": "このページに有効期限は設定されていません。"
     }
   },
+  "page_table_of_contents": {
+    "empty": "目次は空です"
+  },
   "page_edit": {
     "Show active line": "アクティブ行をハイライト",
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",

+ 4 - 2
resource/locales/zh_CN/translation.json

@@ -108,7 +108,6 @@
 	"Input page name (optional)": "Input page name (optional)",
 	"New Page": "新页面",
 	"Create under": "Create page under below:",
-	"Table of Contents": "Table of Contents",
 	"Wiki Management Home Page": "Wiki管理首页",
 	"App Settings": "系统设置",
 	"Site URL settings": "主页URL设置",
@@ -285,7 +284,10 @@
 		"notice": {
 			"conflict": "无法保存您所做的更改,因为其他人正在编辑此页。请在重新加载页面后重新编辑受影响的部分。"
 		}
-	},
+  },
+  "page_table_of_contents": {
+    "empty": "目录为空"
+  },
   "page_comment": {
     "display_the_page_when_posting_this_comment": "Display the page when posting this comment"
   },

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

@@ -63,7 +63,7 @@ class SecuritySetting extends React.Component {
               <td>{ t('always_hidden') }</td>
             </tr>
             <tr>
-              <th scope="row">{ t('Just me') }</th>
+              <th scope="row">{ t('Only me') }</th>
               <td>
                 <div className="custom-control custom-switch custom-checkbox-success">
                   <input

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

@@ -56,7 +56,7 @@ class Drawio extends React.Component {
 
   render() {
     return (
-      <div className="editable-with-drawio position-relative">
+      <div className="editable-with-drawio container-lg position-relative">
         { !this.isPreview && (
           <NotAvailableForGuest>
             <button type="button" className="drawio-iframe-trigger position-absolute btn btn-outline-secondary" onClick={this.onEdit}>

+ 10 - 2
src/client/js/components/Hotkeys/Subscribers/EditPage.jsx

@@ -1,6 +1,9 @@
 import React, { useEffect } from 'react';
 import PropTypes from 'prop-types';
 
+import NavigationContainer from '../../../services/NavigationContainer';
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
 const EditPage = (props) => {
 
   // setup effect
@@ -10,6 +13,8 @@ const EditPage = (props) => {
       return;
     }
 
+    props.navigationContainer.setEditorMode('edit');
+
     // remove this
     props.onDeleteRender(this);
   }, [props]);
@@ -18,11 +23,14 @@ const EditPage = (props) => {
 };
 
 EditPage.propTypes = {
+  navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
   onDeleteRender: PropTypes.func.isRequired,
 };
 
-EditPage.getHotkeyStrokes = () => {
+const EditPageWrapper = withUnstatedContainers(EditPage, [NavigationContainer]);
+
+EditPageWrapper.getHotkeyStrokes = () => {
   return [['e']];
 };
 
-export default EditPage;
+export default EditPageWrapper;

+ 5 - 0
src/client/js/components/Icons/LooockIcon.jsx

@@ -0,0 +1,5 @@
+import React from 'react';
+
+const LockIcon = () => <i className="icon-fw icon-lock"></i>;
+
+export default LockIcon;

+ 5 - 0
src/client/js/components/Icons/PaperPlaneIcon.jsx

@@ -0,0 +1,5 @@
+import React from 'react';
+
+const PaperPlaneIcon = () => <i className="icon-fw icon-paper-plane"></i>;
+
+export default PaperPlaneIcon;

+ 5 - 0
src/client/js/components/Icons/ShareAltIcon.jsx

@@ -0,0 +1,5 @@
+import React from 'react';
+
+const ShareAltIcon = () => <i className="icon-fw icon-share-alt"></i>;
+
+export default ShareAltIcon;

+ 5 - 0
src/client/js/components/Icons/UserIcon.jsx

@@ -0,0 +1,5 @@
+import React from 'react';
+
+const UserIcon = () => <i className="icon-fw icon-user"></i>;
+
+export default UserIcon;

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

@@ -39,7 +39,7 @@ class LikeButton extends React.Component {
       <button
         type="button"
         onClick={this.handleClick}
-        className={`btn btn-like border-0 d-edit-none
+        className={`btn btn-like border-0
         ${pageContainer.state.isLiked ? 'active' : ''}`}
       >
         <i className="icon-like mr-3"></i>

+ 36 - 42
src/client/js/components/Me/PersonalSettings.jsx

@@ -1,59 +1,53 @@
 
-import React, { Fragment } from 'react';
+import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
-
+import CustomNavigation from '../CustomNavigation';
 import UserSettings from './UserSettings';
 import PasswordSettings from './PasswordSettings';
 import ExternalAccountLinkedMe from './ExternalAccountLinkedMe';
 import ApiSettings from './ApiSettings';
 
+import UserIcon from '../Icons/UserIcon';
+import ShareAltIcon from '../Icons/ShareAltIcon';
+import LockIcon from '../Icons/LooockIcon';
+import PaperPlaneIcon from '../Icons/PaperPlaneIcon';
+
 class PersonalSettings extends React.Component {
 
   render() {
     const { t } = this.props;
 
+    const navTabMapping = {
+      user_infomation: {
+        Icon: UserIcon,
+        Content: UserSettings,
+        i18n: t('User Information'),
+        index: 0,
+      },
+      external_accounts: {
+        Icon: ShareAltIcon,
+        Content: ExternalAccountLinkedMe,
+        i18n: t('admin:user_management.external_accounts'),
+        index: 1,
+      },
+      password_settings: {
+        Icon: LockIcon,
+        Content: PasswordSettings,
+        i18n: t('Password Settings'),
+        index: 2,
+      },
+      api_settings: {
+        Icon: PaperPlaneIcon,
+        Content: ApiSettings,
+        i18n: t('API Settings'),
+        index: 3,
+      },
+    };
+
+
     return (
-      <Fragment>
-        <div className="personal-settings">
-          <ul className="nav nav-tabs" role="tablist">
-            <li className="nav-item">
-              <a className="nav-link active" href="#user-settings" data-toggle="tab" role="tab">
-                <i className="icon-fw icon-user"></i>{ t('User Information') }
-              </a>
-            </li>
-            <li className="nav-item">
-              <a className="nav-link" href="#external-accounts" data-toggle="tab" role="tab">
-                <i className="icon-fw icon-share-alt"></i>{ t('admin:user_management.external_accounts') }
-              </a>
-            </li>
-            <li className="nav-item">
-              <a className="nav-link" href="#password-settings" data-toggle="tab" role="tab">
-                <i className="icon-fw icon-lock"></i>{ t('Password Settings') }
-              </a>
-            </li>
-            <li className="nav-item">
-              <a className="nav-link" href="#apiToken" data-toggle="tab" role="tab">
-                <i className="icon-fw icon-paper-plane"></i>{ t('API Settings') }
-              </a>
-            </li>
-          </ul>
-          <div className="tab-content p-t-10">
-            <div id="user-settings" className="tab-pane active" role="tabpanel">
-              <UserSettings />
-            </div>
-            <div id="external-accounts" className="tab-pane" role="tabpanel">
-              <ExternalAccountLinkedMe />
-            </div>
-            <div id="password-settings" className="tab-pane" role="tabpanel">
-              <PasswordSettings />
-            </div>
-            <div id="apiToken" className="tab-pane" role="tabpanel">
-              <ApiSettings />
-            </div>
-          </div>
-        </div>
-      </Fragment>
+      <CustomNavigation navTabMapping={navTabMapping} />
     );
   }
 

+ 4 - 4
src/client/js/components/MyDraftList/Draft.jsx

@@ -105,10 +105,9 @@ class Draft extends React.Component {
   }
 
   renderControls() {
-    const { t, path } = this.props;
+    const { t, path, index } = this.props;
 
-    const encodedPath = path.replace(/\//g, '-');
-    const tooltipTargetId = `draft-copied-tooltip_${encodedPath}`;
+    const tooltipTargetId = `draft-copied-tooltip_${index}`;
 
     return (
       <div className="icon-container">
@@ -116,7 +115,7 @@ class Draft extends React.Component {
           ? null
           : (
             <a
-              href={`${this.props.path}#edit`}
+              href={`${path}#edit`}
               target="_blank"
               rel="noopener noreferrer"
               data-toggle="tooltip"
@@ -203,6 +202,7 @@ Draft.propTypes = {
   t: PropTypes.func.isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
+  index: PropTypes.number.isRequired,
   path: PropTypes.string.isRequired,
   markdown: PropTypes.string.isRequired,
   isExist: PropTypes.bool.isRequired,

+ 2 - 3
src/client/js/components/MyDraftList/MyDraftList.jsx

@@ -91,9 +91,10 @@ class MyDraftList extends React.Component {
    *
    */
   generateDraftList(drafts) {
-    return drafts.map((draft) => {
+    return drafts.map((draft, index) => {
       return (
         <Draft
+          index={index}
           key={draft.path}
           path={draft.path}
           markdown={draft.markdown}
@@ -135,8 +136,6 @@ class MyDraftList extends React.Component {
 
     return (
       <div className="page-list-container-create ">
-        <h1>My Drafts</h1>
-        <hr />
         { totalCount === 0
           && <span className="mt-2">No drafts yet.</span>
         }

+ 3 - 3
src/client/js/components/Navbar/GrowiSubNavigation.jsx

@@ -71,7 +71,7 @@ const PagePathNav = ({ pageId, pagePath, isPageForbidden }) => {
 const PageReactionButtons = ({ appContainer, pageContainer }) => {
 
   const {
-    pageId, isLiked, pageUser, shareLinkId,
+    pageUser, shareLinkId,
   } = pageContainer.state;
 
   const isSharedPage = useMemo(() => {
@@ -82,11 +82,11 @@ const PageReactionButtons = ({ appContainer, pageContainer }) => {
     <>
       {pageUser == null && !isSharedPage && (
       <span className="mr-2">
-        <LikeButton pageId={pageId} isLiked={isLiked} />
+        <LikeButton />
       </span>
       )}
       <span>
-        <BookmarkButton pageId={pageId} crowi={appContainer} />
+        <BookmarkButton crowi={appContainer} />
       </span>
     </>
   );

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

@@ -134,7 +134,7 @@ class Page extends React.Component {
     const { markdown } = pageContainer.state;
 
     return (
-      <div className={`${isMobile && 'page-mobile'}`}>
+      <div className={`mb-5 ${isMobile ? 'page-mobile' : ''}`}>
         <RevisionRenderer growiRenderer={this.growiRenderer} markdown={markdown} />
 
         { isLoggedIn && (

+ 2 - 2
src/client/js/components/Page/PageManagement.jsx

@@ -146,8 +146,8 @@ const PageManagement = (props) => {
     return (
       <>
         <div className="dropdown-divider"></div>
-        <button className="dropdown-item" type="button" onClick={openPageDeleteModalHandler}>
-          <i className="icon-fw icon-fire text-danger"></i> { t('Delete') }
+        <button className="dropdown-item text-danger" type="button" onClick={openPageDeleteModalHandler}>
+          <i className="icon-fw icon-fire"></i> { t('Delete') }
         </button>
       </>
     );

+ 6 - 4
src/client/js/components/PageAccessoriesModalControl.jsx

@@ -19,7 +19,8 @@ const PageAccessoriesModalControl = (props) => {
   const { t, pageAccessoriesContainer, isGuestUserMode } = props;
 
   return (
-    <div className="grw-page-accessories-control d-flex align-items-center pb-1">
+    <div className="grw-page-accessories-control d-flex align-items-center justify-content-between pb-1">
+
       <button
         type="button"
         className="btn btn-link grw-btn-page-accessories"
@@ -67,9 +68,10 @@ const PageAccessoriesModalControl = (props) => {
         </UncontrolledTooltip>
       )}
 
-      <span className="border-left grw-border-vr mx-1">&nbsp;</span>
-
-      <SeenUserInfo />
+      <div className="d-flex align-items-center">
+        <span className="border-left grw-border-vr">&nbsp;</span>
+        <SeenUserInfo />
+      </div>
     </div>
   );
 };

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

@@ -148,7 +148,7 @@ class PageComments extends React.Component {
     }
 
     return (
-      <div key={commentId} className={`mb-5 ${rootClassNames}`}>
+      <div key={commentId} className={rootClassNames}>
         <Comment
           comment={comment}
           deleteBtnClicked={this.confirmToDeleteComment}

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

@@ -14,12 +14,12 @@ const PageContentFooter = (props) => {
   } = pageContainer.state;
 
   return (
-    <div className="page-content-footer mt-5 py-4 d-edit-none d-print-none">
+    <div className="page-content-footer py-4 d-edit-none d-print-none">
       <div className="container-lg">
-        <p className="page-meta">
+        <div className="page-meta">
           <AuthorInfo user={creator} date={createdAt} mode="create" locate="footer" />
           <AuthorInfo user={revisionAuthor} date={updatedAt} mode="update" locate="footer" />
-        </p>
+        </div>
       </div>
     </div>
   );

+ 21 - 9
src/client/js/components/TableOfContents.jsx

@@ -20,7 +20,7 @@ const logger = loggerFactory('growi:TableOfContents');
  */
 const TableOfContents = (props) => {
 
-  const { pageContainer, navigationContainer } = props;
+  const { t, pageContainer, navigationContainer } = props;
   const { pageUser } = pageContainer.state;
   const isUserPage = pageUser != null;
 
@@ -59,14 +59,24 @@ const TableOfContents = (props) => {
       stickyElemSelector=".grw-side-contents-sticky-container"
       calcViewHeightFunc={calcViewHeight}
     >
-      <div
-        id="revision-toc-content"
-        className="revision-toc-content"
-        // eslint-disable-next-line react/no-danger
-        dangerouslySetInnerHTML={{
-        __html: tocHtml,
-      }}
-      />
+      { tocHtml !== ''
+      ? (
+        <div
+          id="revision-toc-content"
+          className="revision-toc-content mb-3"
+          // eslint-disable-next-line react/no-danger
+          dangerouslySetInnerHTML={{ __html: tocHtml }}
+        />
+      )
+      : (
+        <div
+          id="revision-toc-content"
+          className="revision-toc-content mb-2"
+        >
+          <span className="text-muted">({t('page_table_of_contents.empty')})</span>
+        </div>
+      ) }
+
     </StickyStretchableScroller>
   );
 
@@ -78,6 +88,8 @@ const TableOfContents = (props) => {
 const TableOfContentsWrapper = withUnstatedContainers(TableOfContents, [PageContainer, NavigationContainer]);
 
 TableOfContents.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
 };

+ 1 - 1
src/client/js/components/User/UserInfo.jsx

@@ -12,7 +12,7 @@ const UserInfo = (props) => {
   }
 
   return (
-    <div className="grw-users-info d-flex align-items-center d-edit-none pb-2 border-bottom">
+    <div className="grw-users-info d-flex align-items-center d-edit-none mb-5 pb-3 border-bottom">
       <UserPicture user={pageUser} />
 
       <div className="users-meta">

+ 0 - 21
src/client/js/legacy/crowi.js

@@ -154,27 +154,6 @@ Crowi.highlightSelectedSection = function(hash) {
   }
 };
 
-$(() => {
-  const pageId = $('#content-main').data('page-id');
-  const isSeen = $('#content-main').data('page-is-seen');
-
-  $('[data-toggle="popover"]').popover();
-  $('[data-toggle="tooltip"]').tooltip();
-  $('[data-tooltip-stay]').tooltip('show');
-
-  if (pageId) {
-
-    if (!isSeen) {
-      $.post('/_api/pages.seen', { page_id: pageId }, (res) => {
-        // ignore unless response has error
-        if (res.ok && res.seenUser) {
-          $('#content-main').data('page-is-seen', 1);
-        }
-      });
-    }
-  } // end if pageId
-});
-
 window.addEventListener('load', (e) => {
   const { appContainer } = window;
 

+ 15 - 15
src/client/js/services/PageContainer.js

@@ -104,8 +104,8 @@ export default class PageContainer extends Container {
     this.initStateMarkdown();
     this.checkAndUpdateImageUrlCached(this.state.likerUsers);
 
-    // skip if shared page
-    if (this.state.shareLinkId == null) {
+    // skip if shared page or new page
+    if (this.state.shareLinkId == null && this.state.pageId != null) {
       this.retrieveSeenUsers();
       this.retrieveLikeInfo();
       this.retrieveBookmarkInfo();
@@ -162,11 +162,12 @@ export default class PageContainer extends Container {
   }
 
   async retrieveLikeInfo() {
-    const like = await this.appContainer.apiv3Get('/page/like-info', { _id: this.state.pageId });
+    const res = await this.appContainer.apiv3Get('/page/like-info', { _id: this.state.pageId });
+    const { sumOfLikers, isLiked } = res.data;
+
     this.setState({
-      sumOfLikers: like.data.sumOfLikers,
-      likerUsers: like.data.users.liker,
-      isLiked: like.data.isLiked,
+      sumOfLikers,
+      isLiked,
     });
   }
 
@@ -179,14 +180,11 @@ export default class PageContainer extends Container {
   }
 
   async retrieveBookmarkInfo() {
-    const response = await this.appContainer.apiv3Get('/bookmarks', { pageId: this.state.pageId });
-    if (response.data.bookmarks != null) {
-      this.setState({ isBookmarked: true });
-    }
-    else {
-      this.setState({ isBookmarked: false });
-    }
-    this.setState({ sumOfBookmarks: response.data.sumOfBookmarks });
+    const response = await this.appContainer.apiv3Get('/bookmarks/info', { pageId: this.state.pageId });
+    this.setState({
+      sumOfBookmarks: response.data.sumOfBookmarks,
+      isBookmarked: response.data.isBookmarked,
+    });
   }
 
   async toggleBookmark() {
@@ -246,6 +244,8 @@ export default class PageContainer extends Container {
       revisionIdHackmdSynced: page.revisionHackmdSynced,
       hasDraftOnHackmd: page.hasDraftOnHackmd,
       markdown: page.revision.body,
+      createdAt: page.createdAt,
+      updatedAt: page.updatedAt,
     };
     if (tags != null) {
       newState.tags = tags;
@@ -255,7 +255,7 @@ export default class PageContainer extends Container {
     // PageEditor component
     const pageEditor = this.appContainer.getComponentInstance('PageEditor');
     if (pageEditor != null) {
-      if (editorMode !== 'builtin') {
+      if (editorMode !== 'edit') {
         pageEditor.updateEditorValue(newState.markdown);
       }
     }

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

@@ -107,16 +107,6 @@ body {
       margin-bottom: 20px;
       font-size: 0.9em;
       border: solid 1px $gray-400;
-
-      .revision-toc-head {
-        display: inline-block;
-        float: none;
-      }
-
-      .revision-toc-content.collapse {
-        display: block;
-        height: auto;
-      }
     }
 
     .meta {

+ 1 - 2
src/client/styles/scss/_page-accessories-control.scss

@@ -4,8 +4,7 @@
   border-bottom: 1px solid transparent;
 
   .grw-btn-page-accessories {
-    padding: 0.375rem 0.5rem;
-    margin: 0 0.2rem;
+    padding: 0.375rem;
 
     svg {
       width: 16px;

+ 2 - 27
src/client/styles/scss/_page.scss

@@ -1,32 +1,7 @@
 // import diff2html styles
 @import '~diff2html/bundles/css/diff2html.min.css';
 
-.main-container {
-  .url-line {
-    font-size: 1rem;
-    color: $gray-400;
-  }
-
-  h1.title {
-    margin-top: 0;
-    margin-bottom: 0;
-
-    .d-flex {
-      flex-wrap: wrap; // for long page path
-    }
-
-    // crowi layout only
-    a.last-path {
-      color: $gray-300;
-
-      &:hover {
-        color: inherit;
-      }
-    }
-  }
-}
-
-.main .content-main .revision-history {
+.revision-history {
   .revision-history-list {
     .revision-history-outer {
       // add border-top except of first element
@@ -128,7 +103,7 @@
 .editable-with-drawio {
   .drawio-iframe-trigger {
     top: 11px;
-    right: 10px;
+    right: 40px;
     z-index: 14;
     font-size: 12px;
     line-height: 1;

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

@@ -72,13 +72,3 @@ body .page-list {
     background-color: $gray-300;
   }
 }
-
-.grw-page-list-m {
-  .grw-page-list-title-m {
-    svg {
-      width: 35px;
-      height: 35px;
-      margin-bottom: 6px;
-    }
-  }
-}

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

@@ -43,3 +43,13 @@ $easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
     }
   }
 }
+
+.user-page {
+  .grw-user-page-header {
+    svg {
+      width: 35px;
+      height: 35px;
+      margin-bottom: 6px;
+    }
+  }
+}

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

@@ -1,6 +0,0 @@
-.growi .user-page {
-  .revision-toc {
-    position: sticky;
-    top: 105px;
-  }
-}

+ 1 - 1
src/client/styles/scss/atoms/_buttons.scss

@@ -1,5 +1,5 @@
 .btn.btn-like {
-  @include button-outline-variant($secondary, lighten($info, 15%), rgba(lighten($info, 10%), 0.5), rgba(lighten($info, 10%), 0.5));
+  @include button-outline-variant($secondary, lighten($info, 15%), rgba(lighten($info, 10%), 0.15), rgba(lighten($info, 10%), 0.5));
   &:not(:disabled):not(.disabled):active,
   &:not(:disabled):not(.disabled).active {
     color: lighten($info, 15%);

+ 0 - 1
src/client/styles/scss/style-app.scss

@@ -58,7 +58,6 @@
 @import 'tag';
 @import 'toc';
 @import 'user';
-@import 'user_growi';
 @import 'staff_credit';
 @import 'waves';
 @import 'wiki';

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

@@ -349,10 +349,8 @@ body.on-edit {
   }
 }
 
-.growi .main {
-  .page-comments-row {
-    background: $bgcolor-subnav;
-  }
+.page-comments-row {
+  background: $bgcolor-subnav;
 }
 
 /*
@@ -364,14 +362,3 @@ body.on-edit {
     background-color: $bgcolor-tags;
   }
 }
-
-/*
- * GROWI user page
- */
-.grw-page-list-m {
-  .grw-page-list-title-m {
-    svg {
-      fill: $color-global;
-    }
-  }
-}

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

@@ -279,10 +279,8 @@ $table-hover-bg: $bgcolor-table-hover;
   }
 }
 
-.growi .main {
-  .page-comments-row {
-    background: $bgcolor-subnav;
-  }
+.page-comments-row {
+  background: $bgcolor-subnav;
 }
 
 /*
@@ -294,14 +292,3 @@ $table-hover-bg: $bgcolor-table-hover;
     background-color: $bgcolor-tags;
   }
 }
-
-/*
- * GROWI user page
- */
-.grw-page-list-m {
-  .grw-page-list-title-m {
-    svg {
-      fill: $color-global;
-    }
-  }
-}

+ 1 - 1
src/client/styles/scss/theme/_apply-colors.scss

@@ -457,7 +457,7 @@ body.on-edit {
 /*
  * GROWI comment form
  */
-.growi .main {
+.page-comments {
   .page-comment .page-comment-main,
   .page-comment-form .comment-form-main {
     background-color: $bgcolor-global;

+ 4 - 0
src/client/styles/scss/theme/spring.scss

@@ -146,6 +146,10 @@ html[dark] {
   h1,
   h2 {
     color: $subthemecolor;
+
+    svg {
+      fill: $subthemecolor;
+    }
   }
 
   .nav.nav-tabs {

+ 43 - 36
src/server/routes/apiv3/bookmarks.js

@@ -50,11 +50,23 @@ const router = express.Router();
  *          bool:
  *            type: boolean
  *            description: boolean for bookmark status
+ *
+ *      BookmarkInfo:
+ *        description: BookmarkInfo
+ *        type: object
+ *        properties:
+ *          sumOfBookmarks:
+ *            type: number
+ *            description: how many people bookmarked the page
+ *          isBookmarked:
+ *            type: boolean
+ *            description: Whether the request user bookmarked (will be returned if the user is included in the request)
  */
 
 module.exports = (crowi) => {
   const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
-  const loginRequired = require('../../middlewares/login-required')(crowi);
+  const loginRequiredStrictly = require('@server/middlewares/login-required')(crowi);
+  const loginRequired = require('../../middlewares/login-required')(crowi, true);
   const csrf = require('../../middlewares/csrf')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
 
@@ -73,12 +85,12 @@ module.exports = (crowi) => {
   /**
    * @swagger
    *
-   *    /bookmarks:
+   *    /bookmarks/info:
    *      get:
    *        tags: [Bookmarks]
-   *        summary: /bookmarks
-   *        description: Get bookmarked status
-   *        operationId: getBookmarkedStatus
+   *        summary: /bookmarks/info
+   *        description: Get bookmarked info
+   *        operationId: getBookmarkedInfo
    *        parameters:
    *          - name: pageId
    *            in: query
@@ -87,24 +99,41 @@ module.exports = (crowi) => {
    *              type: string
    *        responses:
    *          200:
-   *            description: Succeeded to get bookmarked status.
+   *            description: Succeeded to get bookmark info.
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/Bookmark'
+   *                  $ref: '#/components/schemas/BookmarkInfo'
    */
-  router.get('/', accessTokenParser, loginRequired, validator.bookmarkInfo, async(req, res) => {
+  router.get('/info', accessTokenParser, loginRequired, validator.bookmarkInfo, apiV3FormValidator, async(req, res) => {
+    const { user } = req;
     const { pageId } = req.query;
 
+    const responsesParams = {};
+
     try {
-      const bookmarks = await Bookmark.findByPageIdAndUserId(pageId, req.user);
-      const sumOfBookmarks = await Bookmark.countByPageId(pageId);
-      return res.apiv3({ bookmarks, sumOfBookmarks });
+      responsesParams.sumOfBookmarks = await Bookmark.countByPageId(pageId);
     }
     catch (err) {
-      logger.error('get-bookmark-failed', err);
+      logger.error('get-bookmark-count-failed', err);
       return res.apiv3Err(err, 500);
     }
+
+    // guest user only get bookmark count
+    if (user == null) {
+      return res.apiv3(responsesParams);
+    }
+
+    try {
+      const bookmark = await Bookmark.findByPageIdAndUserId(pageId, user._id);
+      responsesParams.isBookmarked = (bookmark != null);
+      return res.apiv3(responsesParams);
+    }
+    catch (err) {
+      logger.error('get-bookmark-state-failed', err);
+      return res.apiv3Err(err, 500);
+    }
+
   });
 
   // select page from bookmark where userid = userid
@@ -152,7 +181,7 @@ module.exports = (crowi) => {
     query('limit').if(value => value != null).isInt({ max: 300 }).withMessage('You should set less than 300 or not to set limit.'),
   ];
 
-  router.get('/:userId', accessTokenParser, loginRequired, validator.myBookmarkList, apiV3FormValidator, async(req, res) => {
+  router.get('/:userId', accessTokenParser, loginRequiredStrictly, validator.myBookmarkList, apiV3FormValidator, async(req, res) => {
     const { userId } = req.params;
     const page = req.query.page;
     const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30;
@@ -213,7 +242,7 @@ module.exports = (crowi) => {
    *                schema:
    *                  $ref: '#/components/schemas/Bookmark'
    */
-  router.put('/', accessTokenParser, loginRequired, csrf, validator.bookmarks, apiV3FormValidator, async(req, res) => {
+  router.put('/', accessTokenParser, loginRequiredStrictly, csrf, validator.bookmarks, apiV3FormValidator, async(req, res) => {
     const { pageId, bool } = req.body;
 
     let bookmark;
@@ -240,27 +269,5 @@ module.exports = (crowi) => {
     return res.apiv3({ bookmark });
   });
 
-  /**
-   * @swagger
-   *
-   *    /count-bookmarks:
-   *      get:
-   *        tags: [Bookmarks]
-   *        summary: /bookmarks
-   *        description: Count bookmsrks
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                $ref: '#/components/schemas/BookmarkParams'
-   *        responses:
-   *          200:
-   *            description: Succeeded to count bookmarks.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  $ref: '#/components/schemas/Bookmark'
-   */
-
   return router;
 };

+ 49 - 8
src/server/routes/apiv3/page.js

@@ -10,6 +10,7 @@ const router = express.Router();
 const { convertToNewAffiliationPath } = require('../../../lib/util/path-utils');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
+
 /**
  * @swagger
  *  tags:
@@ -110,6 +111,17 @@ const ErrorV3 = require('../../models/vo/error-apiv3');
  *          bool:
  *            type: boolean
  *            description: boolean for like status
+ *
+ *      LikeInfo:
+ *        description: LikeInfo
+ *        type: object
+ *        properties:
+ *          sumOfLikers:
+ *            type: number
+ *            description: how many people liked the page
+ *          isLiked:
+ *            type: boolean
+ *            description: Whether the request user liked (will be returned if the user is included in the request)
  */
 module.exports = (crowi) => {
   const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
@@ -119,7 +131,7 @@ module.exports = (crowi) => {
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
 
   const globalNotificationService = crowi.getGlobalNotificationService();
-  const { Page, GlobalNotificationSetting, User } = crowi.models;
+  const { Page, GlobalNotificationSetting } = crowi.models;
   const { exportService } = crowi;
 
   const validator = {
@@ -205,19 +217,48 @@ module.exports = (crowi) => {
     return res.apiv3({ result });
   });
 
-  router.get('/like-info', loginRequired, validator.likeInfo, async(req, res) => {
+  /**
+   * @swagger
+   *
+   *    /page/like-info:
+   *      get:
+   *        tags: [Page]
+   *        summary: /page/like-info
+   *        description: Get like info
+   *        operationId: getLikeInfo
+   *        parameters:
+   *          - name: _id
+   *            in: query
+   *            description: page id
+   *            schema:
+   *              type: string
+   *        responses:
+   *          200:
+   *            description: Succeeded to get bookmark info.
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  $ref: '#/components/schemas/LikeInfo'
+   */
+  router.get('/like-info', loginRequired, validator.likeInfo, apiV3FormValidator, async(req, res) => {
     const pageId = req.query._id;
-    const userId = req.user._id;
+
+    const responsesParams = {};
+
     try {
       const page = await Page.findById(pageId);
-      const users = await Page.findById(pageId).populate('liker', User.USER_PUBLIC_FIELDS);
-      const sumOfLikers = page.liker.length;
-      const isLiked = page.liker.includes(userId);
+      responsesParams.sumOfLikers = page.liker.length;
+
+      // guest user return nothing
+      if (!req.user) {
+        return res.apiv3(responsesParams);
+      }
 
-      return res.apiv3({ users, sumOfLikers, isLiked });
+      responsesParams.isLiked = page.liker.includes(req.user._id);
+      return res.apiv3(responsesParams);
     }
     catch (err) {
-      logger.error('error like info', err);
+      logger.error('get-like-count-failed', err);
       return res.apiv3Err(err, 500);
     }
   });

+ 4 - 0
src/server/routes/index.js

@@ -142,7 +142,11 @@ module.exports = function(crowi, app) {
   app.get('/_api/pages.updatePost'    , accessTokenParser, loginRequired, page.api.getUpdatePost);
   app.get('/_api/pages.getPageTag'    , accessTokenParser , loginRequired , page.api.getPageTag);
   // allow posting to guests because the client doesn't know whether the user logged in
+<<<<<<< HEAD
   app.post('/_api/pages.seen'         , accessTokenParser , loginRequired , page.api.seen);
+=======
+  app.post('/_api/pages.rename'       , accessTokenParser , loginRequiredStrictly , csrf, page.api.rename);
+>>>>>>> master
   app.post('/_api/pages.remove'       , loginRequiredStrictly , csrf, page.api.remove); // (Avoid from API Token)
   app.post('/_api/pages.revertRemove' , loginRequiredStrictly , csrf, page.api.revertRemove); // (Avoid from API Token)
   app.post('/_api/pages.unlink'       , loginRequiredStrictly , csrf, page.api.unlink); // (Avoid from API Token)

+ 10 - 76
src/server/routes/page.js

@@ -65,13 +65,6 @@
  *            example: ""
  *          revision:
  *            $ref: '#/components/schemas/Revision'
- *          seenUsers:
- *            type: array
- *            description: granted users
- *            items:
- *              type: string
- *              description: user ID
- *            example: ["5ae5fccfc5577b0004dbd8ab"]
  *          status:
  *            type: string
  *            description: status
@@ -327,6 +320,11 @@ module.exports = function(crowi, app) {
     let portalPage = await Page.findByPathAndViewer(portalPath, req.user);
     portalPage.initLatestRevisionField(revisionId);
 
+    // add user to seen users
+    if (req.user != null) {
+      portalPage = await portalPage.seen(req.user);
+    }
+
     // populate
     portalPage = await portalPage.populateDataToShowRevision();
 
@@ -372,6 +370,11 @@ module.exports = function(crowi, app) {
 
     page.initLatestRevisionField(revisionId);
 
+    // add user to seen users
+    if (req.user != null) {
+      page = await page.seen(req.user);
+    }
+
     // populate
     page = await page.populateDataToShowRevision();
     addRenderVarsForPage(renderVars, page);
@@ -1068,75 +1071,6 @@ module.exports = function(crowi, app) {
     return res.json(ApiResponse.success(result));
   };
 
-  /**
-   * @swagger
-   *
-   *    /pages.seen:
-   *      post:
-   *        tags: [Pages, CrowiCompatibles]
-   *        operationId: seenPage
-   *        summary: /pages.seen
-   *        description: Mark as seen user
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                properties:
-   *                  page_id:
-   *                    $ref: '#/components/schemas/Page/properties/_id'
-   *                required:
-   *                  - page_id
-   *        responses:
-   *          200:
-   *            description: Succeeded to be page seen.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *                    seenUser:
-   *                      $ref: '#/components/schemas/Page/properties/seenUsers'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * @api {post} /pages.seen Mark as seen user
-   * @apiName SeenPage
-   * @apiGroup Page
-   *
-   * @apiParam {String} page_id Page Id.
-   */
-  api.seen = async function(req, res) {
-    const user = req.user;
-    const pageId = req.body.page_id;
-    if (!pageId) {
-      return res.json(ApiResponse.error('page_id required'));
-    }
-    if (!req.user) {
-      return res.json(ApiResponse.error('user required'));
-    }
-
-    let page;
-    try {
-      page = await Page.findByIdAndViewer(pageId, user);
-      if (user != null) {
-        page = await page.seen(user);
-      }
-    }
-    catch (err) {
-      debug('Seen user update error', err);
-      return res.json(ApiResponse.error(err));
-    }
-
-    const result = {};
-    result.seenUser = page.seenUsers;
-
-    return res.json(ApiResponse.success(result));
-  };
-
   /**
    * @swagger
    *

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

@@ -13,6 +13,3 @@
 {% block content_main %}
   <div id="admin-app"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

+ 0 - 2
src/server/views/admin/customize.html

@@ -22,5 +22,3 @@
 </div>
 <div id="admin-customize" class="admin-customize"></div>
 {% endblock content_main %}
-
-{% block content_footer %} {% endblock content_footer %}

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

@@ -9,6 +9,3 @@
 {% block content_main %}
 <div id="admin-export-page" class="admin-export"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

+ 0 - 5
src/server/views/admin/external-accounts.html

@@ -9,8 +9,3 @@
 {% block content_main %}
 <div id="admin-external-account-setting"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}
-
-

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

@@ -10,6 +10,3 @@
 <div id="admin-global-notification-setting"
     data-global-notification="{{ globalNotification|json }}"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

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

@@ -9,6 +9,3 @@
 {% block content_main %}
 <div id="admin-importer" class="admin-importer"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

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

@@ -9,6 +9,3 @@
 {% block content_main %}
 <div id="admin-home"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

+ 0 - 10
src/server/views/admin/markdown.html

@@ -9,13 +9,3 @@
 {% block content_main %}
 <div id="admin-markdown-setting"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}
-
-
-
-
-
-
-

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

@@ -9,6 +9,3 @@
 {% block content_main %}
 <div id="admin-notification-setting" class="admin-notification"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

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

@@ -9,6 +9,3 @@
 {% block content_main %}
   <div id ="admin-full-text-search-management"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

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

@@ -9,6 +9,3 @@
 {% block content_main %}
 <div id="admin-security-setting" class="admin-security"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

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

@@ -13,6 +13,3 @@
 >
 </div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}

+ 0 - 5
src/server/views/admin/user-groups.html

@@ -9,8 +9,3 @@
 {% block content_main %}
 <div id ="admin-user-group-page"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}
-
-

+ 0 - 5
src/server/views/admin/users.html

@@ -9,8 +9,3 @@
 {% block content_main %}
 <div id ="admin-user-page"></div>
 {% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}
-
-

+ 25 - 19
src/server/views/layout-growi/base/layout.html

@@ -7,29 +7,35 @@
 {% endblock %}
 
 {% block layout_main %}
+<div class="h-100 d-flex flex-column justify-content-between">
 
-{% block content_header_wrapper %}
-<header class="py-0">
-  {% block content_header %}
-    <div id="grw-subnav-container"></div>
+  {% block content_header_wrapper %}
+    <header class="py-0">
+      {% block content_header %}
+        <div id="grw-subnav-container"></div>
+      {% endblock %}
+      <div id="grw-subnav-switcher-container" class="d-edit-none"></div>
+      <div id="grw-subnav-sticky-trigger" class="sticky-top"></div>
+      <div id="grw-fav-sticky-trigger" class="sticky-top"></div>
+    </header>
   {% endblock %}
-</header>
-<div id="grw-subnav-switcher-container" class="d-edit-none"></div>
-<div id="grw-subnav-sticky-trigger" class="sticky-top"></div>
-<div id="grw-fav-sticky-trigger" class="sticky-top"></div>
-{% endblock %}
 
-<div id="main" class="main {% if page %}{{ css.grant(page) }}{% endif %} {% block main_css_class %}{% endblock %}">
-  {% block content_main_before %}
-  {% endblock %}
+  <div class="flex-grow-1">
+    <div id="main" class="main {% if page %}{{ css.grant(page) }}{% endif %}">
+      {% block content_main_before %}
+      {% endblock %}
 
-  {% block content_main %}
-  {% endblock content_main %}
+      {% block content_main %}
+      {% endblock content_main %}
 
-  {% block content_main_after %}
-  {% endblock %}
-</div><!-- /.main -->
+      {% block content_main_after %}
+      {% endblock %}
+    </div>
+  </div>
+
+  <footer class="footer">
+    {% block content_footer %}{% endblock %}
+  </footer>
 
-<footer class="footer">
-</footer>
+</div>
 {% endblock %} {# layout_main #}

+ 1 - 5
src/server/views/layout-growi/forbidden.html

@@ -9,11 +9,7 @@
 
 {% block content_main %}
   <div class="container-lg">
-    <div class="row">
-      <div class="col">
-        {% include '../widget/forbidden_content.html' %}
-      </div>
-    </div>
+    {% include '../widget/forbidden_content.html' %}
   </div>
 {% endblock %}
 

+ 1 - 5
src/server/views/layout-growi/not_creatable.html

@@ -10,11 +10,7 @@
 
 {% block content_main %}
   <div class="container-lg">
-    <div class="row">
-      <div class="col">
-        {% include '../widget/not_creatable_content.html' %}
-      </div>
-    </div>
+    {% include '../widget/not_creatable_content.html' %}
   </div>
 {% endblock %}
 

+ 1 - 5
src/server/views/layout-growi/not_found.html

@@ -10,11 +10,7 @@
 
 {% block content_main %}
   <div class="container-lg">
-    <div class="row">
-      <div class="col">
-        {% include '../widget/not_found_content.html' %}
-      </div>
-    </div>
+    {% include '../widget/not_found_content.html' %}
   </div>
 {% endblock %}
 

+ 1 - 3
src/server/views/layout-growi/page.html

@@ -13,13 +13,11 @@
   </div>
 {% endblock %}
 
-
-{% block content_main_after %}
+{% block content_footer %}
   {% include 'widget/comments.html' %}
   <div id="page-content-footer"></div>
 {% endblock %}
 
-
 {% block body_end %}
   <div id="presentation-layer" class="fullscreen-layer">
     <div id="presentation-container"></div>

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

@@ -18,9 +18,11 @@
       <div id="trash-page-list"></div>
     </div>
   {% endif %}
-  <div id="page-content-footer"></div>
 {% endblock %}
 
+{% block content_footer %}
+  <div id="page-content-footer"></div>
+{% endblock %}
 
 {% block body_end %}
   <div id="presentation-layer" class="fullscreen-layer">

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

@@ -1,27 +1,21 @@
 {% extends 'page.html' %}
 
-{% block main_css_class %}
-  {% parent %}
-  user-page
-{% endblock %}
-
 {% block content_main %}
-  <div class="container-lg">
+  <div class="container-lg user-page">
 
     {% include '../widget/page_content.html' %}
 
   </div>
 {% endblock %}
 
-
-{% block content_main_after %}
+{% block content_footer %}
   {% include 'widget/comments.html' %}
 
   {% if page %}
     <div class="container-lg">
 
       <div class="grw-page-list-m mt-5 pb-5 d-edit-none">
-        <h2 class="grw-page-list-title-m border-bottom pb-2 mb-3" id="bookmarks-list">
+        <h2 class="grw-user-page-header border-bottom pb-2 mb-3" id="bookmarks-list">
           <i id="user-bookmark-icon"></i>
           Bookmarks
         </h2>
@@ -32,7 +26,7 @@
       </div>
 
       <div class="grw-page-list-m mt-5 pb-5 d-edit-none">
-        <h2 class="grw-page-list-title-m border-bottom pb-2 mb-3" id="recently-created-list">
+        <h2 class="grw-user-page-header border-bottom pb-2 mb-3" id="recently-created-list">
           <i id="recent-created-icon"></i>
           Recently Created
         </h2>
@@ -46,5 +40,4 @@
   {% endif %}
 
   <div id="page-content-footer"></div>
-
 {% endblock %}

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

@@ -16,7 +16,7 @@
 </header>
 {% endblock %}
 
-<div id="main" class="main {% block main_css_class %}{% endblock %}">
+<div id="main" class="main">
 
   <div class="container-fluid">
     <div class="row">
@@ -34,7 +34,4 @@
     </div>
   </div>
 </div><!-- /.main -->
-
-<footer class="footer">
-</footer>
 {% endblock %} {# layout_main #}

+ 15 - 12
src/server/views/me/drafts.html

@@ -1,18 +1,21 @@
-{% extends '../layout-growi/base/layout.html' %}
+{% extends '../layout/layout.html' %}
 
 {% block html_title %}{{ customizeService.generateCustomTitleForFixedPageName(t('My Drafts')) }}{% endblock %}
 
-{% block content_header %}
+{% block layout_main %}
+
+{% block content_header_wrapper %}
+<header class="py-3">
+  <div class="container-fluid">
+    <h1 class="title">{{ t('My Drafts') }}</h1>
+  </div>
+</header>
+<div id="grw-fav-sticky-trigger" class="sticky-top"></div>
 {% endblock %}
 
-{% block content_main %}
-<div id="content-main" class="content-main container">
-  <div id="my-drafts"></div>
+<div id="main" class="main">
+  <div id="content-main" class="content-main container-lg">
+    <div id="my-drafts"></div>
+  </div>
 </div>
-{% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}
-
-{% block layout_footer %}
-{% endblock layout_footer %}
+{% endblock %}

+ 15 - 13
src/server/views/me/index.html

@@ -1,19 +1,21 @@
-{% extends '../layout-growi/base/layout.html' %}
+{% extends '../layout/layout.html' %}
 
 {% block html_title %}{{ customizeService.generateCustomTitleForFixedPageName(t('User Settings')) }}{% endblock %}
 
-{% block html_base_css %}user-settings-page{% endblock %}
+{% block layout_main %}
 
-{% block content_header %}
-<h1 class="title">{{ t('User Settings') }}</h1>
+{% block content_header_wrapper %}
+<header class="py-3">
+  <div class="container-fluid">
+    <h1 class="title">{{ t('User Settings') }}</h1>
+  </div>
+</header>
+<div id="grw-fav-sticky-trigger" class="sticky-top"></div>
 {% endblock %}
 
-{% block content_main %}
-<div class="content-main" id="personal-setting"></div>
-{% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %}
-
-{% block layout_footer %}
-{% endblock layout_footer %}
+<div id="main" class="main">
+  <div id="content-main" class="content-main container-lg">
+    <div class="content-main" id="personal-setting"></div>
+  </div>
+</div>
+{% endblock %}

+ 0 - 3
src/server/views/search.html

@@ -23,7 +23,4 @@
   </div>
 
 </div><!-- /.container-fluid -->
-
-<footer class="footer">
-</footer>
 {% endblock %} {# layout_main #}

+ 0 - 3
src/server/views/tags.html

@@ -16,7 +16,4 @@
     </div>
   </div>
 </div><!-- /.container-fluid -->
-
-<footer class="footer">
-</footer>
 {% endblock %} {# layout_main #}

+ 2 - 4
src/server/views/widget/page_content.html

@@ -55,12 +55,10 @@
 </div>
 
 {% if revision %}
-<div class="d-none d-lg-block d-editor-none grw-side-contents-container">
+<div class="d-none d-lg-block d-edit-none grw-side-contents-container">
   <div class="grw-side-contents-sticky-container">
     <div id="page-accessories" class="page-accessories"></div>
-    <div id="revision-toc" class="revision-toc sps sps--abv" data-sps-offset="123">
-      <div id="revision-toc-content" class="revision-toc-content"></div>
-    </div>
+    <div id="revision-toc" class="revision-toc sps sps--abv" data-sps-offset="123"></div>
     {% if pageUser %}
       <div id="grw-user-contents-links"></div>
     {% endif %}