1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

let users like comments

This commit is contained in:
Fedor Katurov 2023-11-01 20:56:47 +06:00
parent 822f51f5de
commit bd802ede10
22 changed files with 332 additions and 154 deletions

View file

@ -13,20 +13,22 @@ import { CommendDeleted } from '../../node/CommendDeleted';
import styles from './styles.module.scss';
type IProps = HTMLAttributes<HTMLDivElement> & {
type Props = HTMLAttributes<HTMLDivElement> & {
nodeId: number;
isEmpty?: boolean;
isLoading?: boolean;
group: ICommentGroup;
isSame?: boolean;
canEdit?: boolean;
canLike?: boolean;
highlighted?: boolean;
saveComment: (data: IComment) => Promise<IComment | undefined>;
onDelete: (id: IComment['id'], isLocked: boolean) => void;
onLike: (id: IComment['id'], isLiked: boolean) => void;
onShowImageModal: (images: IFile[], index: number) => void;
};
const Comment: FC<IProps> = memo(
const Comment: FC<Props> = memo(
({
group,
nodeId,
@ -36,7 +38,9 @@ const Comment: FC<IProps> = memo(
className,
highlighted,
canEdit,
canLike,
onDelete,
onLike,
onShowImageModal,
saveComment,
...props
@ -84,10 +88,12 @@ const Comment: FC<IProps> = memo(
saveComment={saveComment}
nodeId={nodeId}
comment={comment}
key={comment.id}
canEdit={!!canEdit}
onDelete={onDelete}
canLike={!!canLike}
onLike={() => onLike(comment.id, !comment.liked)}
onDelete={(val: boolean) => onDelete(comment.id, val)}
onShowImageModal={onShowImageModal}
key={comment.id}
/>
);
})}

View file

@ -1,7 +1,6 @@
import React, {
import {
createElement,
FC,
Fragment,
memo,
ReactNode,
useCallback,
@ -11,7 +10,6 @@ import React, {
import classnames from 'classnames';
import { Authorized } from '~/components/containers/Authorized';
import { Group } from '~/components/containers/Group';
import { AudioPlayer } from '~/components/media/AudioPlayer';
import { COMMENT_BLOCK_RENDERERS } from '~/constants/comment';
@ -22,6 +20,7 @@ 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';
@ -31,17 +30,21 @@ interface IProps {
nodeId: number;
comment: IComment;
canEdit: boolean;
canLike: boolean;
saveComment: (data: IComment) => Promise<IComment | undefined>;
onDelete: (id: IComment['id'], isLocked: boolean) => void;
onDelete: (isLocked: boolean) => void;
onLike: () => void;
onShowImageModal: (images: IFile[], index: number) => void;
}
const CommentContent: FC<IProps> = memo(
({
comment,
canEdit,
nodeId,
saveComment,
canEdit,
canLike,
onLike,
onDelete,
onShowImageModal,
prefix,
@ -65,7 +68,7 @@ const CommentContent: FC<IProps> = memo(
);
const onLockClick = useCallback(() => {
onDelete(comment.id, !comment.deleted_at);
onDelete(!comment.deleted_at);
}, [comment, onDelete]);
const onImageClick = useCallback(
@ -75,15 +78,12 @@ const CommentContent: FC<IProps> = memo(
);
const menu = useMemo(
() => (
<div>
{canEdit && (
<Authorized>
<CommentMenu onDelete={onLockClick} onEdit={startEditing} />
</Authorized>
)}
</div>
),
() =>
canEdit && (
<div className={styles.menu}>
<CommentMenu onDelete={onLockClick} onEdit={startEditing} />
</div>
),
[canEdit, startEditing, onLockClick],
);
@ -110,57 +110,56 @@ const CommentContent: FC<IProps> = memo(
<div className={styles.wrap}>
{!!prefix && <div className={styles.prefix}>{prefix}</div>}
{comment.text.trim() && (
<Group className={classnames(styles.block, styles.block_text)}>
{menu}
<div className={styles.content}>
{menu}
<Group className={styles.renderers}>
{blocks.map(
(block, key) =>
COMMENT_BLOCK_RENDERERS[block.type] &&
createElement(COMMENT_BLOCK_RENDERERS[block.type], {
block,
key,
}),
)}
</Group>
<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>
)}
<div className={styles.date}>
{getPrettyDate(comment.created_at)}
</div>
</Group>
)}
{groupped.image && groupped.image.length > 0 && (
<div className={classnames(styles.block, styles.block_image)}>
{menu}
<CommentImageGrid files={groupped.image} onClick={onImageClick} />
<div className={styles.date}>
{getPrettyDate(comment.created_at)}
</div>
</div>
)}
{groupped.audio && groupped.audio.length > 0 && (
<Fragment>
{groupped.audio.map((file) => (
<div
className={classnames(styles.block, styles.block_audio)}
key={file.id}
>
{menu}
<AudioPlayer file={file} />
<div className={styles.date}>
{getPrettyDate(comment.created_at)}
</div>
{groupped.image && groupped.image.length > 0 && (
<div className={classnames(styles.block, styles.block_image)}>
<CommentImageGrid
files={groupped.image}
onClick={onImageClick}
/>
</div>
))}
</Fragment>
)}
)}
{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>
);
},

View file

@ -54,7 +54,14 @@
top: 28px;
}
.content {
width: 100%;
position: relative;
}
.block {
@include row_shadow;
min-height: $comment_height;
display: flex;
align-items: flex-start;
@ -90,13 +97,7 @@
}
.block_image {
padding-bottom: 0 !important;
.date {
background: $content_bg;
border-radius: $radius 0 $radius 0;
color: $gray_25;
}
padding: $gap / 2;
}
.block_text {
@ -105,16 +106,20 @@
.date {
position: absolute;
bottom: 1px;
bottom: 1px; // should not cover block shadow
right: 0;
font: $font_12_regular;
font: $font_12_medium;
color: $gray_75;
padding: 0 6px 2px;
fill: $gray_75;
padding: 3px 6px;
z-index: 2;
background: $content_bg_light;
border-radius: 4px;
pointer-events: none;
touch-action: none;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.audios {
@ -141,3 +146,13 @@
align-items: center;
justify-content: center;
}
.menu {
position: absolute;
right: 0;
top: 0;
width: 48px;
height: 48px;
z-index: 10;
outline: none;
}

View file

@ -0,0 +1,44 @@
import React, { FC } from 'react';
import classnames from 'classnames';
import { Icon } from '~/components/input/Icon';
import styles from './styles.module.scss';
interface CommentLikeProps {
className?: string;
count?: number;
active?: boolean;
liked?: boolean;
onLike?: () => void;
}
const CommentLike: FC<CommentLikeProps> = ({
className,
count,
active,
liked,
onLike,
}) => {
if (!active && !count) {
return null;
}
return (
<div
onClick={active ? onLike : undefined}
className={classnames(styles.likes, className, {
[styles.liked]: active && liked,
[styles.active]: active,
})}
>
<div className={styles.icon}>
<Icon icon={count ? 'heart_full' : 'heart'} size={14} />
</div>
{Boolean(count) && <span className={styles.count}>{count}</span>}
</div>
);
};
export { CommentLike };

View file

@ -0,0 +1,34 @@
@import 'src/styles/variables';
.likes {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
position: relative;
&.active {
cursor: pointer;
&::before {
content: ' ';
position: absolute;
inset: -10px -10px -8px -16px;
}
&:not(.liked):hover .icon {
@include pulse;
}
}
&.liked {
color: $color_like;
fill: currentColor;
}
}
.icon {
position: relative;
width: 14px;
height: 14px;
}

View file

@ -1,13 +1,6 @@
@import 'src/styles/variables';
.wrap {
position: absolute;
right: -3px;
top: -3px;
width: 48px;
height: 48px;
z-index: 10;
outline: none;
cursor: pointer;
}

View file

@ -25,7 +25,7 @@ const NodeLikeButton: FC<NodeLikeButtonProps> = ({
[styles.is_liked]: active,
})}
>
{active ? (
{count ? (
<Icon icon="heart_full" size={24} onClick={onClick} />
) : (
<Icon icon="heart" size={24} onClick={onClick} />

View file

@ -1,31 +1,5 @@
@import 'src/styles/variables';
@keyframes pulse {
0% {
transform: scale(1);
}
45% {
transform: scale(1);
}
60% {
transform: scale(1.4);
}
75% {
transform: scale(1);
}
90% {
transform: scale(1.4);
}
100% {
transform: scale(1);
}
}
.like {
transition: fill, stroke 0.25s;
will-change: transform;
@ -46,8 +20,9 @@
}
&:hover {
@include pulse;
fill: $color_like;
animation: pulse 0.75s infinite;
.count {
opacity: 0;