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

Merge pull request #7804 from weseek/fix/gw7961-cancel-a-comment-will-cancel-all-comments

fix: Cancel a comment will cancel all comments
Yuki Takei 2 лет назад
Родитель
Сommit
c1a0049140

+ 11 - 5
apps/app/src/components/Comments.tsx

@@ -5,7 +5,7 @@ import dynamic from 'next/dynamic';
 
 import { ROOT_ELEM_ID as PageCommentRootElemId, type PageCommentProps } from '~/components/PageComment';
 import { useSWRxPageComment } from '~/stores/comment';
-import { useIsTrashPage } from '~/stores/page';
+import { useIsTrashPage, useSWRMUTxPageInfo } from '~/stores/page';
 
 import { useCurrentUser } from '../stores/context';
 
@@ -32,6 +32,7 @@ export const Comments = (props: CommentsProps): JSX.Element => {
   } = props;
 
   const { mutate } = useSWRxPageComment(pageId);
+  const { trigger: mutatePageInfo } = useSWRMUTxPageInfo(pageId);
   const { data: isDeleted } = useIsTrashPage();
   const { data: currentUser } = useCurrentUser();
 
@@ -41,8 +42,8 @@ export const Comments = (props: CommentsProps): JSX.Element => {
     const parent = pageCommentParentRef.current;
     if (parent == null) return;
 
-    const observerCallback = (mutationRecords:MutationRecord[]) => {
-      mutationRecords.forEach((record:MutationRecord) => {
+    const observerCallback = (mutationRecords: MutationRecord[]) => {
+      mutationRecords.forEach((record: MutationRecord) => {
         const target = record.target as HTMLElement;
 
         for (const child of Array.from(target.children)) {
@@ -69,6 +70,11 @@ export const Comments = (props: CommentsProps): JSX.Element => {
     return <></>;
   }
 
+  const onCommentButtonClickHandler = () => {
+    mutate();
+    mutatePageInfo();
+  };
+
   return (
     <div className="page-comments-row mt-5 py-4 d-edit-none d-print-none">
       <div className="container-lg">
@@ -83,12 +89,12 @@ export const Comments = (props: CommentsProps): JSX.Element => {
             hideIfEmpty={false}
           />
         </div>
-        { !isDeleted && (
+        {!isDeleted && (
           <div id="page-comment-write">
             <CommentEditor
               pageId={pageId}
               isForNewComment
-              onCommentButtonClicked={mutate}
+              onCommentButtonClicked={onCommentButtonClickHandler}
               revisionId={revision._id}
             />
           </div>

+ 13 - 8
apps/app/src/components/Page/PageView.tsx

@@ -111,11 +111,16 @@ export const PageView = (props: Props): JSX.Element => {
     ? (
       <>
         <div id="comments-container" ref={commentsContainerRef}>
-          <Comments pageId={page._id} pagePath={pagePath} revision={page.revision} onLoaded={() => setCommentsLoaded(true)} />
+          <Comments
+            pageId={page._id}
+            pagePath={pagePath}
+            revision={page.revision}
+            onLoaded={() => setCommentsLoaded(true)}
+          />
         </div>
-        { (isUsersHomePagePath && page.creator != null) && (
-          <UsersHomePageFooter creatorId={page.creator._id}/>
-        ) }
+        {(isUsersHomePagePath && page.creator != null) && (
+          <UsersHomePageFooter creatorId={page.creator._id} />
+        )}
         <PageContentFooter page={page} />
       </>
     )
@@ -144,15 +149,15 @@ export const PageView = (props: Props): JSX.Element => {
     >
       <PageAlerts />
 
-      { specialContents }
-      { specialContents == null && (
+      {specialContents}
+      {specialContents == null && (
         <>
-          { (isUsersHomePagePath && page?.creator != null) && <UserInfo author={page.creator} /> }
+          {(isUsersHomePagePath && page?.creator != null) && <UserInfo author={page.creator} />}
           <div className={`mb-5 ${isMobile ? `page-mobile ${styles['page-mobile']}` : ''}`}>
             <Contents />
           </div>
         </>
-      ) }
+      )}
 
     </MainPane>
   );

+ 20 - 14
apps/app/src/components/PageComment.tsx

@@ -9,6 +9,7 @@ import { Button } from 'reactstrap';
 import { apiPost } from '~/client/util/apiv1-client';
 import { toastError } from '~/client/util/toastr';
 import { RendererOptions } from '~/interfaces/renderer-options';
+import { useSWRMUTxPageInfo } from '~/stores/page';
 import { useCommentForCurrentPageOptions } from '~/stores/renderer';
 
 import { ICommentHasId, ICommentHasIdList } from '../interfaces/comment';
@@ -42,7 +43,7 @@ export type PageCommentProps = {
   hideIfEmpty?: boolean,
 }
 
-export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps): JSX.Element => {
+export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps): JSX.Element => {
 
   const {
     rendererOptions: rendererOptionsByProps,
@@ -56,6 +57,7 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState<boolean>(false);
   const [showEditorIds, setShowEditorIds] = useState<Set<string>>(new Set());
   const [errorMessageOnDelete, setErrorMessageOnDelete] = useState<string>('');
+  const { trigger: mutatePageInfo } = useSWRMUTxPageInfo(pageId);
 
   const commentsFromOldest = useMemo(() => (comments != null ? [...comments].reverse() : null), [comments]);
   const commentsExceptReply: ICommentHasIdList | undefined = useMemo(
@@ -84,7 +86,8 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
   const onDeleteCommentAfterOperation = useCallback(() => {
     onCancelDeleteComment();
     mutate();
-  }, [mutate, onCancelDeleteComment]);
+    mutatePageInfo();
+  }, [mutate, onCancelDeleteComment, mutatePageInfo]);
 
   const onDeleteComment = useCallback(async() => {
     if (commentToBeDeleted == null) return;
@@ -92,7 +95,7 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
       await apiPost('/comments.remove', { comment_id: commentToBeDeleted._id });
       onDeleteCommentAfterOperation();
     }
-    catch (error:unknown) {
+    catch (error: unknown) {
       setErrorMessageOnDelete(error as string);
       toastError(`error: ${error}`);
     }
@@ -100,12 +103,20 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
 
   const removeShowEditorId = useCallback((commentId: string) => {
     setShowEditorIds((previousState) => {
-      const previousShowEditorIds = new Set(...previousState);
-      previousShowEditorIds.delete(commentId);
-      return previousShowEditorIds;
+      return new Set([...previousState].filter(id => id !== commentId));
     });
   }, []);
 
+  const onReplyButtonClickHandler = useCallback((commentId: string) => {
+    setShowEditorIds(previousState => new Set([...previousState, commentId]));
+  }, []);
+
+  const onCommentButtonClickHandler = useCallback((commentId: string) => {
+    removeShowEditorId(commentId);
+    mutate();
+    mutatePageInfo();
+  }, [removeShowEditorId, mutate, mutatePageInfo]);
+
   if (hideIfEmpty && comments?.length === 0) {
     return <PageCommentRoot />;
   }
@@ -163,7 +174,7 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
         <div className="page-comments">
           <h2 className={commentTitleClasses}><i className="icon-fw icon-bubbles"></i>Comments</h2>
           <div className="page-comments-list" id="page-comments-list">
-            { commentsExceptReply.map((comment) => {
+            {commentsExceptReply.map((comment) => {
 
               const defaultCommentThreadClasses = 'page-comment-thread pb-5';
               const hasReply: boolean = Object.keys(allReplies).includes(comment._id);
@@ -184,9 +195,7 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
                             color="secondary"
                             size="sm"
                             className="btn-comment-reply"
-                            onClick={() => {
-                              setShowEditorIds(previousState => new Set(previousState.add(comment._id)));
-                            }}
+                            onClick={() => onReplyButtonClickHandler(comment._id)}
                           >
                             <i className="icon-fw icon-action-undo"></i> Reply
                           </Button>
@@ -201,10 +210,7 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
                       onCancelButtonClicked={() => {
                         removeShowEditorId(comment._id);
                       }}
-                      onCommentButtonClicked={() => {
-                        removeShowEditorId(comment._id);
-                        mutate();
-                      }}
+                      onCommentButtonClicked={() => onCommentButtonClickHandler(comment._id)}
                       revisionId={revisionId}
                     />
                   )}

+ 5 - 3
apps/app/src/components/PageComment/CommentEditor.tsx

@@ -109,6 +109,8 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
   }, []);
 
   const initializeEditor = useCallback(async() => {
+    const editingCommentsNum = comment !== '' ? await decrementEditingCommentsNum() : undefined;
+
     setComment('');
     setActiveTab('comment_editor');
     setError(undefined);
@@ -116,11 +118,11 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
     // reset value
     if (editorRef.current == null) { return }
     editorRef.current.setValue('');
-    const editingCommentsNum = await decrementEditingCommentsNum();
-    if (editingCommentsNum === 0) {
+
+    if (editingCommentsNum != null && editingCommentsNum === 0) {
       mutateIsEnabledUnsavedWarning(false); // must be after clearing comment or else onChange will override bool
     }
-  }, [initializeSlackEnabled, mutateIsEnabledUnsavedWarning, decrementEditingCommentsNum]);
+  }, [initializeSlackEnabled, comment, decrementEditingCommentsNum, mutateIsEnabledUnsavedWarning]);
 
   const cancelButtonClickedHandler = useCallback(() => {
     // change state to not ready

+ 10 - 2
apps/app/src/components/PageSideContents.tsx

@@ -4,7 +4,9 @@ import { IPageHasId, pagePathUtils } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { Link } from 'react-scroll';
 
+import { IPageInfoForOperation } from '~/interfaces/page';
 import { useDescendantsPageListModal } from '~/stores/modal';
+import { useSWRxPageInfo } from '~/stores/page';
 
 import CountBadge from './Common/CountBadge';
 import { ContentLinkButtons } from './ContentLinkButtons';
@@ -29,6 +31,8 @@ export const PageSideContents = (props: PageSideContentsProps): JSX.Element => {
 
   const { page, isSharedUser } = props;
 
+  const { data: pageInfo } = useSWRxPageInfo(page._id);
+
   const pagePath = page.path;
   const isTopPagePath = isTopPage(pagePath);
   const isUsersHomePagePath = isUsersHomePage(pagePath);
@@ -51,7 +55,9 @@ export const PageSideContents = (props: PageSideContentsProps): JSX.Element => {
             {t('page_list')}
 
             {/* Do not display CountBadge if '/trash/*': https://github.com/weseek/growi/pull/7600 */}
-            { !isTrash ? <CountBadge count={page?.descendantCount} offset={1} /> : <div className='px-2'></div>}
+            { !isTrash && pageInfo != null
+              ? <CountBadge count={(pageInfo as IPageInfoForOperation).descendantCount} offset={1} />
+              : <div className='px-2'></div>}
           </button>
         )}
       </div>
@@ -67,7 +73,9 @@ export const PageSideContents = (props: PageSideContentsProps): JSX.Element => {
             >
               <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
               <span>Comments</span>
-              <CountBadge count={page.commentCount} />
+              { pageInfo != null
+                ? <CountBadge count={(pageInfo as IPageInfoForOperation).commentCount} />
+                : <div className='px-2'></div>}
             </button>
           </Link>
         </div>

+ 2 - 0
apps/app/src/server/service/page.ts

@@ -2319,6 +2319,8 @@ class PageService {
       isAbleToDeleteCompletely: false,
       isRevertible: isTrashPage(page.path),
       contentAge: page.getContentAge(),
+      descendantCount: page.descendantCount,
+      commentCount: page.commentCount,
     };
 
   }

+ 2 - 0
packages/core/src/interfaces/page.ts

@@ -87,6 +87,8 @@ export type IPageInfoForEntity = IPageInfo & {
   sumOfSeenUsers: number,
   seenUserIds: string[],
   contentAge: number,
+  descendantCount: number,
+  commentCount: number,
 }
 
 export type IPageInfoForOperation = IPageInfoForEntity & {