mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-25 10:02:27 -04:00
133 lines
4.6 KiB
TypeScript
133 lines
4.6 KiB
TypeScript
import {EllipsisHorizontalIcon, PencilIcon, TrashIcon} from '@heroicons/react/24/outline'
|
|
import {FaceSmileIcon} from '@heroicons/react/24/solid'
|
|
import {JSONContent} from '@tiptap/react'
|
|
import clsx from 'clsx'
|
|
import {PrivateChatMessage} from 'common/chat-message'
|
|
import {Dispatch, SetStateAction, useEffect, useRef, useState} from 'react'
|
|
import {toast} from 'react-hot-toast'
|
|
import DropdownMenu, {DropdownItem} from 'web/components/comments/dropdown-menu'
|
|
import {useClickOutside} from 'web/hooks/use-click-outside'
|
|
import {useIsMobile} from 'web/hooks/use-is-mobile'
|
|
import {useUser} from 'web/hooks/use-user'
|
|
import {api} from 'web/lib/api'
|
|
import {useT} from 'web/lib/locale'
|
|
import {updateReactionUI} from 'web/lib/supabase/chat-messages'
|
|
import {handleReaction} from 'web/lib/util/message-reactions'
|
|
|
|
const REACTIONS = ['👍', '❤️', '😂', '😮', '😢', '👎']
|
|
|
|
export function MessageActions(props: {
|
|
message: {
|
|
id: number
|
|
userId: string
|
|
content: JSONContent
|
|
isEdited?: boolean
|
|
reactions?: Record<string, boolean>
|
|
}
|
|
onRequestEdit?: () => void
|
|
setMessages?: Dispatch<SetStateAction<PrivateChatMessage[] | undefined>>
|
|
className?: string
|
|
// If provided, when this key changes, the emoji picker will open
|
|
openEmojiPickerKey?: number
|
|
// If true, hide the trigger menu button and only render the picker anchor
|
|
hideTrigger?: boolean
|
|
}) {
|
|
const {message, onRequestEdit, className, setMessages, openEmojiPickerKey, hideTrigger} = props
|
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
|
const emojiPickerRef = useRef<HTMLDivElement>(null)
|
|
const user = useUser()
|
|
const isOwner = user?.id === message.userId
|
|
const isMobile = useIsMobile()
|
|
const t = useT()
|
|
|
|
useClickOutside(emojiPickerRef, () => {
|
|
setShowEmojiPicker(false)
|
|
})
|
|
|
|
// Open emoji picker when external key changes
|
|
useEffect(() => {
|
|
if (openEmojiPickerKey !== undefined) {
|
|
setShowEmojiPicker(true)
|
|
}
|
|
}, [openEmojiPickerKey])
|
|
|
|
const handleDelete = async () => {
|
|
if (!confirm(t('messages.delete_confirm', 'Are you sure you want to delete this message?')))
|
|
return
|
|
const messageId = message.id
|
|
try {
|
|
await api('delete-message', {messageId})
|
|
toast.success(t('messages.deleted', 'Message deleted'))
|
|
setMessages?.((prevMessages) => {
|
|
if (!prevMessages) return prevMessages
|
|
return prevMessages.filter((m) => m.id !== messageId)
|
|
})
|
|
} catch (error) {
|
|
toast.error(t('messages.delete_failed', 'Failed to delete message'))
|
|
console.error(error)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={clsx('flex items-center gap-1', className)}>
|
|
{showEmojiPicker && (
|
|
<div
|
|
ref={emojiPickerRef}
|
|
className={clsx(
|
|
'absolute mb-2 rounded-lg bg-canvas-100 p-2 shadow-lg pr-10 z-10 max-w-[250px]',
|
|
isMobile ? 'left-1/2 transform -translate-x-1/2' : isOwner ? 'right-20' : 'left-20',
|
|
)}
|
|
>
|
|
<div className="grid grid-cols-6 gap-8">
|
|
{REACTIONS.map((reaction) => (
|
|
<button
|
|
key={reaction}
|
|
className="text-2xl hover:scale-125"
|
|
onClick={async () => {
|
|
setShowEmojiPicker(false)
|
|
updateReactionUI(message, user, reaction, setMessages)
|
|
await handleReaction(reaction, message.id)
|
|
}}
|
|
>
|
|
{reaction}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{!hideTrigger && (
|
|
<DropdownMenu
|
|
items={
|
|
[
|
|
isOwner && {
|
|
name: t('messages.action.edit', 'Edit'),
|
|
icon: <PencilIcon className="h-4 w-4" />,
|
|
onClick: onRequestEdit,
|
|
},
|
|
isOwner && {
|
|
name: t('messages.action.delete', 'Delete'),
|
|
icon: <TrashIcon className="h-4 w-4" />,
|
|
onClick: handleDelete,
|
|
},
|
|
{
|
|
name: t('messages.action.add_reaction', 'Add Reaction'),
|
|
icon: <FaceSmileIcon className="h-4 w-4" />,
|
|
onClick: () => {
|
|
setShowEmojiPicker(!showEmojiPicker)
|
|
},
|
|
},
|
|
].filter(Boolean) as DropdownItem[]
|
|
}
|
|
closeOnClick={true}
|
|
icon={<EllipsisHorizontalIcon className="h-5 w-5 text-gray-500" />}
|
|
menuWidth="w-40"
|
|
className="text-ink-500 hover:text-ink-700 rounded-full p-1 hover:bg-canvas-50"
|
|
/>
|
|
)}
|
|
{/*{message.isEdited && (*/}
|
|
{/* <span className="text-xs text-gray-400">{t('messages.edited', 'edited')}</span>*/}
|
|
{/*)}*/}
|
|
</div>
|
|
)
|
|
}
|