Skip to content

Commit b7fd2f2

Browse files
committed
feat: display reaction on context menu
1 parent cc74946 commit b7fd2f2

File tree

6 files changed

+108
-38
lines changed

6 files changed

+108
-38
lines changed

components/message/chat/window/ChatHeader.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface ChatHeaderProps {
1919
export const ChatHeader = memo(
2020
({ chatroom, onlineCount, typingUsers }: ChatHeaderProps) => {
2121
const handleMoreAction = () => {
22-
toast.success('功能正在开发中...')
22+
toast.success('杂鱼杂鱼杂鱼, 功能正在开发中...')
2323
}
2424

2525
const hasTypingUsers = Object.keys(typingUsers).length > 0
@@ -60,3 +60,5 @@ export const ChatHeader = memo(
6060
)
6161
}
6262
)
63+
64+
ChatHeader.displayName = 'ChatHeader'

components/message/chat/window/ChatMessage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ export const ChatMessage = ({
222222
onClose={() => setIsMenuOpen(false)}
223223
anchorPoint={anchorPoint}
224224
onReaction={handleReaction}
225+
reactionArray={message.reaction}
225226
onDelete={onOpenDelete}
226227
onReply={onReply}
227228
onEdit={onEdit}

components/message/chat/window/ChatMessageContextMenu.tsx

Lines changed: 101 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,70 @@ import {
44
Dropdown,
55
DropdownMenu,
66
DropdownItem,
7-
DropdownTrigger
7+
DropdownTrigger,
8+
Tooltip,
9+
Avatar,
10+
DropdownSection
811
} from '@heroui/react'
912
import { CornerDownRight, Trash2, Edit } from 'lucide-react'
10-
import type { Key } from 'react'
13+
import { useMemo, type Key } from 'react'
14+
// import { formatDate } from '~/utils/time'
15+
import type { ChatMessageReaction } from '~/types/api/chat'
1116

1217
interface Props {
1318
isMenuOpen: boolean
1419
onClose: () => void
1520
anchorPoint: { x: number; y: number }
1621
onReaction: (emoji: string) => void
22+
reactionArray: ChatMessageReaction[]
1723
onDelete?: () => void
1824
onReply?: () => void
1925
onEdit?: () => void
2026
isOwner?: boolean
2127
}
2228

23-
const commonReactions = ['🥰', '👍', '❤️', '🤨', '🙄']
29+
const commonReactions = [
30+
'🥰',
31+
'👍',
32+
'❤️',
33+
'🤨',
34+
'🙄',
35+
'😎',
36+
'😱',
37+
'😭',
38+
'🔥',
39+
'🎉'
40+
]
2441

2542
export const ChatMessageContextMenu = ({
2643
isMenuOpen,
2744
onClose,
2845
anchorPoint,
2946
onReaction,
47+
reactionArray,
3048
onDelete,
3149
onReply,
3250
onEdit,
3351
isOwner = false
3452
}: Props) => {
3553
if (!isMenuOpen) return null
3654

55+
const groupedReactions = useMemo(() => {
56+
if (!reactionArray || reactionArray.length === 0) {
57+
return {}
58+
}
59+
return reactionArray.reduce(
60+
(acc, reaction) => {
61+
if (!acc[reaction.emoji]) {
62+
acc[reaction.emoji] = []
63+
}
64+
acc[reaction.emoji].push(reaction)
65+
return acc
66+
},
67+
{} as Record<string, ChatMessageReaction[]>
68+
)
69+
}, [reactionArray])
70+
3771
const handleMenuAction = (key: Key) => {
3872
switch (key) {
3973
case 'reply':
@@ -65,42 +99,76 @@ export const ChatMessageContextMenu = ({
6599
textValue="Reactions"
66100
isReadOnly
67101
key="reactions"
68-
className="gap-1 cursor-default data-[hover=true]:bg-background"
69-
>
70-
{commonReactions.map((emoji) => (
71-
<span
72-
key={emoji}
73-
className="cursor-pointer p-1 rounded-full text-lg hover:bg-default-200"
74-
onClick={() => handleReactionClick(emoji)}
75-
>
76-
{emoji}
77-
</span>
78-
))}
79-
</DropdownItem>
80-
81-
<DropdownItem
82-
key="reply"
83-
startContent={<CornerDownRight className="size-4" />}
102+
className="gap-1 px-0 cursor-default data-[hover=true]:bg-background"
84103
>
85-
回复
104+
<div className="grid grid-cols-5">
105+
{commonReactions.map((emoji) => (
106+
<span
107+
key={emoji}
108+
className="cursor-pointer p-1 flex justify-center items-center rounded-full text-lg hover:bg-default-200"
109+
onClick={() => handleReactionClick(emoji)}
110+
>
111+
{emoji}
112+
</span>
113+
))}
114+
</div>
86115
</DropdownItem>
87116

88-
{isOwner ? (
89-
<DropdownItem key="edit" startContent={<Edit className="size-4" />}>
90-
编辑
91-
</DropdownItem>
92-
) : null}
93-
94-
{isOwner ? (
117+
<DropdownSection showDivider title="消息操作">
95118
<DropdownItem
96-
key="delete"
97-
className="text-danger"
98-
color="danger"
99-
startContent={<Trash2 className="size-4" />}
119+
key="reply"
120+
startContent={<CornerDownRight className="size-4" />}
100121
>
101-
删除
122+
回复
102123
</DropdownItem>
103-
) : null}
124+
125+
{isOwner ? (
126+
<DropdownItem
127+
key="edit"
128+
startContent={<Edit className="size-4" />}
129+
>
130+
编辑
131+
</DropdownItem>
132+
) : null}
133+
134+
{isOwner ? (
135+
<DropdownItem
136+
key="delete"
137+
className="text-danger"
138+
color="danger"
139+
startContent={<Trash2 className="size-4" />}
140+
>
141+
删除
142+
</DropdownItem>
143+
) : null}
144+
</DropdownSection>
145+
146+
<DropdownSection title="回应表情">
147+
<>
148+
{Object.entries(groupedReactions).map(([emoji, reactions]) => (
149+
<DropdownItem
150+
key={`reaction-detail-${emoji}`}
151+
isReadOnly
152+
startContent={<span className="text-xs">{emoji}</span>}
153+
endContent={
154+
<span className="text-xs text-default-600">
155+
{reactions.length}
156+
</span>
157+
}
158+
className="cursor-default data-[hover=true]:bg-background"
159+
textValue={`${emoji} ${reactions.length}`}
160+
>
161+
<span className="text-xs">{reactions[0].user.name}</span>
162+
{reactions.length > 1 && (
163+
<span className="text-xs text-default-500">
164+
{' '}
165+
{reactions.length - 1}
166+
</span>
167+
)}
168+
</DropdownItem>
169+
))}
170+
</>
171+
</DropdownSection>
104172
</DropdownMenu>
105173
</Dropdown>
106174
</div>

components/message/chat/window/MessageList.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,5 @@ export const MessageList = memo(
6969
)
7070
}
7171
)
72+
73+
MessageList.displayName = 'MessageList'

migration/userDailyUploadSize.mjs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ async function migrateDailyUploadSize() {
66
try {
77
console.log('Starting migration...')
88

9-
// Step 1: 更新所有用户的数据,将 MB 转换为 Byte(避免精度丢失)
109
await prisma.$executeRaw`UPDATE "user" SET "daily_upload_size" = "daily_upload_size" * 1024 * 1024;`
1110

1211
console.log('Data conversion completed.')
1312

14-
// Step 2: 修改数据库字段类型
1513
await prisma.$executeRaw`ALTER TABLE "user" ALTER COLUMN "daily_upload_size" SET DATA TYPE INT;`
1614

1715
console.log('Column type updated to INT.')
@@ -22,5 +20,4 @@ async function migrateDailyUploadSize() {
2220
}
2321
}
2422

25-
// 执行迁移
2623
migrateDailyUploadSize()

socket/handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Server, Socket } from 'socket.io'
22
import { prisma } from '~/prisma/index'
33
import { registerChatEventHandlers } from './event/register'
4-
import { broadcastRoomStatus } from './event/roomStatus' // 1. 引入辅助函数
4+
import { broadcastRoomStatus } from './event/roomStatus'
55

66
export const onSocketConnection = async (io: Server, socket: Socket) => {
77
try {

0 commit comments

Comments
 (0)