1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 04:46:40 +07:00
vault-frontend/src/components/comment/CommentContent/index.tsx
2023-11-01 21:02:48 +06:00

168 lines
4.5 KiB
TypeScript

import {
createElement,
FC,
memo,
ReactNode,
useCallback,
useMemo,
useState,
} from 'react';
import classnames from 'classnames';
import { Group } from '~/components/containers/Group';
import { AudioPlayer } from '~/components/media/AudioPlayer';
import { COMMENT_BLOCK_RENDERERS } from '~/constants/comment';
import { UploadType } from '~/constants/uploads';
import { IComment, IFile } from '~/types';
import { formatCommentText, getPrettyDate } from '~/utils/dom';
import { append, assocPath, path, reduce } from '~/utils/ramda';
import { CommentEditingForm } from '../CommentEditingForm';
import { CommentImageGrid } from '../CommentImageGrid';
import { CommentLike } from '../CommentLike';
import { CommentMenu } from '../CommentMenu';
import styles from './styles.module.scss';
interface IProps {
prefix?: ReactNode;
nodeId: number;
comment: IComment;
canEdit: boolean;
canLike: boolean;
saveComment: (data: IComment) => Promise<IComment | undefined>;
onDelete: (isLocked: boolean) => void;
onLike: () => void;
onShowImageModal: (images: IFile[], index: number) => void;
}
const CommentContent: FC<IProps> = memo(
({
comment,
nodeId,
saveComment,
canEdit,
canLike,
onLike,
onDelete,
onShowImageModal,
prefix,
}) => {
const [isEditing, setIsEditing] = useState(false);
const startEditing = useCallback(() => setIsEditing(true), [setIsEditing]);
const stopEditing = useCallback(() => setIsEditing(false), [setIsEditing]);
const groupped = useMemo<Record<UploadType, IFile[]>>(
() =>
reduce(
(group, file) =>
file.type
? assocPath([file.type], append(file, group[file.type]), group)
: group,
{} as Record<UploadType, IFile[]>,
comment.files,
),
[comment],
);
const onLockClick = useCallback(() => {
onDelete(!comment.deleted_at);
}, [comment, onDelete]);
const onImageClick = useCallback(
(file: IFile) =>
onShowImageModal(groupped.image, groupped.image.indexOf(file)),
[onShowImageModal, groupped],
);
const menu = useMemo(
() =>
canEdit && (
<div className={styles.menu}>
<CommentMenu onDelete={onLockClick} onEdit={startEditing} />
</div>
),
[canEdit, startEditing, onLockClick],
);
const blocks = useMemo(
() =>
!!comment.text.trim()
? formatCommentText(path(['user', 'username'], comment), comment.text)
: [],
[comment],
);
if (isEditing) {
return (
<CommentEditingForm
saveComment={saveComment}
nodeId={nodeId}
comment={comment}
onCancelEdit={stopEditing}
/>
);
}
return (
<div className={styles.wrap}>
{!!prefix && <div className={styles.prefix}>{prefix}</div>}
<div className={styles.content}>
{menu}
<div>
{comment.text.trim() && (
<Group className={classnames(styles.block, styles.block_text)}>
<Group className={styles.renderers}>
{blocks.map(
(block, key) =>
COMMENT_BLOCK_RENDERERS[block.type] &&
createElement(COMMENT_BLOCK_RENDERERS[block.type], {
block,
key,
}),
)}
</Group>
</Group>
)}
{groupped.image && groupped.image.length > 0 && (
<div className={classnames(styles.block, styles.block_image)}>
<CommentImageGrid
files={groupped.image}
onClick={onImageClick}
/>
</div>
)}
{groupped.audio &&
groupped.audio.length > 0 &&
groupped.audio.map((file) => (
<div
className={classnames(styles.block, styles.block_audio)}
key={file.id}
>
<AudioPlayer file={file} />
</div>
))}
</div>
</div>
<div className={styles.date}>
{getPrettyDate(comment.created_at)}
<CommentLike
onLike={onLike}
count={comment.like_count}
active={canLike}
liked={comment.liked}
/>
</div>
</div>
);
},
);
export { CommentContent };