Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit da2640b

Browse files
clokepgermain-ggGermain
authored
Display rooms & threads as unread (bold) if threads have unread messages. (#9763)
Co-authored-by: Germain <[email protected]> Co-authored-by: Germain <[email protected]> Fixes element-hq/element-web#23907
1 parent df03112 commit da2640b

File tree

9 files changed

+513
-175
lines changed

9 files changed

+513
-175
lines changed

src/Unread.ts

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ limitations under the License.
1515
*/
1616

1717
import { Room } from "matrix-js-sdk/src/models/room";
18+
import { Thread } from "matrix-js-sdk/src/models/thread";
1819
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
1920
import { EventType } from "matrix-js-sdk/src/@types/event";
2021
import { M_BEACON } from "matrix-js-sdk/src/@types/beacon";
@@ -59,35 +60,39 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean {
5960
return false;
6061
}
6162

63+
for (const timeline of [room, ...room.getThreads()]) {
64+
// If the current timeline has unread messages, we're done.
65+
if (doesRoomOrThreadHaveUnreadMessages(timeline)) {
66+
return true;
67+
}
68+
}
69+
// If we got here then no timelines were found with unread messages.
70+
return false;
71+
}
72+
73+
export function doesRoomOrThreadHaveUnreadMessages(roomOrThread: Room | Thread): boolean {
74+
// If there are no messages yet in the timeline then it isn't fully initialised
75+
// and cannot be unread.
76+
if (!roomOrThread || roomOrThread.timeline.length === 0) {
77+
return false;
78+
}
79+
6280
const myUserId = MatrixClientPeg.get().getUserId();
6381

82+
// as we don't send RRs for our own messages, make sure we special case that
83+
// if *we* sent the last message into the room, we consider it not unread!
84+
// Should fix: https://github.com/vector-im/element-web/issues/3263
85+
// https://github.com/vector-im/element-web/issues/2427
86+
// ...and possibly some of the others at
87+
// https://github.com/vector-im/element-web/issues/3363
88+
if (roomOrThread.timeline.at(-1)?.getSender() === myUserId) {
89+
return false;
90+
}
91+
6492
// get the most recent read receipt sent by our account.
6593
// N.B. this is NOT a read marker (RM, aka "read up to marker"),
6694
// despite the name of the method :((
67-
const readUpToId = room.getEventReadUpTo(myUserId!);
68-
69-
if (!SettingsStore.getValue("feature_threadstable")) {
70-
// as we don't send RRs for our own messages, make sure we special case that
71-
// if *we* sent the last message into the room, we consider it not unread!
72-
// Should fix: https://github.com/vector-im/element-web/issues/3263
73-
// https://github.com/vector-im/element-web/issues/2427
74-
// ...and possibly some of the others at
75-
// https://github.com/vector-im/element-web/issues/3363
76-
if (room.timeline.length && room.timeline[room.timeline.length - 1].getSender() === myUserId) {
77-
return false;
78-
}
79-
}
80-
81-
// if the read receipt relates to an event is that part of a thread
82-
// we consider that there are no unread messages
83-
// This might be a false negative, but probably the best we can do until
84-
// the read receipts have evolved to cater for threads
85-
if (readUpToId) {
86-
const event = room.findEventById(readUpToId);
87-
if (event?.getThread()) {
88-
return false;
89-
}
90-
}
95+
const readUpToId = roomOrThread.getEventReadUpTo(myUserId!);
9196

9297
// this just looks at whatever history we have, which if we've only just started
9398
// up probably won't be very much, so if the last couple of events are ones that
@@ -96,8 +101,8 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean {
96101
// but currently we just guess.
97102

98103
// Loop through messages, starting with the most recent...
99-
for (let i = room.timeline.length - 1; i >= 0; --i) {
100-
const ev = room.timeline[i];
104+
for (let i = roomOrThread.timeline.length - 1; i >= 0; --i) {
105+
const ev = roomOrThread.timeline[i];
101106

102107
if (ev.getId() == readUpToId) {
103108
// If we've read up to this event, there's nothing more recent

src/components/views/right_panel/RoomHeaderButtons.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ limitations under the License.
2121
import React from "react";
2222
import classNames from "classnames";
2323
import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
24+
import { ThreadEvent } from "matrix-js-sdk/src/models/thread";
2425
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
2526

2627
import { _t } from "../../../languageHandler";
@@ -44,6 +45,7 @@ import { NotificationStateEvents } from "../../../stores/notifications/Notificat
4445
import PosthogTrackers from "../../../PosthogTrackers";
4546
import { ButtonEvent } from "../elements/AccessibleButton";
4647
import { MatrixClientPeg } from "../../../MatrixClientPeg";
48+
import { doesRoomOrThreadHaveUnreadMessages } from "../../../Unread";
4749

4850
const ROOM_INFO_PHASES = [
4951
RightPanelPhases.RoomSummary,
@@ -154,7 +156,17 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
154156
if (!this.supportsThreadNotifications) {
155157
this.threadNotificationState?.on(NotificationStateEvents.Update, this.onNotificationUpdate);
156158
} else {
159+
// Notification badge may change if the notification counts from the
160+
// server change, if a new thread is created or updated, or if a
161+
// receipt is sent in the thread.
157162
this.props.room?.on(RoomEvent.UnreadNotifications, this.onNotificationUpdate);
163+
this.props.room?.on(RoomEvent.Receipt, this.onNotificationUpdate);
164+
this.props.room?.on(RoomEvent.Timeline, this.onNotificationUpdate);
165+
this.props.room?.on(RoomEvent.Redaction, this.onNotificationUpdate);
166+
this.props.room?.on(RoomEvent.LocalEchoUpdated, this.onNotificationUpdate);
167+
this.props.room?.on(RoomEvent.MyMembership, this.onNotificationUpdate);
168+
this.props.room?.on(ThreadEvent.New, this.onNotificationUpdate);
169+
this.props.room?.on(ThreadEvent.Update, this.onNotificationUpdate);
158170
}
159171
this.onNotificationUpdate();
160172
RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatus);
@@ -166,6 +178,13 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
166178
this.threadNotificationState?.off(NotificationStateEvents.Update, this.onNotificationUpdate);
167179
} else {
168180
this.props.room?.off(RoomEvent.UnreadNotifications, this.onNotificationUpdate);
181+
this.props.room?.off(RoomEvent.Receipt, this.onNotificationUpdate);
182+
this.props.room?.off(RoomEvent.Timeline, this.onNotificationUpdate);
183+
this.props.room?.off(RoomEvent.Redaction, this.onNotificationUpdate);
184+
this.props.room?.off(RoomEvent.LocalEchoUpdated, this.onNotificationUpdate);
185+
this.props.room?.off(RoomEvent.MyMembership, this.onNotificationUpdate);
186+
this.props.room?.off(ThreadEvent.New, this.onNotificationUpdate);
187+
this.props.room?.off(ThreadEvent.Update, this.onNotificationUpdate);
169188
}
170189
RoomNotificationStateStore.instance.off(UPDATE_STATUS_INDICATOR, this.onUpdateStatus);
171190
}
@@ -191,9 +210,17 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
191210
return NotificationColor.Red;
192211
case NotificationCountType.Total:
193212
return NotificationColor.Grey;
194-
default:
195-
return NotificationColor.None;
196213
}
214+
// We don't have any notified messages, but we might have unread messages. Let's
215+
// find out.
216+
for (const thread of this.props.room!.getThreads()) {
217+
// If the current thread has unread messages, we're done.
218+
if (doesRoomOrThreadHaveUnreadMessages(thread)) {
219+
return NotificationColor.Bold;
220+
}
221+
}
222+
// Otherwise, no notification color.
223+
return NotificationColor.None;
197224
}
198225

199226
private onUpdateStatus = (notificationState: SummarizedNotificationState): void => {

src/hooks/useUnreadNotifications.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ limitations under the License.
1515
*/
1616

1717
import { NotificationCount, NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
18+
import { Thread } from "matrix-js-sdk/src/models/thread";
1819
import { useCallback, useEffect, useState } from "react";
1920

2021
import { getUnsentMessages } from "../components/structures/RoomStatusBar";
2122
import { getRoomNotifsState, getUnreadNotificationCount, RoomNotifState } from "../RoomNotifs";
2223
import { NotificationColor } from "../stores/notifications/NotificationColor";
23-
import { doesRoomHaveUnreadMessages } from "../Unread";
24+
import { doesRoomOrThreadHaveUnreadMessages } from "../Unread";
2425
import { EffectiveMembership, getEffectiveMembership } from "../utils/membership";
2526
import { useEventEmitter } from "./useEventEmitter";
2627

@@ -75,12 +76,14 @@ export const useUnreadNotifications = (
7576
setColor(NotificationColor.Red);
7677
} else if (greyNotifs > 0) {
7778
setColor(NotificationColor.Grey);
78-
} else if (!threadId) {
79-
// TODO: No support for `Bold` on threads at the moment
80-
79+
} else {
8180
// We don't have any notified messages, but we might have unread messages. Let's
8281
// find out.
83-
const hasUnread = doesRoomHaveUnreadMessages(room);
82+
let roomOrThread: Room | Thread = room;
83+
if (threadId) {
84+
roomOrThread = room.getThread(threadId)!;
85+
}
86+
const hasUnread = doesRoomOrThreadHaveUnreadMessages(roomOrThread);
8487
setColor(hasUnread ? NotificationColor.Bold : NotificationColor.None);
8588
}
8689
}

0 commit comments

Comments
 (0)