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...
+
+ );
+}