Add emoji picker, reactions user_id & UI tweaks

Introduce client-side emoji picker and richer reaction UX, and propagate user IDs for reactions over the WS protocol. Backend: include user_id in reaction events. Frontend: add EmojiPicker component; update useChatSocket types to include user_id; handle "reaction" events in ChatRoomPage to update cached messages (add/remove/switch reactions). Message component: UI overhaul — action menu with slide-out actions, toggle button (⋮→✕), emoji picker trigger, grouped reaction buttons that show counts and indicate "mine" state, and various layout/cleanups. Media: defer video loading by setting preload="none" in ChatMediaGallery, MediaGallery and PostComposer to improve performance.
This commit is contained in:
2026-06-04 00:14:34 +02:00
parent bb09f0ccd3
commit 3859659b13
8 changed files with 193 additions and 78 deletions

View File

@@ -148,7 +148,7 @@ export default function ChatRoomPage() {
(event: ChatSocketEvent) => {
if (event.type === "new_chat_message" || event.type === "new_reply_chat_message") {
let replyTo: MessageModel["reply_to"] = null;
if (event.type === "new_reply_chat_message" && event.reply_to_id != null) {
if (event.reply_to_id != null) {
const cached = queryClient.getQueryData<{ pages: CursorPaginated<MessageModel>[] }>(
messagesQueryKey(chatId),
);
@@ -184,6 +184,31 @@ export default function ChatRoomPage() {
);
} else if (event.type === "stop_typing") {
setTypingUsers((prev) => prev.filter((u) => u !== event.user));
} else if (event.type === "reaction") {
queryClient.setQueryData<{
pages: CursorPaginated<MessageModel>[];
pageParams: unknown[];
}>(messagesQueryKey(chatId), (old) => {
if (!old) return old as never;
return {
...old,
pages: old.pages.map((p) => ({
...p,
results: p.results.map((m) => {
if (m.id !== event.message_id) return m;
let reactions = [...(m.reactions ?? [])] as typeof m.reactions;
if (event.action === "added") {
reactions = [...reactions, { id: -Date.now(), user: event.user_id, emoji: event.emoji, created_at: new Date().toISOString() } as never];
} else if (event.action === "removed") {
reactions = reactions.filter((r) => !((r.user as unknown as number) === event.user_id && r.emoji === event.emoji));
} else if (event.action === "switched") {
reactions = reactions.map((r) => (r.user as unknown as number) === event.user_id ? { ...r, emoji: event.emoji } : r);
}
return { ...m, reactions };
}),
})),
};
});
}
},
[appendMessage, removeMessage, chatId, user, queryClient],