From 30e63bfdb444b8afe190d7d03c150bdab48c9545 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 1 Dec 2025 17:23:15 +0100 Subject: [PATCH] Handle typing events --- matrixgw_frontend/src/api/WsApi.ts | 3 +- .../src/utils/RoomEventsManager.ts | 5 +++ .../src/widgets/messages/RoomWidget.tsx | 6 ++-- .../src/widgets/messages/TypingNotice.tsx | 35 +++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 matrixgw_frontend/src/widgets/messages/TypingNotice.tsx diff --git a/matrixgw_frontend/src/api/WsApi.ts b/matrixgw_frontend/src/api/WsApi.ts index 556851e..334ca21 100644 --- a/matrixgw_frontend/src/api/WsApi.ts +++ b/matrixgw_frontend/src/api/WsApi.ts @@ -69,7 +69,8 @@ export type WsMessage = | RoomMessageEvent | RoomReactionEvent | RoomRedactionEvent - | RoomReceiptEvent; + | RoomReceiptEvent + | RoomTypingEvent; export class WsApi { /** diff --git a/matrixgw_frontend/src/utils/RoomEventsManager.ts b/matrixgw_frontend/src/utils/RoomEventsManager.ts index 5a4d041..08a5a95 100644 --- a/matrixgw_frontend/src/utils/RoomEventsManager.ts +++ b/matrixgw_frontend/src/utils/RoomEventsManager.ts @@ -31,11 +31,13 @@ export class RoomEventsManager { private events: MatrixEvent[]; messages: Message[]; endToken?: string; + typingUsers: string[]; constructor(room: Room, initialMessages: MatrixEventsList) { this.room = room; this.events = []; this.messages = []; + this.typingUsers = []; this.processNewEvents(initialMessages); } @@ -88,6 +90,9 @@ export class RoomEventsManager { file: m.data.file, }, }; + } else if (m.type === "TypingEvent") { + this.typingUsers = m.user_ids; + return true; } else { // Ignore event console.info("Event not supported => ignored"); diff --git a/matrixgw_frontend/src/widgets/messages/RoomWidget.tsx b/matrixgw_frontend/src/widgets/messages/RoomWidget.tsx index 61d33be..5dce7ea 100644 --- a/matrixgw_frontend/src/widgets/messages/RoomWidget.tsx +++ b/matrixgw_frontend/src/widgets/messages/RoomWidget.tsx @@ -1,11 +1,12 @@ import React from "react"; +import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent"; import type { UsersMap } from "../../api/matrix/MatrixApiProfile"; import type { Room } from "../../api/matrix/MatrixApiRoom"; +import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider"; import { RoomEventsManager } from "../../utils/RoomEventsManager"; import { RoomMessagesList } from "./RoomMessagesList"; import { SendMessageForm } from "./SendMessageForm"; -import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent"; -import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider"; +import { TypingNotice } from "./TypingNotice"; export function RoomWidget(p: { room: Room; @@ -36,6 +37,7 @@ export function RoomWidget(p: { onClick={handleRoomClick} > + ); diff --git a/matrixgw_frontend/src/widgets/messages/TypingNotice.tsx b/matrixgw_frontend/src/widgets/messages/TypingNotice.tsx new file mode 100644 index 0000000..4223811 --- /dev/null +++ b/matrixgw_frontend/src/widgets/messages/TypingNotice.tsx @@ -0,0 +1,35 @@ +import { Typography } from "@mui/material"; +import React from "react"; +import type { UsersMap } from "../../api/matrix/MatrixApiProfile"; +import type { RoomEventsManager } from "../../utils/RoomEventsManager"; +import { useUserInfo } from "../dashboard/BaseAuthenticatedPage"; + +export function TypingNotice(p: { + users: UsersMap; + manager: RoomEventsManager; +}): React.ReactElement { + const user = useUserInfo(); + + const users = React.useMemo( + () => + [...p.users.values()].filter( + (u) => + p.manager.typingUsers.includes(u.user_id) && + u.user_id !== user.info.matrix_user_id + ), + [p.manager.typingUsers] + ); + + if (users.length === 0) return <>; + + return ( + + {users.map((u) => u.display_name ?? u.display_name).join(", ")}{" "} + {users.length > 1 ? "are" : "is"} typing... + + ); +}