Skip to content

Commit

Permalink
Add initial emoji reaction in comments
Browse files Browse the repository at this point in the history
Update comment reactions
Update user showcase issue
Add joined reaction counts
Count reactions on go
Update handling of reactions from another user
Fix remove reaction to add if not from current user
Update comment list and thread item
Add reaction counts and update styles
Add comment delete cleanup
Add support for dynamic reaction count showcase
Add with delayed tooltip commponent
Refactor Comment reactions common code to a separate component
Add CommentEmoji component
  • Loading branch information
Komediruzecki authored and Rokt33r committed Mar 1, 2022
1 parent 7b96e5f commit d3eef27
Show file tree
Hide file tree
Showing 14 changed files with 796 additions and 120 deletions.
34 changes: 34 additions & 0 deletions src/cloud/api/comments/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,40 @@ export async function deleteComment({ id }: { id: string }) {
await callApi(`api/comments/${id}`, { method: 'delete' })
}

export async function removeReactionFromComment({
commentId,
reactionId,
}: {
commentId: string
reactionId: string
}): Promise<Comment> {
const { comment } = await callApi<UpdateCommentResponseBody>(
`api/comments/${commentId}/reaction`,
{
method: 'delete',
json: { reactionId: reactionId },
}
)
return deserialize(comment)
}

export async function addReaction({
id,
reaction,
}: {
id: string
reaction: string
}): Promise<Comment> {
const { comment } = await callApi<UpdateCommentResponseBody>(
`api/comments/${id}/reaction`,
{
method: 'post',
json: { reaction: reaction },
}
)
return deserialize(comment)
}

