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

display comment and reply in search page

yuto-oweseek 4 лет назад
Родитель
Сommit
48bb420a0d

+ 106 - 0
packages/app/src/components/PageCommentsThread.tsx

@@ -0,0 +1,106 @@
+import React, { FC, memo, useMemo } from 'react';
+
+import { UserPicture } from '@growi/ui';
+import AppContainer from '~/client/services/AppContainer';
+
+import Username from './User/Username';
+import FormattedDistanceDate from './FormattedDistanceDate';
+import HistoryIcon from './Icons/HistoryIcon';
+
+import { useSWRxPageComment } from '../stores/comment';
+
+
+type Props = {
+  appContainer: AppContainer,
+  pageId: string,
+
+}
+
+const PageCommentsThread:FC<Props> = memo((props:Props):JSX.Element => {
+
+  const { appContainer, pageId } = props;
+
+  const { data: comments } = useSWRxPageComment(pageId);
+
+  const commentsFromOldest = useMemo(() => (comments != null ? [...comments].reverse() : null), [comments]);
+  const allReplies = {};
+
+  if (commentsFromOldest != null) {
+    commentsFromOldest.forEach((comment) => {
+      if (comment.replyTo != null) {
+        allReplies[comment.replyTo] = comment;
+      }
+    });
+  }
+
+
+  const growiRenderer = useMemo(() => appContainer.getRenderer('comment'), [appContainer]);
+
+  if (comments == null) return <></>;
+
+  return (
+    <div className="border border-top mt-5 px-2">
+      <h2 className="my-3 text-center"><i className="icon-fw icon-bubbles"></i>Comments</h2>
+
+      { commentsFromOldest?.map((comment) => {
+
+        const hasReply: boolean = Object.keys(allReplies).includes(comment._id);
+
+        return (
+          <div key={comment._id} className="age-comment-main">
+            {/* display comment */}
+            <div className={`d-flex ${hasReply ? 'mb-3' : 'mb-5'}`}>
+              <div className="flex-shrink-0">
+                <UserPicture user={comment.creator} size="md" noLink noTooltip />
+              </div>
+              <div className="flex-grow-1 ml-3">
+                <div className="d-flex">
+                  <div className="flex-shrink-0">
+                    <Username user={comment.creator} />
+                  </div>
+                  <div className="flex-grow-1 ml-3 text-right">
+                    <div className="page-comment-meta">
+                      <HistoryIcon />
+                      <a href={`#${comment._id}`}>
+                        <FormattedDistanceDate id={comment._id} date={comment.createdAt} />
+                      </a>
+                    </div>
+                  </div>
+                </div>
+                <div className="page-comment-body mt-1">{comment.comment}</div>
+              </div>
+            </div>
+            {/* display reply comment */}
+            {hasReply && (
+              <div className="d-flex ml-4 mb-5">
+                <div className="flex-shrink-0">
+                  <UserPicture user={allReplies[comment._id].creator} size="md" noLink noTooltip />
+                </div>
+                <div className="flex-grow-1 ml-3">
+                  <div className="d-flex">
+                    <div className="flex-shrink-0">
+                      <Username user={allReplies[comment._id].creator} />
+                    </div>
+                    <div className="flex-grow-1 ml-3 text-right">
+                      <div className="page-comment-meta">
+                        <HistoryIcon />
+                        <a href={`#${allReplies[comment._id]._id}`}>
+                          <FormattedDistanceDate id={allReplies[comment._id]._id} date={allReplies[comment._id].createdAt} />
+                        </a>
+                      </div>
+                    </div>
+                  </div>
+                  <div className="page-comment-body mt-1">{allReplies[comment._id].comment}</div>
+                </div>
+              </div>
+            )}
+          </div>
+        );
+      })}
+
+    </div>
+  );
+});
+
+
+export default PageCommentsThread;

+ 2 - 0
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -16,6 +16,7 @@ import { exportAsMarkdown } from '~/client/services/page-operation';
 import { toastSuccess } from '~/client/util/apiNotification';
 
 import PageContentFooter from '../PageContentFooter';
+import PageCommentsThread from '../PageCommentsThread';
 
 import RevisionLoader from '../Page/RevisionLoader';
 import AppContainer from '../../client/services/AppContainer';
@@ -218,6 +219,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
           highlightKeywords={highlightKeywords}
           isRenderable
         />
+        <PageCommentsThread appContainer={appContainer} pageId={page._id} />
         <PageContentFooter
           createdAt={pageWithMeta.data.createdAt}
           updatedAt={pageWithMeta.data.updatedAt}

+ 20 - 0
packages/app/src/interfaces/comment.ts

@@ -0,0 +1,20 @@
+import { Nullable, Ref } from './common';
+import { IPage } from './page';
+import { IUser } from './user';
+import { IRevision } from './revision';
+import { HasObjectId } from './has-object-id';
+
+export type IComment = {
+  comment: string;
+  commentPosition: number,
+  isMarkdown: boolean,
+  replyTo: Nullable<string>,
+  createdAt: Date,
+  updatedAt: Date,
+  page: Ref<IPage>,
+  revision: Ref<IRevision>,
+  creator: IUser,
+};
+
+export type ICommentHasId = IComment & HasObjectId;
+export type ICommentHasIdList = ICommentHasId[];

+ 18 - 0
packages/app/src/stores/comment.tsx

@@ -0,0 +1,18 @@
+import useSWR, { SWRResponse } from 'swr';
+
+import { apiGet } from '~/client/util/apiv1-client';
+
+import { ICommentHasIdList } from '../interfaces/comment';
+import { Nullable } from '../interfaces/common';
+
+type IResponseComment = {
+  comments: ICommentHasIdList,
+  ok: string,
+}
+
+export const useSWRxPageComment = (pageId: Nullable<string>): SWRResponse<ICommentHasIdList, Error> => {
+  return useSWR(
+    pageId != null ? '/comments.get' : null,
+    endpoint => apiGet(endpoint, { page_id: pageId }).then((response:IResponseComment) => response.comments),
+  );
+};