Comment.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import React, { useEffect, useState } from 'react';
  2. import { UserPicture } from '@growi/ui';
  3. import { format } from 'date-fns';
  4. import { useTranslation } from 'next-i18next';
  5. import { UncontrolledTooltip } from 'reactstrap';
  6. import { RendererOptions } from '~/services/renderer/renderer';
  7. import { useCurrentUser } from '~/stores/context';
  8. import { ICommentHasId } from '../../interfaces/comment';
  9. import FormattedDistanceDate from '../FormattedDistanceDate';
  10. import HistoryIcon from '../Icons/HistoryIcon';
  11. import RevisionRenderer from '../Page/RevisionRenderer';
  12. import Username from '../User/Username';
  13. import CommentControl from './CommentControl';
  14. import { CommentEditor } from './CommentEditor';
  15. import styles from './Comment.module.scss';
  16. type CommentProps = {
  17. comment: ICommentHasId,
  18. isReadOnly: boolean,
  19. deleteBtnClicked: (comment: ICommentHasId) => void,
  20. onComment: () => void,
  21. rendererOptions: RendererOptions,
  22. currentPagePath: string,
  23. currentRevisionId: string,
  24. currentRevisionCreatedAt: Date,
  25. }
  26. export const Comment = (props: CommentProps): JSX.Element => {
  27. const {
  28. comment, isReadOnly, deleteBtnClicked, onComment, rendererOptions,
  29. currentPagePath, currentRevisionId, currentRevisionCreatedAt,
  30. } = props;
  31. const { t } = useTranslation();
  32. const { data: currentUser } = useCurrentUser();
  33. const [markdown, setMarkdown] = useState('');
  34. const [isReEdit, setIsReEdit] = useState(false);
  35. const commentId = comment._id;
  36. const creator = comment.creator;
  37. const isMarkdown = comment.isMarkdown;
  38. const createdAt = new Date(comment.createdAt);
  39. const updatedAt = new Date(comment.updatedAt);
  40. const isEdited = createdAt < updatedAt;
  41. useEffect(() => {
  42. setMarkdown(comment.comment);
  43. const isCurrentRevision = () => {
  44. return comment.revision === currentRevisionId;
  45. };
  46. isCurrentRevision();
  47. }, [comment, currentRevisionId]);
  48. const isCurrentUserEqualsToAuthor = () => {
  49. const { creator }: any = comment;
  50. if (creator == null || currentUser == null) {
  51. return false;
  52. }
  53. return creator.username === currentUser.username;
  54. };
  55. const getRootClassName = (comment) => {
  56. let className = `${styles['page-comment']} page-comment flex-column`;
  57. if (comment.revision === currentRevisionId) {
  58. className += ' page-comment-current';
  59. }
  60. else if (comment.createdAt.getTime() > currentRevisionCreatedAt.getTime()) {
  61. className += ' page-comment-newer';
  62. }
  63. else {
  64. className += ' page-comment-older';
  65. }
  66. if (isCurrentUserEqualsToAuthor()) {
  67. className += ' page-comment-me';
  68. }
  69. return className;
  70. };
  71. const deleteBtnClickedHandler = (comment) => {
  72. deleteBtnClicked(comment);
  73. };
  74. const renderText = (comment) => {
  75. return <span style={{ whiteSpace: 'pre-wrap' }}>{comment}</span>;
  76. };
  77. const renderRevisionBody = () => {
  78. return (
  79. <RevisionRenderer
  80. rendererOptions={rendererOptions}
  81. markdown={markdown}
  82. additionalClassName="comment"
  83. pagePath={currentPagePath}
  84. />
  85. );
  86. };
  87. const rootClassName = getRootClassName(comment);
  88. const commentBody = isMarkdown ? renderRevisionBody() : renderText(comment.comment);
  89. const revHref = `?revision=${comment.revision}`;
  90. const editedDateId = `editedDate-${comment._id}`;
  91. const editedDateFormatted = isEdited
  92. ? format(updatedAt, 'yyyy/MM/dd HH:mm')
  93. : null;
  94. return (
  95. <>
  96. {(isReEdit && !isReadOnly) ? (
  97. <CommentEditor
  98. rendererOptions={rendererOptions}
  99. replyTo={undefined}
  100. currentCommentId={commentId}
  101. commentBody={comment.comment}
  102. onCancelButtonClicked={() => setIsReEdit(false)}
  103. onCommentButtonClicked={() => {
  104. setIsReEdit(false);
  105. if (onComment != null) onComment();
  106. }}
  107. />
  108. ) : (
  109. <div id={commentId} className={rootClassName}>
  110. <div className={`${styles['page-comment-writer']} page-comment-writer`}>
  111. <UserPicture user={creator} noLink noTooltip />
  112. </div>
  113. <div className={`${styles['page-comment-main']} page-comment-main`}>
  114. <div className="page-comment-creator">
  115. <Username user={creator} />
  116. </div>
  117. <div className="page-comment-body">{commentBody}</div>
  118. <div className="page-comment-meta">
  119. <a href={`#${commentId}`}>
  120. <FormattedDistanceDate id={commentId} date={comment.createdAt} />
  121. </a>
  122. { isEdited && (
  123. <>
  124. <span id={editedDateId}>&nbsp;(edited)</span>
  125. <UncontrolledTooltip placement="bottom" fade={false} target={editedDateId}>{editedDateFormatted}</UncontrolledTooltip>
  126. </>
  127. )}
  128. <span className="ml-2">
  129. <a id={`page-comment-revision-${commentId}`} className="page-comment-revision" href={revHref}>
  130. <HistoryIcon />
  131. </a>
  132. <UncontrolledTooltip placement="bottom" fade={false} target={`page-comment-revision-${commentId}`}>
  133. {t('page_comment.display_the_page_when_posting_this_comment')}
  134. </UncontrolledTooltip>
  135. </span>
  136. </div>
  137. {(isCurrentUserEqualsToAuthor() && !isReadOnly) && (
  138. <CommentControl
  139. onClickDeleteBtn={deleteBtnClickedHandler}
  140. onClickEditBtn={() => setIsReEdit(true)}
  141. />
  142. ) }
  143. </div>
  144. </div>
  145. )
  146. }
  147. </>
  148. );
  149. };