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

Merge pull request #6518 from weseek/support/create-UsersHomePageFooter-integrate

imprv: Update Footer content integrate
Yuki Takei 3 лет назад
Родитель
Сommit
d11507f8ea
38 измененных файлов с 389 добавлено и 385 удалено
  1. 4 1
      packages/app/public/static/locales/en_US/translation.json
  2. 4 1
      packages/app/public/static/locales/ja_JP/translation.json
  3. 4 1
      packages/app/public/static/locales/zh_CN/translation.json
  4. 1 1
      packages/app/src/components/Admin/UserGroupDetail/UserGroupPageList.tsx
  5. 14 4
      packages/app/src/components/Comments.tsx
  6. 1 1
      packages/app/src/components/ContentLinkButtons.tsx
  7. 1 3
      packages/app/src/components/Icons/RecentlyCreatedIcon.tsx
  8. 1 0
      packages/app/src/components/Navbar/DrawerToggler.tsx
  9. 1 1
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  10. 1 2
      packages/app/src/components/Navbar/GrowiNavbar.tsx
  11. 27 32
      packages/app/src/components/Navbar/GrowiSubNavigation.tsx
  12. 1 3
      packages/app/src/components/NotFoundPage.tsx
  13. 6 6
      packages/app/src/components/Page.tsx
  14. 0 1
      packages/app/src/components/Page/RevisionLoader.jsx
  15. 1 2
      packages/app/src/components/Page/RevisionRenderer.tsx
  16. 15 16
      packages/app/src/components/Page/TagLabels.tsx
  17. 15 18
      packages/app/src/components/PageComment.tsx
  18. 28 31
      packages/app/src/components/PageComment/Comment.tsx
  19. 1 6
      packages/app/src/components/PageComment/CommentEditor.tsx
  20. 1 3
      packages/app/src/components/PageComment/CommentPreview.tsx
  21. 1 1
      packages/app/src/components/PageComment/DeleteCommentModal.tsx
  22. 8 8
      packages/app/src/components/PageComment/ReplyComments.tsx
  23. 27 12
      packages/app/src/components/PageContentFooter.tsx
  24. 2 2
      packages/app/src/components/PageEditor/Preview.tsx
  25. 15 28
      packages/app/src/components/PageList/BookmarkList.tsx
  26. 0 39
      packages/app/src/components/PageList/PageListItemS.jsx
  27. 32 0
      packages/app/src/components/PageList/PageListItemS.tsx
  28. 1 0
      packages/app/src/components/PagePathHierarchicalLink.tsx
  29. 0 91
      packages/app/src/components/RecentCreated/RecentCreated.jsx
  30. 74 0
      packages/app/src/components/RecentCreated/RecentCreated.tsx
  31. 23 14
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  32. 0 1
      packages/app/src/components/Sidebar/CustomSidebar.tsx
  33. 11 0
      packages/app/src/components/UsersHomePageFooter.module.scss
  34. 40 0
      packages/app/src/components/UsersHomePageFooter.tsx
  35. 12 0
      packages/app/src/interfaces/bookmark-info.ts
  36. 0 1
      packages/app/src/pages/[[...path]].page.module.scss
  37. 16 45
      packages/app/src/pages/[[...path]].page.tsx
  38. 0 10
      packages/app/src/styles/_user.scss

+ 4 - 1
packages/app/public/static/locales/en_US/translation.json

@@ -150,7 +150,6 @@
   "add_bookmark": "Add to Bookmarks",
   "remove_bookmark": "Remove from Bookmarks",
   "wide_view": "Wide View",
-  "Recent Created": "Recent Created",
   "Recent Changes": "Recent Changes",
   "Page Tree": "Page Tree",
   "original_path":"Original path",
@@ -842,5 +841,9 @@
   "page_operation":{
     "paths_recovered": "Paths recovered successfully",
     "path_recovery_failed":"Path recovery failed"
+  },
+  "footer": {
+    "bookmarks": "Bookmarks",
+    "recently_created": "Recently Created"
   }
 }

+ 4 - 1
packages/app/public/static/locales/ja_JP/translation.json

@@ -143,7 +143,6 @@
   "add_bookmark": "ブックマークに追加",
   "remove_bookmark": "ブックマークから削除",
   "wide_view": "ワイドビュー",
-  "Recent Created": "最新の作成",
   "Recent Changes": "最新の変更",
   "Page Tree": "ページツリー",
   "original_path":"元のパス",
@@ -833,5 +832,9 @@
   "page_operation":{
     "paths_recovered": "パスを修復しました",
     "path_recovery_failed":"パスを修復できませんでした"
+  },
+  "footer": {
+    "bookmarks": "ブックマーク",
+    "recently_created": "最近作成したページ"
   }
 }

+ 4 - 1
packages/app/public/static/locales/zh_CN/translation.json

@@ -153,7 +153,6 @@
   "add_bookmark": "添加到书签",
   "remove_bookmark": "从书签中删除",
   "wide_view": "视野开阔",
-	"Recent Created": "最新创建",
   "Recent Changes": "最新修改",
   "Page Tree": "页面树",
   "original_path":"Original path",
@@ -889,5 +888,9 @@
   "page_operation":{
     "paths_recovered": "成功恢复了页面路径",
     "path_recovery_failed":"路径恢复失败"
+  },
+  "footer": {
+    "bookmarks": "书签",
+    "recently_created": "最近创建页面"
   }
 }

+ 1 - 1
packages/app/src/components/Admin/UserGroupDetail/UserGroupPageList.tsx

@@ -6,7 +6,7 @@ import { toastError } from '~/client/util/apiNotification';
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { IPageHasId } from '~/interfaces/page';
 
-import PageListItemS from '../../PageList/PageListItemS';
+import { PageListItemS } from '../../PageList/PageListItemS';
 import PaginationWrapper from '../../PaginationWrapper';
 
 const pagingLimit = 10;

+ 14 - 4
packages/app/src/components/Comments.tsx

@@ -1,11 +1,12 @@
 import React from 'react';
 
+import { IRevisionHasId } from '@growi/core';
 import dynamic from 'next/dynamic';
 
 import { PageComment } from '~/components/PageComment';
 import { useSWRxPageComment } from '~/stores/comment';
 
-import { useIsTrashPage } from '../stores/context';
+import { useIsTrashPage, useCurrentUser } from '../stores/context';
 
 import { CommentEditorProps } from './PageComment/CommentEditor';
 
