diff --git a/matrixgw_frontend/src/utils/RoomEventsManager.ts b/matrixgw_frontend/src/utils/RoomEventsManager.ts index 5a8f72d..36e5a1a 100644 --- a/matrixgw_frontend/src/utils/RoomEventsManager.ts +++ b/matrixgw_frontend/src/utils/RoomEventsManager.ts @@ -17,7 +17,7 @@ export interface Message { time_sent: number; time_sent_dayjs: dayjs.Dayjs; modified: boolean; - reactions: MessageReaction[]; + reactions: Map; content: string; image?: string; } @@ -80,7 +80,7 @@ export class RoomEventsManager { event_id: evt.id, account: evt.sender, modified: false, - reactions: [], + reactions: new Map(), time_sent: evt.time, time_sent_dayjs: dayjs.unix(evt.time / 1000), image: data.content.file?.url, @@ -93,12 +93,16 @@ export class RoomEventsManager { const message = this.messages.find( (m) => m.event_id === data.content["m.relates_to"].event_id ); + const key = data.content["m.relates_to"].key; if (!message) continue; - message.reactions.push({ + + if (!message.reactions.has(key)) message.reactions.set(key, []); + + message.reactions.get(key)!.push({ account: evt.sender, event_id: evt.id, - key: data.content["m.relates_to"].key, + key, }); } } diff --git a/matrixgw_frontend/src/widgets/EmojiIcon.tsx b/matrixgw_frontend/src/widgets/EmojiIcon.tsx new file mode 100644 index 0000000..a51b780 --- /dev/null +++ b/matrixgw_frontend/src/widgets/EmojiIcon.tsx @@ -0,0 +1,24 @@ +import { Emoji, EmojiStyle } from "emoji-picker-react"; + +function emojiUnicode(emoji: string): string { + let comp; + if (emoji.length === 1) { + comp = emoji.charCodeAt(0); + } + comp = + (emoji.charCodeAt(0) - 0xd800) * 0x400 + + (emoji.charCodeAt(1) - 0xdc00) + + 0x10000; + if (comp < 0) { + comp = emoji.charCodeAt(0); + } + const s = comp.toString(16); + return s.includes("f") ? s : `${s}-fe0f`; +} + +export function EmojiIcon(p: { emojiKey: string }): React.ReactElement { + const unified = emojiUnicode(p.emojiKey); + return ( + + ); +} diff --git a/matrixgw_frontend/src/widgets/messages/RoomMessagesList.tsx b/matrixgw_frontend/src/widgets/messages/RoomMessagesList.tsx index a912ada..a06349c 100644 --- a/matrixgw_frontend/src/widgets/messages/RoomMessagesList.tsx +++ b/matrixgw_frontend/src/widgets/messages/RoomMessagesList.tsx @@ -5,6 +5,7 @@ import { Box, Button, ButtonGroup, + Chip, Dialog, DialogActions, DialogContent, @@ -22,8 +23,14 @@ import type { Room } from "../../api/matrix/MatrixApiRoom"; import { useAlert } from "../../hooks/contexts_provider/AlertDialogProvider"; import { useConfirm } from "../../hooks/contexts_provider/ConfirmDialogProvider"; import { useLoadingMessage } from "../../hooks/contexts_provider/LoadingMessageProvider"; -import type { Message, RoomEventsManager } from "../../utils/RoomEventsManager"; +import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider"; +import type { + Message, + MessageReaction, + RoomEventsManager, +} from "../../utils/RoomEventsManager"; import { useUserInfo } from "../dashboard/BaseAuthenticatedPage"; +import { EmojiIcon } from "../EmojiIcon"; import { AccountIcon } from "./AccountIcon"; export function RoomMessagesList(p: { @@ -83,6 +90,7 @@ function RoomMessage(p: { const user = useUserInfo(); const alert = useAlert(); const confirm = useConfirm(); + const snackbar = useSnackbar(); const loadingMessage = useLoadingMessage(); const [showImageFullScreen, setShowImageFullScreen] = React.useState(false); @@ -140,6 +148,20 @@ function RoomMessage(p: { } }; + const handleToggleReaction = async ( + key: string, + reaction: MessageReaction + ) => { + try { + if (!reaction) + await MatrixApiEvent.ReactToEvent(p.room, p.message.event_id, key); + else await MatrixApiEvent.DeleteEvent(p.room, reaction.event_id); + } catch (e) { + console.error(`Failed to toggle reaction!`, e); + snackbar(`Failed to toggle reaction! ${e}`); + } + }; + return ( <> {/* Print date if required */} @@ -243,6 +265,49 @@ function RoomMessage(p: { + {/* Reaction */} + + {[...p.message.reactions.keys()].map((r) => { + const userReaction = p.message.reactions + .get(r)! + .find((r) => r.account === user.info.matrix_user_id); + return ( + handleToggleReaction(r, userReaction) }, + label: { style: { height: "2em" } }, + }} + color={userReaction !== undefined ? "success" : undefined} + variant="filled" + label={ + +
+ +
+
+ {p.message.reactions.get(r)?.length} +
+
+ } + /> + ); + })} +
+ {/* Full screen image dialog */} r.key === p.emojiKey && r.account === user.info.matrix_user_id - ) !== undefined + p.message.reactions + .get(p.emojiKey) + ?.find( + (r) => r.key === p.emojiKey && r.account === user.info.matrix_user_id + ) !== undefined ) return <>; - return ; + return ( + + ); }