Viewing File: /home/markqprx/iniasli.pro/client/comments/comment-list/comment-list-item.tsx
import React, {Fragment, memo, useContext, useState} from 'react';
import {SiteConfigContext} from '@common/core/settings/site-config-context';
import {Link} from 'react-router-dom';
import {Comment} from '@common/comments/comment';
import {useAuth} from '@common/auth/use-auth';
import {UserAvatar} from '@common/ui/images/user-avatar';
import {Button} from '@common/ui/buttons/button';
import {Trans} from '@common/i18n/trans';
import {NewCommentForm} from '@common/comments/new-comment-form';
import {User} from '@common/auth/user';
import {Commentable} from '@common/comments/commentable';
import {useDeleteComments} from '@common/comments/requests/use-delete-comments';
import {DialogTrigger} from '@common/ui/overlays/dialog/dialog-trigger';
import {queryClient} from '@common/http/query-client';
import {ConfirmationDialog} from '@common/ui/overlays/dialog/confirmation-dialog';
import {FormattedDuration} from '@common/i18n/formatted-duration';
import {useIsMobileMediaQuery} from '@common/utils/hooks/is-mobile-media-query';
import {ThumbButtons} from '@common/votes/thumb-buttons';
import {ReplyIcon} from '@common/icons/material/Reply';
import {MoreVertIcon} from '@common/icons/material/MoreVert';
import {
Menu,
MenuItem,
MenuTrigger,
} from '@common/ui/navigation/menu/menu-trigger';
import {FormattedRelativeTime} from '@common/i18n/formatted-relative-time';
import {useSubmitReport} from '@common/reports/requests/use-submit-report';
interface CommentListItemProps {
comment: Comment;
commentable: Commentable;
canDelete?: boolean;
}
export function CommentListItem({
comment,
commentable,
// user can delete comment if they have created it, or they have relevant permissions on commentable
canDelete,
}: CommentListItemProps) {
const isMobile = useIsMobileMediaQuery();
const {user, hasPermission} = useAuth();
const [replyFormVisible, setReplyFormVisible] = useState(false);
const showReplyButton =
user != null &&
!comment.deleted &&
!isMobile &&
comment.depth < 5 &&
hasPermission('comments.create');
return (
<div
style={{paddingLeft: `${comment.depth * 20}px`}}
onClick={() => {
if (isMobile) {
setReplyFormVisible(!replyFormVisible);
}
}}
>
<div className="group flex min-h-70 items-start gap-24 py-18">
<UserAvatar user={comment.user} size={isMobile ? 'lg' : 'xl'} circle />
<div className="flex-auto text-sm">
<div className="mb-4 flex items-center gap-8">
{comment.user && <UserDisplayName user={comment.user} />}
<time className="text-xs text-muted">
<FormattedRelativeTime date={comment.created_at} />
</time>
{comment.position ? (
<Position commentable={commentable} position={comment.position} />
) : null}
</div>
<div className="whitespace-pre-line">
{comment.deleted ? (
<span className="italic text-muted">
<Trans message="[COMMENT DELETED]" />
</span>
) : (
comment.content
)}
</div>
{!comment.deleted && (
<div className="-ml-8 mt-10 flex items-center gap-8">
{showReplyButton && (
<Button
sizeClassName="text-sm px-8 py-4"
startIcon={<ReplyIcon />}
onClick={() => setReplyFormVisible(!replyFormVisible)}
>
<Trans message="Reply" />
</Button>
)}
<ThumbButtons model={comment} showUpvotesOnly />
<CommentOptionsTrigger
comment={comment}
canDelete={canDelete}
user={user}
/>
</div>
)}
</div>
</div>
{replyFormVisible ? (
<NewCommentForm
className={!comment?.depth ? 'pl-20' : undefined}
commentable={commentable}
inReplyTo={comment}
autoFocus
onSuccess={() => {
setReplyFormVisible(false);
}}
/>
) : null}
</div>
);
}
interface PositionProps {
commentable: Commentable;
position: number;
}
const Position = memo(({commentable, position}: PositionProps) => {
if (!commentable.duration) return null;
const seconds = (position / 100) * (commentable.duration / 1000);
return (
<span className="text-xs text-muted">
<Trans
message="at :position"
values={{
position: <FormattedDuration seconds={seconds} />,
}}
/>
</span>
);
});
interface DeleteCommentsButtonProps {
comment: Comment;
canDelete?: boolean;
user: User | null;
}
export function CommentOptionsTrigger({
comment,
canDelete,
user,
}: DeleteCommentsButtonProps) {
const deleteComments = useDeleteComments();
const reportComment = useSubmitReport(comment);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const showDeleteButton =
(comment.user_id === user?.id || canDelete) && !comment.deleted;
const handleReport = () => {
reportComment.mutate({});
};
const handleDelete = (isConfirmed: boolean) => {
setIsDeleteDialogOpen(false);
if (isConfirmed) {
deleteComments.mutate(
{commentIds: [comment.id]},
{
onSuccess: () => {
queryClient.invalidateQueries({queryKey: ['comment']});
},
},
);
}
};
return (
<Fragment>
<MenuTrigger>
<Button startIcon={<MoreVertIcon />} sizeClassName="text-sm px-8 py-4">
<Trans message="More" />
</Button>
<Menu>
<MenuItem value="report" onSelected={() => handleReport()}>
<Trans message="Report comment" />
</MenuItem>
{showDeleteButton && (
<MenuItem
value="delete"
onSelected={() => setIsDeleteDialogOpen(true)}
>
<Trans message="Delete" />
</MenuItem>
)}
</Menu>
</MenuTrigger>
<DialogTrigger
type="modal"
isOpen={isDeleteDialogOpen}
onClose={isConfirmed => handleDelete(isConfirmed)}
>
<ConfirmationDialog
isDanger
title={<Trans message="Delete comment?" />}
body={
<Trans message="Are you sure you want to delete this comment?" />
}
confirm={<Trans message="Delete" />}
/>
</DialogTrigger>
</Fragment>
);
}
interface UserDisplayNameProps {
user: User;
}
function UserDisplayName({user}: UserDisplayNameProps) {
const {auth} = useContext(SiteConfigContext);
if (auth.getUserProfileLink) {
return (
<Link
to={auth.getUserProfileLink(user)}
className="text-base font-medium hover:underline"
>
{user.display_name}
</Link>
);
}
return <div className="text-base font-medium">{user.display_name}</div>;
}
Back to Directory
File Manager