function deserialize(comment: SerializedComment): Comment {
return {
...comment,
Expand Down
58 changes: 58 additions & 0 deletions src/cloud/components/CommentEmoji.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react'
import { Emoji } from 'emoji-mart'
import cc from 'classcat'
import Flexbox from '../../design/components/atoms/Flexbox'
import { IconSize } from '../../design/components/atoms/Icon'
import WithDelayedTooltip from '../../design/components/atoms/WithDelayedTooltip'

interface EmojiIconProps {
emoji: string
defaultIcon?: string
className?: string
style?: React.CSSProperties
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void
size?: IconSize
tooltip?: React.ReactNode
emojiTextContent?: React.ReactNode
tooltipDelay: number
tooltipSide?: 'right' | 'bottom' | 'bottom-right' | 'top'
}

const CommentEmoji = ({
emoji,
defaultIcon,
className,
style,
size = 34,
tooltip,
tooltipDelay = 0,
onClick,
emojiTextContent,
tooltipSide,
}: EmojiIconProps) => {
if (emoji == null && defaultIcon == null) {
return null
}

return (
<Flexbox
style={{ marginRight: 5, cursor: 'pointer', ...style }}
flex={'0 0 auto'}
className={cc([onClick != null && 'button', className])}
onClick={onClick}
>
<WithDelayedTooltip
tooltip={tooltip}
tooltipDelay={tooltipDelay}
side={tooltipSide}
>
<Flexbox direction={'row'}>
<Emoji emoji={emoji} set='apple' size={size} />
{emojiTextContent != null && emojiTextContent}
</Flexbox>
</WithDelayedTooltip>
</Flexbox>
)
}

export default CommentEmoji
131 changes: 102 additions & 29 deletions src/cloud/components/Comments/CommentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@ import styled from '../../../design/lib/styled'
import UserIcon from '../UserIcon'
import { format } from 'date-fns'
import Icon from '../../../design/components/atoms/Icon'
import { mdiClose, mdiPencil, mdiTrashCanOutline } from '@mdi/js'
import {
mdiClose,
mdiEmoticonHappyOutline,
mdiPencil,
mdiTrashCanOutline,
} from '@mdi/js'
import { SerializedUser } from '../../interfaces/db/user'
import CommentInput from './CommentInput'
import sortBy from 'ramda/es/sortBy'
import prop from 'ramda/es/prop'
import { toText } from '../../lib/comments'
import CommentReactions from './CommentReactions'
import EmojiPickHandler from './EmojiPickHandler'

interface CommentThreadProps {
comments: Comment[]
className: string
updateComment: (comment: Comment, message: string) => Promise<any>
deleteComment: (comment: Comment) => void
deleteComment: (comment: Comment) => Promise<any>
addReaction: (comment: Comment, emoji: string) => Promise<any>
removeReaction: (comment: Comment, reactionId: string) => Promise<any>
user?: SerializedUser
users: SerializedUser[]
}
Expand All @@ -25,6 +34,8 @@ function CommentList({
className,
updateComment,
deleteComment,
addReaction,
removeReaction,
user,
users,
}: CommentThreadProps) {
Expand All @@ -42,6 +53,9 @@ function CommentList({
deleteComment={deleteComment}
editable={user != null && comment.user.id === user.id}
users={users}
addReaction={addReaction}
removeReaction={removeReaction}
user={user}
/>
</div>
))}
Expand All @@ -52,19 +66,32 @@ function CommentList({
interface CommentItemProps {
comment: Comment
updateComment: (comment: Comment, message: string) => Promise<any>
deleteComment: (comment: Comment) => void
deleteComment: (comment: Comment) => Promise<any>
addReaction: (comment: Comment, emoji: string) => Promise<any>
removeReaction: (comment: Comment, reactionId: string) => Promise<any>
editable?: boolean
users: SerializedUser[]
user?: SerializedUser
}

const smallUserIconStyle = { width: '28px', height: '28px', lineHeight: '26px' }

export interface EmojiReactionData {
id: string
emoji: string
count: number
userIds: string[]
}

export function CommentItem({
comment,
editable,
updateComment,
deleteComment,
addReaction,
removeReaction,
users,
user,
}: CommentItemProps) {
const [editing, setEditing] = useState(false)
const [showingContextMenu, setShowingContextMenu] = useState<boolean>(false)
Expand All @@ -81,6 +108,50 @@ export function CommentItem({
return toText(comment.message, users)
}, [comment.message, users])

const contextMenuItems = useCallback(() => {
if (editable) {
return (
<div className={'comment__meta__actions'}>
<EmojiPickHandler
className='comment__meta__actions__emoji'
comment={comment}
addReaction={addReaction}
removeReaction={removeReaction}
user={user}
>
<Icon size={20} path={mdiEmoticonHappyOutline} />
</EmojiPickHandler>
<div
onClick={() => setEditing(true)}
className='comment__meta__actions__edit'
>
<Icon size={20} path={mdiPencil} />
</div>
<div
onClick={() => deleteComment(comment)}
className='comment__meta__actions__remove'
>
<Icon size={20} path={mdiTrashCanOutline} />
</div>
</div>
)
} else {
return (
<div className={'comment__meta__actions'}>
<EmojiPickHandler
className='comment__meta__actions__emoji'
comment={comment}
addReaction={addReaction}
removeReaction={removeReaction}
user={user}
>
<Icon size={20} path={mdiEmoticonHappyOutline} />
</EmojiPickHandler>
</div>
)
}
}, [addReaction, comment, deleteComment, editable, removeReaction, user])

return (
<CommentItemContainer>
<div className='comment__icon'>
Expand All @@ -98,29 +169,14 @@ export function CommentItem({
<span className='comment__meta__date'>
{format(comment.createdAt, 'hh:mmaaa MMM do')}
</span>
{editable &&
(editing ? (
<div onClick={() => setEditing(false)}>
<Icon path={mdiClose} />
</div>
) : (
showingContextMenu && (
<div className={'comment__meta__actions'}>
<div
onClick={() => setEditing(true)}
className='comment__meta__actions__edit'
>
<Icon size={20} path={mdiPencil} />
</div>
<div
onClick={() => deleteComment(comment)}
className='comment__meta__actions__remove'
>
<Icon size={20} path={mdiTrashCanOutline} />
</div>
</div>
)
))}

{editing ? (
<div onClick={() => setEditing(false)}>
<Icon path={mdiClose} />
</div>
) : (
showingContextMenu && contextMenuItems()
)}
</div>
{editing ? (
<CommentInput
Expand All @@ -131,7 +187,16 @@ export function CommentItem({
users={users}
/>
) : (
<div className='comment__message'>{content}</div>
<>
<div className='comment__message'>{content}</div>
<CommentReactions
comment={comment}
addReaction={addReaction}
removeReaction={removeReaction}
users={users}
user={user}
/>
</>
)}
</div>
</CommentItemContainer>
Expand Down Expand Up @@ -183,6 +248,13 @@ const CommentItemContainer = styled.div`
}
}
.comment__add__reaction__button {
color: #9e9e9e;
background-color: ${({ theme }) => theme.colors.background.tertiary};
border-radius: 6px;
padding: 4px 8px;
}
.comment__message {
white-space: pre-wrap;
word-break: break-word;
Expand All @@ -200,10 +272,11 @@ const CommentItemContainer = styled.div`
gap: 4px;
border-radius: ${({ theme }) => theme.borders.radius}px;
background-color: #1e2024;
background-color: ${({ theme }) => theme.colors.background.primary};
.comment__meta__actions__edit,
.comment__meta__actions__remove {
.comment__meta__actions__remove,
.comment__meta__actions__emoji {
height: 20px;
margin: 3px;
Expand Down
Loading

0 comments on commit d3eef27

Please sign in to comment.