@@ -14,15 +15,17 @@ const CommentEditor = dynamic<CommentEditorProps>(() => import('./PageComment/Co
 
 
 type CommentsProps = {
-  pageId?: string,
+  pageId: string,
+  revision: IRevisionHasId,
 }
 
 export const Comments = (props: CommentsProps): JSX.Element => {
 
-  const { pageId } = props;
+  const { pageId, revision } = props;
 
   const { mutate } = useSWRxPageComment(pageId);
   const { data: isDeleted } = useIsTrashPage();
+  const { data: currentUser } = useCurrentUser();
 
   if (pageId == null) {
     return <></>;
@@ -34,7 +37,14 @@ export const Comments = (props: CommentsProps): JSX.Element => {
       <div className="container-lg">
         <div className="page-comments">
           <div id="page-comments-list" className="page-comments-list">
-            <PageComment pageId={pageId} isReadOnly={false} titleAlign="left" />
+            <PageComment
+              pageId={pageId}
+              revision={revision}
+              currentUser={currentUser}
+              isReadOnly={false}
+              titleAlign="left"
+              hideIfEmpty={false}
+            />
           </div>
           { !isDeleted && (
             <div id="page-comment-write">

+ 1 - 1
packages/app/src/components/ContentLinkButtons.tsx

@@ -1,9 +1,9 @@
 import React, { useCallback, useMemo } from 'react';
 
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
+import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
 import { usePageUser } from '~/stores/context';
 
-import RecentlyCreatedIcon from './Icons/RecentlyCreatedIcon';
 
 const WIKI_HEADER_LINK = 120;
 

+ 1 - 3
packages/app/src/components/Icons/RecentlyCreatedIcon.jsx → packages/app/src/components/Icons/RecentlyCreatedIcon.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-const RecentlyCreatedIcon = () => (
+export const RecentlyCreatedIcon = (): JSX.Element => (
   <svg
     xmlns="http://www.w3.org/2000/svg"
     width="20"
@@ -40,5 +40,3 @@ const RecentlyCreatedIcon = () => (
     </g>
   </svg>
 );
-
-export default RecentlyCreatedIcon;

+ 1 - 0
packages/app/src/components/Navbar/DrawerToggler.tsx

@@ -1,4 +1,5 @@
 import React, { FC } from 'react';
+
 import { useDrawerOpened } from '~/stores/ui';
 
 type Props = {

+ 1 - 1
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -1,6 +1,5 @@
 import React, { useState, useEffect, useCallback } from 'react';
 
-
 import { isPopulated } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
@@ -40,6 +39,7 @@ import { Skelton } from '../Skelton';
 import { GrowiSubNavigation } from './GrowiSubNavigation';
 import { SubNavButtonsProps } from './SubNavButtons';
 
+
 import PageEditorModeManagerStyles from './PageEditorModeManager.module.scss';
 
 

+ 1 - 2
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -78,11 +78,10 @@ const NavbarRight = memo((): JSX.Element => {
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
           <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
         </li>
-
         <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>;
       </>
     );
-  }, [AppearanceModeDropdown, isAuthenticated]);
+  }, [isAuthenticated]);
 
   return (
     <>

+ 27 - 32
packages/app/src/components/Navbar/GrowiSubNavigation.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 
 import dynamic from 'next/dynamic';
 
@@ -8,47 +8,43 @@ import {
   EditorMode, useEditorMode,
 } from '~/stores/ui';
 
+import { TagLabelsSkelton } from '../Page/TagLabels';
 import PagePathNav from '../PagePathNav';
 import { Skelton } from '../Skelton';
 
 import DrawerToggler from './DrawerToggler';
 
-
-import TagLabelsStyles from '../Page/TagLabels.module.scss';
 import AuthorInfoStyles from './AuthorInfo.module.scss';
 import styles from './GrowiSubNavigation.module.scss';
 
+const TagLabels = dynamic(() => import('../Page/TagLabels').then(mod => mod.TagLabels), {
+  ssr: false,
+  loading: () => <TagLabelsSkelton />,
+});
+const AuthorInfo = dynamic(() => import('./AuthorInfo'), {
+  ssr: false,
+  loading: () => <Skelton additionalClass={`${AuthorInfoStyles['grw-author-info-skelton']} py-1`} />,
+});
+
 
 export type GrowiSubNavigationProps = {
   page: Partial<IPageHasId>,
-
   showDrawerToggler?: boolean,
   showTagLabel?: boolean,
   showPageAuthors?: boolean,
-
   isGuestUser?: boolean,
   isDrawerMode?: boolean,
   isCompactMode?: boolean,
-
   tags?: string[],
   tagsUpdatedHandler?: (newTags: string[]) => Promise<void> | void,
-
-  controls?: React.FunctionComponent,
+  controls: React.FunctionComponent,
   additionalClasses?: string[],
 }
 
 export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element => {
 
-  const TagLabels = dynamic(() => import('../Page/TagLabels'), {
-    ssr: false,
-    loading: () => <Skelton additionalClass={`${TagLabelsStyles['grw-tag-labels-skelton']} py-1`} />,
-  });
-  const AuthorInfo = dynamic(() => import('./AuthorInfo'), {
-    ssr: false,
-    loading: () => <Skelton additionalClass={`${AuthorInfoStyles['grw-author-info-skelton']} py-1`} />,
-  });
-
   const { data: editorMode } = useEditorMode();
+  const [isControls, setControls] = useState(false);
 
   const {
     page,
@@ -59,35 +55,37 @@ export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element
     additionalClasses = [],
   } = props;
 
+  const isViewMode = editorMode === EditorMode.View;
+  const isEditorMode = !isViewMode;
+  const compactModeClasses = isCompactMode ? 'grw-subnav-compact d-print-none' : '';
+
   const {
     _id: pageId, path, creator, lastUpdateUser,
     createdAt, updatedAt,
   } = page;
 
-  const isViewMode = editorMode === EditorMode.View;
-  const isEditorMode = !isViewMode;
+  useEffect(() => {
+    if (Controls != null) {
+      setControls(true);
+    }
+  }, [Controls]);
 
   if (path == null) {
     return <></>;
   }
 
   return (
-    <div className={
-      `grw-subnav ${styles['grw-subnav']} d-flex align-items-center justify-content-between`
-      + ` ${additionalClasses.join(' ')}`
-      + ` ${isCompactMode ? 'grw-subnav-compact d-print-none' : ''}`}
-    >
-
+    <div className={`grw-subnav ${styles['grw-subnav']} d-flex align-items-center justify-content-between ${additionalClasses.join(' ')}
+    ${compactModeClasses}`} >
       {/* Left side */}
       <div className="d-flex grw-subnav-left-side">
-        { showDrawerToggler && isDrawerMode && (
+        { (showDrawerToggler && isDrawerMode) && (
           <div className={`d-none d-md-flex align-items-center ${isEditorMode ? 'mr-2 pr-2' : 'border-right mr-4 pr-4'}`}>
             <DrawerToggler />
           </div>
         ) }
-
         <div className="grw-path-nav-container">
-          { showTagLabel && !isCompactMode && (
+          { (showTagLabel && !isCompactMode) && (
             <div className="grw-taglabels-container">
               <TagLabels tags={tags} isGuestUser={isGuestUser ?? false} tagsUpdateInvoked={tagsUpdatedHandler} />
             </div>
@@ -95,12 +93,9 @@ export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element
           <PagePathNav pageId={pageId} pagePath={path} isSingleLineMode={isEditorMode} isCompactMode={isCompactMode} />
         </div>
       </div>
-
       {/* Right side */}
       <div className="d-flex">
-
-        { Controls && <Controls></Controls> }
-
+        { isControls && <Controls /> }
         {/* Page Authors */}
         { (showPageAuthors && !isCompactMode) && (
           <ul className={`${AuthorInfoStyles['grw-author-info']} text-nowrap border-left d-none d-lg-block d-edit-none py-2 pl-4 mb-0 ml-3`}>

+ 1 - 3
packages/app/src/components/NotFoundPage.tsx

@@ -1,13 +1,12 @@
 import React, { useMemo } from 'react';
 
 import { useTranslation } from 'next-i18next';
-import dynamic from 'next/dynamic';
 
+import CustomNavAndContents from './CustomNavigation/CustomNavAndContents';
 import { DescendantsPageListForCurrentPath } from './DescendantsPageList';
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import { PageTimeline } from './PageTimeline';
-import CustomNavAndContents from './CustomNavigation/CustomNavAndContents';
 
 const NotFoundPage = (): JSX.Element => {
   const { t } = useTranslation();
@@ -29,7 +28,6 @@ const NotFoundPage = (): JSX.Element => {
     };
   }, [t]);
 
-
   return (
     <div className="d-edit-none">
       <CustomNavAndContents navTabMapping={navTabMapping} tabContentClasses={['py-4']} />

+ 6 - 6
packages/app/src/components/Page.tsx

@@ -30,6 +30,11 @@ import RevisionRenderer from './Page/RevisionRenderer';
 // import mdu from './PageEditor/MarkdownDrawioUtil';
 // import mtu from './PageEditor/MarkdownTableUtil';
 
+// const DrawioModal = dynamic(() => import('./PageEditor/DrawioModal'), { ssr: false });
+const GridEditModal = dynamic(() => import('./PageEditor/GridEditModal'), { ssr: false });
+// const HandsontableModal = dynamic(() => import('./PageEditor/HandsontableModal'), { ssr: false });
+const LinkEditModal = dynamic(() => import('./PageEditor/LinkEditModal'), { ssr: false });
+
 const logger = loggerFactory('growi:Page');
 
 type PageSubstanceProps = {
@@ -166,16 +171,11 @@ class PageSubstance extends React.Component<PageSubstanceProps> {
     const { path } = page;
     const { _id: revisionId, body: markdown } = page.revision;
 
-    // const DrawioModal = dynamic(() => import('./PageEditor/DrawioModal'), { ssr: false });
-    const GridEditModal = dynamic(() => import('./PageEditor/GridEditModal'), { ssr: false });
-    // const HandsontableModal = dynamic(() => import('./PageEditor/HandsontableModal'), { ssr: false });
-    const LinkEditModal = dynamic(() => import('./PageEditor/LinkEditModal'), { ssr: false });
-
     return (
       <div className={`mb-5 ${isMobile ? 'page-mobile' : ''}`}>
 
         { revisionId != null && (
-          <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} pagePath={path} />
+          <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
         )}
 
         { !isGuestUser && (

+ 0 - 1
packages/app/src/components/Page/RevisionLoader.jsx

@@ -111,7 +111,6 @@ class RevisionLoader extends React.Component {
       <RevisionRenderer
         rendererOptions={this.props.rendererOptions}
         markdown={markdown}
-        pagePath={this.props.pagePath}
         highlightKeywords={this.props.highlightKeywords}
       />
     );

+ 1 - 2
packages/app/src/components/Page/RevisionRenderer.tsx

@@ -85,7 +85,6 @@ const logger = loggerFactory('components:Page:RevisionRenderer');
 type Props = {
   rendererOptions: RendererOptions,
   markdown: string,
-  pagePath: string,
   highlightKeywords?: string | string[],
   additionalClassName?: string,
 }
@@ -93,7 +92,7 @@ type Props = {
 const RevisionRenderer = React.memo((props: Props): JSX.Element => {
 
   const {
-    rendererOptions, markdown, pagePath, highlightKeywords, additionalClassName,
+    rendererOptions, markdown, highlightKeywords, additionalClassName,
   } = props;
 
   return (

+ 15 - 16
packages/app/src/components/Page/TagLabels.tsx

@@ -1,5 +1,7 @@
 import React, { FC, useState } from 'react';
 
+import { Skelton } from '../Skelton';
+
 import RenderTagLabels from './RenderTagLabels';
 import TagEditModal from './TagEditModal';
 
@@ -11,8 +13,11 @@ type Props = {
   tagsUpdateInvoked?: (tags: string[]) => Promise<void> | void,
 }
 
+export const TagLabelsSkelton = (): JSX.Element => {
+  return <Skelton additionalClass={`${styles['grw-tag-labels-skelton']} py-1`} />;
+};
 
-const TagLabels:FC<Props> = (props: Props) => {
+export const TagLabels:FC<Props> = (props: Props) => {
   const { tags, isGuestUser, tagsUpdateInvoked } = props;
 
   const [isTagEditModalShown, setIsTagEditModalShown] = useState(false);
@@ -25,24 +30,20 @@ const TagLabels:FC<Props> = (props: Props) => {
     setIsTagEditModalShown(false);
   };
 
+  if (tags == null) {
+    return <TagLabelsSkelton />;
+  }
+
   return (
     <>
       <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center`}>
         <i className="tag-icon icon-tag mr-2"></i>
-        { tags == null
-          ? (
-            <span className="grw-tag-label badge badge-secondary">―</span>
-          )
-          : (
-            <RenderTagLabels
-              tags={tags}
-              openEditorModal={openEditorModal}
-              isGuestUser={isGuestUser}
-            />
-          )
-        }
+        <RenderTagLabels
+          tags={tags}
+          openEditorModal={openEditorModal}
+          isGuestUser={isGuestUser}
+        />
       </div>
-
       <TagEditModal
         tags={tags}
         isOpen={isTagEditModalShown}
@@ -52,5 +53,3 @@ const TagLabels:FC<Props> = (props: Props) => {
     </>
   );
 };
-
-export default TagLabels;

+ 15 - 18
packages/app/src/components/PageComment.tsx

@@ -2,13 +2,12 @@ import React, {
   FC, useEffect, useState, useMemo, memo, useCallback,
 } from 'react';
 
+import { IRevisionHasId, isPopulated, getIdForRef } from '@growi/core';
 import dynamic from 'next/dynamic';
 import { Button } from 'reactstrap';
 
 import { toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
-import { useCurrentPagePath } from '~/stores/context';
-import { useSWRxCurrentPage } from '~/stores/page';
 
 import { ICommentHasId, ICommentHasIdList } from '../interfaces/comment';
 import { useSWRxPageComment } from '../stores/comment';
@@ -26,9 +25,10 @@ const DeleteCommentModal = dynamic<DeleteCommentModalProps>(
   () => import('./PageComment/DeleteCommentModal').then(mod => mod.DeleteCommentModal), { ssr: false },
 );
 
-
-type PageCommentProps = {
+export type PageCommentProps = {
   pageId: string,
+  revision: string | IRevisionHasId,
+  currentUser: any,
   isReadOnly: boolean,
   titleAlign?: 'center' | 'left' | 'right',
   highlightKeywords?: string[],
@@ -38,12 +38,10 @@ type PageCommentProps = {
 export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps): JSX.Element => {
 
   const {
-    pageId, highlightKeywords, isReadOnly, titleAlign, hideIfEmpty,
+    pageId, revision, currentUser, highlightKeywords, isReadOnly, titleAlign, hideIfEmpty,
   } = props;
 
   const { data: comments, mutate } = useSWRxPageComment(pageId);
-  const { data: currentPage } = useSWRxCurrentPage();
-  const { data: currentPagePath } = useCurrentPagePath();
 
   const [commentToBeDeleted, setCommentToBeDeleted] = useState<ICommentHasId | null>(null);
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState<boolean>(false);
@@ -130,8 +128,8 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
   let commentTitleClasses = 'border-bottom py-3 mb-3';
   commentTitleClasses = titleAlign != null ? `${commentTitleClasses} text-${titleAlign}` : `${commentTitleClasses} text-center`;
 
-  if (commentsFromOldest == null || commentsExceptReply == null || currentPagePath == null || currentPage == null) {
-    if (hideIfEmpty && comments?.length === 0) {
+  if (commentsFromOldest == null || commentsExceptReply == null) {
+    if (hideIfEmpty) {
       return <></>;
     }
     return (
@@ -139,31 +137,30 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
     );
   }
 
-  if (currentPage.revision == null) {
-    return <></>;
-  }
+  const revisionId = getIdForRef(revision);
+  const revisionCreatedAt = (isPopulated(revision)) ? revision.createdAt : undefined;
 
   const generateCommentElement = (comment: ICommentHasId) => (
     <Comment
       comment={comment}
+      revisionId={revisionId}
+      revisionCreatedAt={revisionCreatedAt as Date}
+      currentUser={currentUser}
       isReadOnly={isReadOnly}
       deleteBtnClicked={onClickDeleteButton}
       onComment={mutate}
-      currentPagePath={currentPagePath}
-      currentRevisionId={currentPage.revision._id}
-      currentRevisionCreatedAt={currentPage.revision.createdAt}
     />
   );
 
   const generateReplyCommentsElement = (replyComments: ICommentHasIdList) => (
     <ReplyComments
       isReadOnly={isReadOnly}
+      revisionId={revisionId}
+      revisionCreatedAt={revisionCreatedAt as Date}
+      currentUser={currentUser}
       replyList={replyComments}
       deleteBtnClicked={onClickDeleteButton}
       onComment={mutate}
-      currentPagePath={currentPagePath}
-      currentRevisionId={currentPage.revision._id}
-      currentRevisionCreatedAt={currentPage.revision.createdAt}
     />
   );
 

+ 28 - 31
packages/app/src/components/PageComment/Comment.tsx

@@ -1,12 +1,12 @@
 import React, { useEffect, useMemo, useState } from 'react';
 
+import { IUser } from '@growi/core';
 import { UserPicture } from '@growi/ui';
 import { format } from 'date-fns';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import { UncontrolledTooltip } from 'reactstrap';
 
-import { useCurrentUser } from '~/stores/context';
 import { useCommentPreviewOptions } from '~/stores/renderer';
 
 import { ICommentHasId } from '../../interfaces/comment';
@@ -22,26 +22,23 @@ import styles from './Comment.module.scss';
 
 const CommentEditor = dynamic<CommentEditorProps>(() => import('./CommentEditor').then(mod => mod.CommentEditor), { ssr: false });
 
-
 type CommentProps = {
   comment: ICommentHasId,
+  revisionId: string,
+  revisionCreatedAt: Date,
+  currentUser: IUser,
   isReadOnly: boolean,
   deleteBtnClicked: (comment: ICommentHasId) => void,
   onComment: () => void,
-  currentPagePath: string,
-  currentRevisionId: string,
-  currentRevisionCreatedAt: Date,
 }
 
 export const Comment = (props: CommentProps): JSX.Element => {
 
   const {
-    comment, isReadOnly, deleteBtnClicked, onComment,
-    currentPagePath, currentRevisionId, currentRevisionCreatedAt,
+    comment, revisionId, revisionCreatedAt, currentUser, isReadOnly, deleteBtnClicked, onComment,
   } = props;
 
   const { t } = useTranslation();
-  const { data: currentUser } = useCurrentUser();
   const { data: rendererOptions } = useCommentPreviewOptions();
 
   const [markdown, setMarkdown] = useState('');
@@ -55,18 +52,20 @@ export const Comment = (props: CommentProps): JSX.Element => {
   const isEdited = createdAt < updatedAt;
 
   useEffect(() => {
+    if (revisionId == null) {
+      return;
+    }
+
     setMarkdown(comment.comment);
 
     const isCurrentRevision = () => {
-      return comment.revision === currentRevisionId;
+      return comment.revision === revisionId;
     };
     isCurrentRevision();
-
-  }, [comment, currentRevisionId]);
+  }, [comment, revisionId]);
 
   const isCurrentUserEqualsToAuthor = () => {
     const { creator }: any = comment;
-
     if (creator == null || currentUser == null) {
       return false;
     }
@@ -76,14 +75,17 @@ export const Comment = (props: CommentProps): JSX.Element => {
   const getRootClassName = (comment: ICommentHasId) => {
     let className = 'page-comment flex-column';
 
-    if (comment.revision === currentRevisionId) {
-      className += ' page-comment-current';
-    }
-    else if (comment.createdAt.getTime() > currentRevisionCreatedAt.getTime()) {
-      className += ' page-comment-newer';
-    }
-    else {
-      className += ' page-comment-older';
+    // Conditional for called from SearchResultContext
+    if (revisionId != null && revisionCreatedAt != null) {
+      if (comment.revision === revisionId) {
+        className += ' page-comment-current';
+      }
+      else if (comment.createdAt.getTime() > revisionCreatedAt.getTime()) {
+        className += ' page-comment-newer';
+      }
+      else {
+        className += ' page-comment-older';
+      }
     }
 
     if (isCurrentUserEqualsToAuthor()) {
@@ -112,23 +114,19 @@ export const Comment = (props: CommentProps): JSX.Element => {
           rendererOptions={rendererOptions}
           markdown={markdown}
           additionalClassName="comment"
-          pagePath={currentPagePath}
         />
       )
       : renderText(comment.comment);
-  }, [comment, currentPagePath, isMarkdown, markdown, rendererOptions]);
+  }, [comment, isMarkdown, markdown, rendererOptions]);
 
   const rootClassName = getRootClassName(comment);
   const revHref = `?revision=${comment.revision}`;
-
   const editedDateId = `editedDate-${comment._id}`;
-  const editedDateFormatted = isEdited
-    ? format(updatedAt, 'yyyy/MM/dd HH:mm')
-    : null;
+  const editedDateFormatted = isEdited ? format(updatedAt, 'yyyy/MM/dd HH:mm') : null;
 
   return (
     <div className={`${styles['comment-styles']}`}>
-      {(isReEdit && !isReadOnly) ? (
+      { (isReEdit && !isReadOnly) ? (
         <CommentEditor
           pageId={comment._id}
           replyTo={undefined}
@@ -159,7 +157,7 @@ export const Comment = (props: CommentProps): JSX.Element => {
                   <span id={editedDateId}>&nbsp;(edited)</span>
                   <UncontrolledTooltip placement="bottom" fade={false} target={editedDateId}>{editedDateFormatted}</UncontrolledTooltip>
                 </>
-              )}
+              ) }
               <span className="ml-2">
                 <a id={`page-comment-revision-${commentId}`} className="page-comment-revision" href={revHref}>
                   <HistoryIcon />
@@ -169,7 +167,7 @@ export const Comment = (props: CommentProps): JSX.Element => {
                 </UncontrolledTooltip>
               </span>
             </div>
-            {(isCurrentUserEqualsToAuthor() && !isReadOnly) && (
+            { (isCurrentUserEqualsToAuthor() && !isReadOnly) && (
               <CommentControl
                 onClickDeleteBtn={deleteBtnClickedHandler}
                 onClickEditBtn={() => setIsReEdit(true)}
@@ -177,8 +175,7 @@ export const Comment = (props: CommentProps): JSX.Element => {
             ) }
           </div>
         </div>
-      )
-      }
+      ) }
     </div>
   );
 };

+ 1 - 6
packages/app/src/components/PageComment/CommentEditor.tsx

@@ -208,12 +208,7 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
       return <></>;
     }
 
-    return (
-      <CommentPreview
-        markdown={comment}
-        path={currentPagePath}
-      />
-    );
+    return <CommentPreview markdown={comment} />;
   }, [currentPagePath, comment]);
 
   const renderBeforeReady = useCallback((): JSX.Element => {

+ 1 - 3
packages/app/src/components/PageComment/CommentPreview.tsx

@@ -8,12 +8,11 @@ import styles from './CommentPreview.module.scss';
 
 type CommentPreviewPorps = {
   markdown: string,
-  path: string,
 }
 
 export const CommentPreview = (props: CommentPreviewPorps): JSX.Element => {
 
-  const { markdown, path } = props;
+  const { markdown } = props;
 
   const { data: rendererOptions } = useCommentPreviewOptions();
 
@@ -27,7 +26,6 @@ export const CommentPreview = (props: CommentPreviewPorps): JSX.Element => {
         rendererOptions={rendererOptions}
         markdown={markdown}
         additionalClassName="comment"
-        pagePath={path}
       />
     </div>
   );

+ 1 - 1
packages/app/src/components/PageComment/DeleteCommentModal.tsx

@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React from 'react';
 
 import { UserPicture } from '@growi/ui';
 import { format } from 'date-fns';

+ 8 - 8
packages/app/src/components/PageComment/ReplyComments.tsx

@@ -4,6 +4,7 @@ import React, { useState } from 'react';
 import { Collapse } from 'reactstrap';
 
 import { ICommentHasId, ICommentHasIdList } from '../../interfaces/comment';
+import { IUser } from '../../interfaces/user';
 import { useIsAllReplyShown } from '../../stores/context';
 
 import { Comment } from './Comment';
@@ -13,19 +14,18 @@ import styles from './ReplyComments.module.scss';
 
 type ReplycommentsProps = {
   isReadOnly: boolean,
+  revisionId: string,
+  revisionCreatedAt: Date,
+  currentUser: IUser,
   replyList: ICommentHasIdList,
   deleteBtnClicked: (comment: ICommentHasId) => void,
   onComment: () => void,
-  currentPagePath: string,
-  currentRevisionId: string,
-  currentRevisionCreatedAt: Date,
 }
 
 export const ReplyComments = (props: ReplycommentsProps): JSX.Element => {
 
   const {
-    isReadOnly, replyList, deleteBtnClicked, onComment,
-    currentPagePath, currentRevisionId, currentRevisionCreatedAt,
+    isReadOnly, revisionId, revisionCreatedAt, currentUser, replyList, deleteBtnClicked, onComment,
   } = props;
 
   const { data: isAllReplyShown } = useIsAllReplyShown();
@@ -37,12 +37,12 @@ export const ReplyComments = (props: ReplycommentsProps): JSX.Element => {
       <div key={reply._id} className={`${styles['page-comment-reply']} ml-4 ml-sm-5 mr-3`}>
         <Comment
           comment={reply}
+          revisionId={revisionId}
+          revisionCreatedAt={revisionCreatedAt}
+          currentUser={currentUser}
           isReadOnly={isReadOnly}
           deleteBtnClicked={deleteBtnClicked}
           onComment={onComment}
-          currentPagePath={currentPagePath}
-          currentRevisionId={currentRevisionId}
-          currentRevisionCreatedAt={currentRevisionCreatedAt}
         />
       </div>
     );

+ 27 - 12
packages/app/src/components/PageContentFooter.tsx

@@ -1,5 +1,6 @@
-import React, { memo } from 'react';
+import React from 'react';
 
+import { IPage, IUser } from '@growi/core';
 import dynamic from 'next/dynamic';
 
 import { useSWRxCurrentPage } from '~/stores/page';
@@ -8,27 +9,41 @@ import { Skelton } from './Skelton';
 
 import styles from './PageContentFooter.module.scss';
 
-const AuthorInfo = dynamic(() => import('./Navbar/AuthorInfo'),
-  { ssr: false, loading: () => <Skelton additionalClass={`${styles['page-content-footer-skelton']} mb-3`} /> });
+const AuthorInfo = dynamic(() => import('./Navbar/AuthorInfo'), {
+  ssr: false,
+  loading: () => <Skelton additionalClass={`${styles['page-content-footer-skelton']} mb-3`} />,
+});
 
-export const PageContentFooter = memo((): JSX.Element => {
+export type PageContentFooterProps = {
+  page: IPage,
+}
 
-  const { data: page } = useSWRxCurrentPage();
+export const PageContentFooter = (props: PageContentFooterProps): JSX.Element => {
 
-  if (page == null || page.revision == null) {
-    return <></>;
-  }
+  const { page } = props;
+
+  const {
+    creator, lastUpdateUser, createdAt, updatedAt,
+  } = page;
 
   return (
     <div className={`${styles['page-content-footer']} page-content-footer py-4 d-edit-none d-print-none}`}>
       <div className="grw-container-convertible">
         <div className="page-meta">
-          <AuthorInfo user={page.creator} date={page.createdAt} mode="create" locate="footer" />
-          <AuthorInfo user={page.revision.author} date={page.updatedAt} mode="update" locate="footer" />
+          <AuthorInfo user={creator as IUser} date={createdAt} mode="create" locate="footer" />
+          <AuthorInfo user={lastUpdateUser as IUser} date={updatedAt} mode="update" locate="footer" />
         </div>
       </div>
     </div>
   );
-});
+};
+
+export const CurrentPageContentFooter = (): JSX.Element => {
+  const { data: currentPage } = useSWRxCurrentPage();
+
+  if (currentPage == null) {
+    return <></>;
+  }
 
-PageContentFooter.displayName = 'PageContentFooter';
+  return <PageContentFooter page={currentPage} />;
+};

+ 2 - 2
packages/app/src/components/PageEditor/Preview.tsx

@@ -35,8 +35,8 @@ const Preview = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>):
         }
       }}
     >
-      { markdown != null && pagePath != null && (
-        <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} pagePath={pagePath}></RevisionRenderer>
+      { markdown != null && (
+        <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown}></RevisionRenderer>
       ) }
     </div>
   );

+ 15 - 28
packages/app/src/components/PageList/BookmarkList.jsx → packages/app/src/components/PageList/BookmarkList.tsx

@@ -1,27 +1,27 @@
 import React, { useState, useCallback, useEffect } from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
 
 import { toastError } from '~/client/util/apiNotification';
 import { apiv3Get } from '~/client/util/apiv3-client';
+import { MyBookmarkList } from '~/interfaces/bookmark-info';
 import loggerFactory from '~/utils/logger';
 
 import PaginationWrapper from '../PaginationWrapper';
 
-
-import PageListItemS from './PageListItemS';
-
+import { PageListItemS } from './PageListItemS';
 
 const logger = loggerFactory('growi:BookmarkList');
 
-const BookmarkList = (props) => {
-  const { t } = useTranslation();
+type BookmarkListProps = {
+  userId: string
+}
 
+export const BookmarkList = (props: BookmarkListProps): JSX.Element => {
   const { userId } = props;
 
-  const [pages, setPages] = useState([]);
-
+  const { t } = useTranslation();
+  const [pages, setPages] = useState<MyBookmarkList>([]);
   const [activePage, setActivePage] = useState(1);
   const [totalItemsCount, setTotalItemsCount] = useState(0);
   const [pagingLimit, setPagingLimit] = useState(10);
@@ -51,24 +51,18 @@ const BookmarkList = (props) => {
     getMyBookmarkList();
   }, [getMyBookmarkList]);
 
-  /**
-   * generate Elements of Page
-   *
-   * @param {any} pages Array of pages Model Obj
-   *
-   */
-  const generatePageList = pages.map(page => (
-    <li key={`my-bookmarks:${page._id}`} className="mt-4">
-      <PageListItemS page={page.page} />
-    </li>
-  ));
-
   return (
     <div className="bookmarks-list-container">
       {pages.length === 0 ? t('No bookmarks yet') : (
         <>
           <ul className="page-list-ul page-list-ul-flat mb-3">
-            {generatePageList}
+
+            {pages.map(page => (
+              <li key={`my-bookmarks:${page._id}`} className="mt-4">
+                <PageListItemS page={page.page} />
+              </li>
+            ))}
+
           </ul>
           <PaginationWrapper
             activePage={activePage}
@@ -82,11 +76,4 @@ const BookmarkList = (props) => {
       )}
     </div>
   );
-
 };
-
-BookmarkList.propTypes = {
-  userId: PropTypes.string.isRequired,
-};
-
-export default BookmarkList;

+ 0 - 39
packages/app/src/components/PageList/PageListItemS.jsx

@@ -1,39 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
-
-
-export default class PageListItemS extends React.Component {
-
-  render() {
-    const {
-      page, noLink,
-    } = this.props;
-
-    let pagePathElem = <PagePathLabel path={page.path} additionalClassNames={['mx-1']} />;
-    if (!noLink) {
-      pagePathElem = <a className="text-break" href={page.path}>{pagePathElem}</a>;
-    }
-
-    return (
-      <>
-        <UserPicture user={page.lastUpdateUser} noLink={noLink} />
-        {pagePathElem}
-        <span className="ml-2">
-          <PageListMeta page={page} />
-        </span>
-      </>
-    );
-  }
-
-}
-
-PageListItemS.propTypes = {
-  page: PropTypes.object.isRequired,
-  noLink: PropTypes.bool,
-};
-
-PageListItemS.defaultProps = {
-  noLink: false,
-};

+ 32 - 0
packages/app/src/components/PageList/PageListItemS.tsx

@@ -0,0 +1,32 @@
+import React from 'react';
+
+import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
+
+import { IPageHasId } from '~/interfaces/page';
+
+
+type PageListItemSProps = {
+  page: IPageHasId,
+  noLink?: boolean,
+}
+
+export const PageListItemS = (props: PageListItemSProps): JSX.Element => {
+
+  const { page, noLink = false } = props;
+
+  let pagePathElement = <PagePathLabel path={page.path} additionalClassNames={['mx-1']} />;
+  if (!noLink) {
+    pagePathElement = <a className="text-break" href={page.path}>{pagePathElement}</a>;
+  }
+
+  return (
+    <>
+      <UserPicture user={page.lastUpdateUser} noLink={noLink} />
+      {pagePathElement}
+      <span className="ml-2">
+        <PageListMeta page={page} />
+      </span>
+    </>
+  );
+
+};

+ 1 - 0
packages/app/src/components/PagePathHierarchicalLink.tsx

@@ -21,6 +21,7 @@ const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkProps): JS
   const {
     linkedPagePath, linkedPagePathByHtml, basePath, isInTrash,
   } = props;
+
   // render root element
   if (linkedPagePath.isRoot) {
     if (basePath != null) {

+ 0 - 91
packages/app/src/components/RecentCreated/RecentCreated.jsx

@@ -1,91 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-import { apiv3Get } from '~/client/util/apiv3-client';
-
-import PageListItemS from '../PageList/PageListItemS';
-import PaginationWrapper from '../PaginationWrapper';
-
-class RecentCreated extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      pages: [],
-      activePage: 1,
-      totalPages: 0,
-      pagingLimit: 10,
-    };
-
-    this.handlePage = this.handlePage.bind(this);
-  }
-
-
-  UNSAFE_componentWillMount() {
-    this.getRecentCreatedList(1);
-  }
-
-  async handlePage(selectedPage) {
-    await this.getRecentCreatedList(selectedPage);
-  }
-
-  async getRecentCreatedList(selectedPage) {
-    const { userId } = this.props;
-    const page = selectedPage;
-
-    // pagesList get and pagination calculate
-    const res = await apiv3Get(`/users/${userId}/recent`, { page });
-    const { totalCount, pages, limit } = res.data;
-
-    this.setState({
-      pages,
-      activePage: selectedPage,
-      totalPages: totalCount,
-      pagingLimit: limit,
-    });
-
-  }
-
-  /**
-   * generate Elements of Page
-   *
-   * @param {any} pages Array of pages Model Obj
-   *
-   */
-  generatePageList(pages) {
-    return pages.map(page => (
-      <li key={`recent-created:list-view:${page._id}`} className="mt-4">
-        <PageListItemS page={page} />
-      </li>
-    ));
-  }
-
-  render() {
-    const pageList = this.generatePageList(this.state.pages);
-
-    return (
-      <div className="page-list-container-create">
-        <ul className="page-list-ul page-list-ul-flat mb-3">
-          {pageList}
-        </ul>
-        <PaginationWrapper
-          align="center"
-          activePage={this.state.activePage}
-          changePage={this.handlePage}
-          totalItemsCount={this.state.totalPages}
-          pagingLimit={this.state.pagingLimit}
-          size="sm"
-        />
-      </div>
-    );
-  }
-
-}
-
-RecentCreated.propTypes = {
-  userId: PropTypes.string.isRequired,
-};
-
-export default RecentCreated;

+ 74 - 0
packages/app/src/components/RecentCreated/RecentCreated.tsx

@@ -0,0 +1,74 @@
+import React, { useState, useCallback, useEffect } from 'react';
+
+import { toastError } from '~/client/util/apiNotification';
+import { apiv3Get } from '~/client/util/apiv3-client';
+import { IPageHasId } from '~/interfaces/page';
+import loggerFactory from '~/utils/logger';
+
+import { PageListItemS } from '../PageList/PageListItemS';
+import PaginationWrapper from '../PaginationWrapper';
+
+const logger = loggerFactory('growi:RecentCreated');
+
+type RecentCreatedProps = {
+  userId: string,
+}
+
+export const RecentCreated = (props: RecentCreatedProps): JSX.Element => {
+
+  const { userId } = props;
+
+  const [pages, setPages] = useState<IPageHasId[]>([]);
+  const [activePage, setActivePage] = useState(1);
+  const [totalPages, setTotalPages] = useState(0);
+  const [pagingLimit, setPagingLimit] = useState(10);
+
+  const getMyRecentCreatedList = useCallback(async(selectedPage) => {
+    const page = selectedPage;
+
+    try {
+      const res = await apiv3Get(`/users/${userId}/recent`, { page });
+      const { totalCount, pages, limit } = res.data;
+
+      setPages(pages);
+      setActivePage(selectedPage);
+      setTotalPages(totalCount);
+      setPagingLimit(limit);
+    }
+    catch (error) {
+      logger.error('failed to fetch data', error);
+      toastError(error, 'Error occurred in recent created page list');
+    }
+  }, [userId]);
+
+  useEffect(() => {
+    getMyRecentCreatedList(1);
+  }, [getMyRecentCreatedList]);
+
+  const handlePage = useCallback(async(selectedPage) => {
+    await getMyRecentCreatedList(selectedPage);
+  }, [getMyRecentCreatedList]);
+
+  return (
+    <div className="page-list-container-create">
+      <ul className="page-list-ul page-list-ul-flat mb-3">
+
+        {pages.map(page => (
+          <li key={`recent-created:list-view:${page._id}`} className="mt-4">
+            <PageListItemS page={page} />
+          </li>
+        ))}
+
+      </ul>
+      <PaginationWrapper
+        activePage={activePage}
+        changePage={handlePage}
+        totalItemsCount={totalPages}
+        pagingLimit={pagingLimit}
+        align="center"
+        size="sm"
+      />
+    </div>
+  );
+
+};

+ 23 - 14
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -2,6 +2,7 @@ import React, {
   FC, useCallback, useEffect, useRef,
 } from 'react';
 
+import { getIdForRef } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import { DropdownItem } from 'reactstrap';
@@ -9,9 +10,10 @@ import { DropdownItem } from 'reactstrap';
 import { exportAsMarkdown } from '~/client/services/page-operation';
 import { toastSuccess } from '~/client/util/apiNotification';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
-import { IPageToDeleteWithMeta, IPageToRenameWithMeta, IPageWithMeta } from '~/interfaces/page';
+import { IPageToDeleteWithMeta, IPageToRenameWithMeta } from '~/interfaces/page';
 import { IPageWithSearchMeta } from '~/interfaces/search';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
+import { useCurrentUser } from '~/stores/context';
 import {
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal,
 } from '~/stores/modal';
@@ -19,11 +21,17 @@ import { useDescendantsPageListForCurrentPathTermManager, usePageTreeTermManager
 import { useSearchResultOptions } from '~/stores/renderer';
 import { useFullTextSearchTermManager } from '~/stores/search';
 
-
 import { AdditionalMenuItemsRendererProps, ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 import { GrowiSubNavigationProps } from '../Navbar/GrowiSubNavigation';
 import { SubNavButtonsProps } from '../Navbar/SubNavButtons';
+import { PageCommentProps } from '../PageComment';
+import { PageContentFooterProps } from '../PageContentFooter';
 
+const GrowiSubNavigation = dynamic<GrowiSubNavigationProps>(() => import('../Navbar/GrowiSubNavigation').then(mod => mod.GrowiSubNavigation), { ssr: false });
+const SubNavButtons = dynamic<SubNavButtonsProps>(() => import('../Navbar/SubNavButtons').then(mod => mod.SubNavButtons), { ssr: false });
+const RevisionLoader = dynamic(() => import('../Page/RevisionLoader'), { ssr: false });
+const PageComment = dynamic<PageCommentProps>(() => import('../PageComment').then(mod => mod.PageComment), { ssr: false });
+const PageContentFooter = dynamic<PageContentFooterProps>(() => import('../PageContentFooter').then(mod => mod.PageContentFooter), { ssr: false });
 
 type AdditionalMenuItemsProps = AdditionalMenuItemsRendererProps & {
   pageId: string,
@@ -77,11 +85,6 @@ const generateObserverCallback = (doScroll: ()=>void) => {
 };
 
 export const SearchResultContent: FC<Props> = (props: Props) => {
-  const GrowiSubNavigation = dynamic<GrowiSubNavigationProps>(() => import('../Navbar/GrowiSubNavigation').then(mod => mod.GrowiSubNavigation), { ssr: false });
-  const SubNavButtons = dynamic<SubNavButtonsProps>(() => import('../Navbar/SubNavButtons').then(mod => mod.SubNavButtons), { ssr: false });
-  const RevisionLoader = dynamic(() => import('../Page/RevisionLoader'), { ssr: false });
-  const PageComment = dynamic(() => import('../PageComment').then(mod => mod.PageComment), { ssr: false });
-  const PageContentFooter = dynamic(() => import('../PageContentFooter').then(mod => mod.PageContentFooter), { ssr: false });
 
   const scrollElementRef = useRef(null);
 
@@ -120,8 +123,8 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
-
   const { data: rendererOptions } = useSearchResultOptions();
+  const { data: currentUser } = useCurrentUser();
 
   const duplicateItemClickedHandler = useCallback(async(pageToDuplicate) => {
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -172,10 +175,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
       return <></>;
     }
 
-    const revisionId = typeof page.revision === 'string'
-      ? page.revision
-      : page.revision._id;
-
+    const revisionId = getIdForRef(page.revision);
 
     return (
       <div className="d-flex flex-column align-items-end justify-content-center py-md-2">
@@ -216,8 +216,17 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
           revisionId={page.revision}
           highlightKeywords={highlightKeywords}
         />
-        <PageComment pageId={page._id} highlightKeywords={highlightKeywords} isReadOnly hideIfEmpty />
-        <PageContentFooter />
+        <PageComment
+          pageId={page._id}
+          revision={page.revision}
+          currentUser={currentUser}
+          highlightKeywords={highlightKeywords}
+          isReadOnly
+          hideIfEmpty
+        />
+        <PageContentFooter
+          page={page}
+        />
       </div>
     </div>
   );

+ 0 - 1
packages/app/src/components/Sidebar/CustomSidebar.tsx

@@ -62,7 +62,6 @@ const CustomSidebar: FC = () => {
             <RevisionRenderer
               rendererOptions={rendererOptions}
               markdown={markdown}
-              pagePath="/Sidebar"
               additionalClassName="grw-custom-sidebar-content"
             />
           </div>

+ 11 - 0
packages/app/src/components/UsersHomePageFooter.module.scss

@@ -0,0 +1,11 @@
+@use '~/styles/molecules/page_list';
+
+.user-page-footer :global {
+  .grw-user-page-list-m {
+    svg {
+      width: 35px;
+      height: 35px;
+      margin-bottom: 6px;
+    }
+  }
+}

+ 40 - 0
packages/app/src/components/UsersHomePageFooter.tsx

@@ -0,0 +1,40 @@
+import React from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
+import { BookmarkList } from '~/components/PageList/BookmarkList';
+import { RecentCreated } from '~/components/RecentCreated/RecentCreated';
+import styles from '~/components/UsersHomePageFooter.module.scss';
+
+export type UsersHomePageFooterProps = {
+  creatorId: string,
+}
+
+export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Element => {
+  const { t } = useTranslation();
+  const { creatorId } = props;
+
+  return (
+    <div className={`container-lg user-page-footer py-5 ${styles['user-page-footer']}`}>
+      <div className="grw-user-page-list-m d-edit-none">
+        <h2 id="bookmarks-list" className="grw-user-page-header border-bottom pb-2 mb-3">
+          <i style={{ fontSize: '1.3em' }} className="fa fa-fw fa-bookmark-o"></i>
+          {t('footer.bookmarks')}
+        </h2>
+        <div id="user-bookmark-list" className={`page-list ${styles['page-list']}`}>
+          <BookmarkList userId={creatorId} />
+        </div>
+      </div>
+      <div className="grw-user-page-list-m mt-5 d-edit-none">
+        <h2 id="recently-created-list" className="grw-user-page-header border-bottom pb-2 mb-3">
+          <i id="recent-created-icon" className="mr-1"><RecentlyCreatedIcon /></i>
+          {t('footer.recently_created')}
+        </h2>
+        <div id="user-created-list" className={`page-list ${styles['page-list']}`}>
+          <RecentCreated userId={creatorId} />
+        </div>
+      </div>
+    </div>
+  );
+};

+ 12 - 0
packages/app/src/interfaces/bookmark-info.ts

@@ -1,3 +1,6 @@
+import { Ref } from '@growi/core';
+
+import { IPageHasId } from '~/interfaces/page';
 import { IUser } from '~/interfaces/user';
 
 export type IBookmarkInfo = {
@@ -5,3 +8,12 @@ export type IBookmarkInfo = {
   isBookmarked: boolean,
   bookmarkedUsers: IUser[]
 };
+
+type BookmarkedPage = {
+  _id: string,
+  page: IPageHasId,
+  user: Ref<IUser>,
+  createdAt: Date,
+}
+
+export type MyBookmarkList = BookmarkedPage[]

+ 0 - 1
packages/app/src/pages/[[...path]].page.module.scss

@@ -1 +0,0 @@
-@use '~/styles/molecules/page_list';

+ 16 - 45
packages/app/src/pages/[[...path]].page.tsx

@@ -20,7 +20,8 @@ import superjson from 'superjson';
 import { Comments } from '~/components/Comments';
 import { PageAlerts } from '~/components/PageAlert/PageAlerts';
 // import { useTranslation } from '~/i18n';
-import { PageContentFooter } from '~/components/PageContentFooter';
+import { CurrentPageContentFooter } from '~/components/PageContentFooter';
+import { UsersHomePageFooterProps } from '~/components/UsersHomePageFooter';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 // import { renderScriptTagByName, renderHighlightJsStyleTag } from '~/service/cdn-resources-loader';
 // import { useIndentSize } from '~/stores/editor';
@@ -41,7 +42,6 @@ import {
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
-
 // import { isUserPage, isTrashPage, isSharedPage } from '~/utils/path-utils';
 
 // import GrowiSubNavigation from '../client/js/components/Navbar/GrowiSubNavigation';
@@ -70,21 +70,21 @@ import {
 } from './utils/commons';
 // import { useCurrentPageSWR } from '../stores/page';
 
-import styles from './[[...path]].page.module.scss';
 
 const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').then(mod => mod.NotCreatablePage), { ssr: false });
 const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('./UnsavedAlertDialog'), { ssr: false });
 const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
+const UsersHomePageFooter = dynamic<UsersHomePageFooterProps>(() => import('../components/UsersHomePageFooter')
+  .then(mod => mod.UsersHomePageFooter), { ssr: false });
 
 const logger = loggerFactory('growi:pages:all');
 
 const {
-  isPermalink: _isPermalink, isUsersHomePage, isTrashPage: _isTrashPage, isUserPage, isCreatablePage,
+  isPermalink: _isPermalink, isUsersHomePage, isTrashPage: _isTrashPage, isUserPage, isCreatablePage, isTopPage,
 } = pagePathUtils;
 const { removeHeadingSlash } = pathUtils;
 
-
 type IPageToShowRevisionWithMeta = IDataWithMeta<IPagePopulatedToShowRevision & PageDocument, IPageInfoForEntity>;
 type IPageToShowRevisionWithMetaSerialized = IDataWithMeta<string, string>;
 
@@ -277,6 +277,8 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   //   classNames.push('not-found-page');
   // }
 
+  const isTopPagePath = isTopPage(pageWithMeta?.data.path ?? '');
+
   return (
     <>
       <Head>
@@ -323,46 +325,15 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
               </div>
             </div>
           </div>
-          {/* TODO: Check CSS import */}
-          <footer className="footer d-edit-none">
-            {/* TODO: Enable page_list.html */}
-            {/* TODO: Enable isIdenticalPathPage or useIdenticalPath */}
-            {/* { !props.isIdenticalPathPage && ( */}
-            <Comments pageId={pageId} />
-            {/* )} */}
-            {/* TODO: Create UsersHomePageFooter conponent */}
-            { isUsersHomePage(props.currentPathname) && (
-              <div className="container-lg user-page-footer py-5">
-                <div className="grw-user-page-list-m d-edit-none">
-                  <h2 id="bookmarks-list" className="grw-user-page-header border-bottom pb-2 mb-3">
-                    <i style={{ fontSize: '1.3em' }} className="fa fa-fw fa-bookmark-o"></i>
-                    Bookmarks
-                  </h2>
-                  <div id="user-bookmark-list" className={`page-list ${styles['page-list']}`}>
-                    {/* TODO: No need page-list-container class ? */}
-                    <div className="page-list-container">
-                      {/* <BookmarkList userId={pageContainer.state.creator._id} /> */}
-                    </div>
-                  </div>
-                </div>
-                <div className="grw-user-page-list-m mt-5 d-edit-none">
-                  <h2 id="recently-created-list" className="grw-user-page-header border-bottom pb-2 mb-3">
-                    <i id="recent-created-icon" className="mr-1">
-                      {/* <RecentlyCreatedIcon /> */}
-                    </i>
-                    Recently Created
-                  </h2>
-                  <div id="user-created-list" className={`page-list ${styles['page-list']}`}>
-                    {/* TODO: No need page-list-container class ? */}
-                    <div className="page-list-container">
-                      {/* <RecentCreated userId={pageContainer.state.creator._id} /> */}
-                    </div>
-                  </div>
-                </div>
-              </div>
-            )}
-            <PageContentFooter />
-          </footer>
+          { !props.isIdenticalPathPage && !props.isNotFound && (
+            <footer className="footer d-edit-none">
+              { !isTopPagePath && (<Comments pageId={pageId} revision={pageWithMeta?.data.revision} />) }
+              { (pageWithMeta != null && isUsersHomePage(pageWithMeta.data.path)) && (
+                <UsersHomePageFooter creatorId={pageWithMeta.data.creator._id}/>
+              ) }
+              <CurrentPageContentFooter />
+            </footer>
+          )}
 
           <UnsavedAlertDialog />
           <DescendantsPageListModal />

+ 0 - 10
packages/app/src/styles/_user.scss

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