riot-web/src/Unread.ts

153 lines
5.6 KiB
TypeScript
Raw Normal View History

/*
Copyright 2024 New Vector Ltd.
Copyright 2015-2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { M_BEACON, Room, Thread, MatrixEvent, EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
2023-04-11 09:41:59 +02:00
import { logger } from "matrix-js-sdk/src/logger";
2021-06-22 18:23:13 +02:00
2022-12-12 12:24:14 +01:00
import shouldHideEvent from "./shouldHideEvent";
import { haveRendererForEvent } from "./events/EventTileFactory";
import SettingsStore from "./settings/SettingsStore";
Pop out of Threads Activity Centre (#12136) * Add `Thread Activity centre` labs flag * Rename translation string * WIP Thread Activity Centre * Update supportedLevels * css lint * i18n lint * Fix labs subsection test * Update Threads Activity Centre label * Rename Thread Activity Centre to Threads Activity Centre * Use compound `MenuItem` instead of custom button * Color thread icon when hovered * Make the pop-up scrollable and add a max height * Remove Math.random in key * Remove unused class * Change add comments on `mx_ThreadsActivityRows` and `mx_ThreadsActivityRow` * Make threads activity centre labs flag split out unread counts Just shows notif & unread counts for main thread if the TAC is enabled. * Fix tests * Simpler fix * Open thread panel when thread clicke in Threads Activity Centre Hopefully this is a sensible enough way. The panel will stay open of course (ie. if you go to a different room & come back), but that's the nature of the right panel. * Dynamic state of room * Add doc * Use the StatelessNotificationBadge component in ThreadsActivityCentre and re-use the existing NotificationLevel * Remove unused style * Add room sorting * Fix `ThreadsActivityRow` props doc * Pass in & cache the status of the TAC labs flag * Pass includeThreads as setting to doesRoomHaveUnreadMessages too * Fix tests * Add analytics to the TAC (#12179) * Update TAC label (#12186) * Add `IndicatorIcon` to the TAC button (#12182) Add `IndicatorIcon` to the TAC button * Threads don't have activity if the room is muted This makes it match the computation in determineUnreadState. Ideally this logic should all be in one place. * Re-use doesRoomHaveUnreadThreads for useRoomThreadNotifications This incorporates the logic of not showing unread dots if the room is muted * Add TAC description in labs (#12197) * Fox position & size of dot on the tac button IndicatorIcon doesn't like having the size of its icon adjusted and we probably shouldn't do it anyway: better to specify to the component what size we want it. * TAC: Utils tests (#12200) * Add tests for `doesRoomHaveUnreadThreads` * Add tests for `getThreadNotificationLevel` * Add test for the ThreadsActivityCentre component * Add snapshot test * Fix narrow hover background on TAC button Make the button 32x32 (and the inner icon 24x24) * Add caption for empty TAC * s/tac/threads_activity_centre/ * Fix i18n & add tests * Add playwright tests for the TAC (#12227) * Fox comments --------- Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2024-02-07 14:49:40 +01:00
import { RoomNotifState, getRoomNotifsState } from "./RoomNotifs";
/**
2022-03-11 19:59:49 +01:00
* Returns true if this event arriving in a room should affect the room's
* count of unread messages
*
* @param client The Matrix Client instance of the logged-in user
* @param {Object} ev The event
* @returns {boolean} True if the given event should affect the unread message count
*/
export function eventTriggersUnreadCount(client: MatrixClient, ev: MatrixEvent): boolean {
if (ev.getSender() === client.getSafeUserId()) {
return false;
}
2021-06-22 18:23:13 +02:00
switch (ev.getType()) {
case EventType.RoomMember:
case EventType.RoomThirdPartyInvite:
case EventType.CallAnswer:
case EventType.CallHangup:
case EventType.RoomCanonicalAlias:
case EventType.RoomServerAcl:
case M_BEACON.name:
case M_BEACON.altName:
2021-06-22 18:23:13 +02:00
return false;
}
if (ev.isRedacted()) return false;
return haveRendererForEvent(ev, client, false /* hidden messages should never trigger unread counts anyways */);
}
export function doesRoomHaveUnreadMessages(room: Room, includeThreads: boolean): boolean {
Implement MSC3575: Sliding Sync (#8328) * Add labs flag for sliding sync; add sliding_sync_proxy_url to config.json * Disable the labs toggle if sliding_sync_proxy_url is not set * Do validation checks on the sliding sync proxy URL before enabling it in Labs * Enable sliding sync and add SlidingSyncManager * Get room subscriptions working * Hijack renderSublists in sliding sync mode * Add support for sorting alphabetically/recency and room name filters * Filter out tombstoned rooms; start adding show more logic list ranges update but the UI doesn't * update the UI when the list is updated * bugfix: make sure the list sorts numerically * Get invites transitioning correctly * Force enable sliding sync and labs for now * Linting * Disable spotlight search * Initial cypress plugins for Sliding Sync Proxy * Use --rm when running Synapse in Docker for Cypress tests * Update src/MatrixClientPeg.ts Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/components/views/rooms/RoomSublist.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/settings/controllers/SlidingSyncController.ts Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/components/views/rooms/RoomSublist.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * WIP add room searching to spotlight search * Only read sliding sync results when there is a result, else use the local cache * Use feature_sliding_sync not slidingSync * Some review comments * More review comments * Use RoomViewStore to set room subscriptions * Comment why any * Update src/components/views/rooms/RoomSublist.tsx Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * Fix cypress docker abstraction * Iterate sliding sync proxy support * Stash mostly functional test * Update sliding sync proxy image * i18n * Add support for spaces; use list ID -> index mappings - Mappings are more reusable and easier to understand than racing for index positions. - Register for all spaces immediately on startup. * When the active space is updated, update the list registration * Set spaces filter in the correct place * Skeleton placeholder whilst loading the space * Filter out spaces from the room list * Use the new txn_id promises * Ensure we actually resolve list registrations * Fix matrix-org/sliding-sync#30: don't show tombstoned search results * Remove unused imports * Add SYNCV3_SECRET to proxy to ensure it starts up; correct aliases for SS test * Add another basic sliding sync e2e test * Unbreak netlify * Add more logging for debugging duplicate rooms * If sliding sync is enabled, always use the rooms result even if it's empty * Drop-in copy of RoomListStore for sliding sync * Remove conditionals from RoomListStore - we have SlidingRoomListStore now * WIP SlidingRoomListStore * Add most sliding sync logic to SlidingRoomListStore Still lots of logic in RoomSublist. Broken things: - Join count is wrong completely. - No skeleton placeholder when switching spaces. * Migrate joined count to SS RLS * Reinstate the skeleton UI when the list is loading * linting * Add support for sticky rooms based on the currently active room * Add a bunch of passing SS E2E tests; some WIP * Unbreak build from git merge * Suppress unread indicators in sliding sync mode * Add regression test for https://github.com/matrix-org/sliding-sync/issues/28 * Add invite test flows; show the invite list The refactor to SS RLS removed the invite list entirely. * Remove show more click as it wasn't the bug * Linting and i18n * only enable SS by default on netlify * Jest fixes; merge conflict fixes; remove debug logging; use right sort enum values * Actually fix jest tests * Add support for favourites and low priority * Bump sliding sync version * Update sliding sync labs to be user configurable * delint * To disable SS or change proxy URL the user has to log out * Review comments * Linting * Apply suggestions from code review Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/stores/room-list/SlidingRoomListStore.ts Co-authored-by: Travis Ralston <travisr@matrix.org> * Review comments * Add issue link for TODO markers * Linting * Apply suggestions from code review Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * More review comments * More review comments * stricter types Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: Travis Ralston <travisr@matrix.org>
2022-09-07 17:42:39 +02:00
if (SettingsStore.getValue("feature_sliding_sync")) {
// TODO: https://github.com/vector-im/element-web/issues/23207
// Sliding Sync doesn't support unread indicator dots (yet...)
return false;
}
const toCheck: Array<Room | Thread> = [room];
if (includeThreads) {
toCheck.push(...room.getThreads());
}
for (const withTimeline of toCheck) {
if (doesTimelineHaveUnreadMessages(room, withTimeline.timeline)) {
// We found an unread, so the room is unread
return true;
}
}
// If we got here then no timelines were found with unread messages.
return false;
}
function doesTimelineHaveUnreadMessages(room: Room, timeline: Array<MatrixEvent>): boolean {
// The room is a space, let's ignore it
if (room.isSpaceRoom()) return false;
const myUserId = room.client.getSafeUserId();
const latestImportantEventId = findLatestImportantEvent(room.client, timeline)?.getId();
if (latestImportantEventId) {
return !room.hasUserReadEvent(myUserId, latestImportantEventId);
} else {
// We couldn't find an important event to check - check the unimportant ones.
const earliestUnimportantEventId = timeline.at(0)?.getId();
if (!earliestUnimportantEventId) {
// There are no events in this timeline - it is uninitialised, so we
// consider it read
return false;
} else if (room.hasUserReadEvent(myUserId, earliestUnimportantEventId)) {
// Some of the unimportant events are read, and there are no
// important ones after them, so we've read everything.
return false;
} else {
// We have events. and none of them are read. We must guess that
// the timeline is unread, because there could be older unread
// important events that we don't have loaded.
logger.warn("Falling back to unread room because of no read receipt or counting message found", {
roomId: room.roomId,
earliestUnimportantEventId: earliestUnimportantEventId,
});
return true;
}
}
}
Pop out of Threads Activity Centre (#12136) * Add `Thread Activity centre` labs flag * Rename translation string * WIP Thread Activity Centre * Update supportedLevels * css lint * i18n lint * Fix labs subsection test * Update Threads Activity Centre label * Rename Thread Activity Centre to Threads Activity Centre * Use compound `MenuItem` instead of custom button * Color thread icon when hovered * Make the pop-up scrollable and add a max height * Remove Math.random in key * Remove unused class * Change add comments on `mx_ThreadsActivityRows` and `mx_ThreadsActivityRow` * Make threads activity centre labs flag split out unread counts Just shows notif & unread counts for main thread if the TAC is enabled. * Fix tests * Simpler fix * Open thread panel when thread clicke in Threads Activity Centre Hopefully this is a sensible enough way. The panel will stay open of course (ie. if you go to a different room & come back), but that's the nature of the right panel. * Dynamic state of room * Add doc * Use the StatelessNotificationBadge component in ThreadsActivityCentre and re-use the existing NotificationLevel * Remove unused style * Add room sorting * Fix `ThreadsActivityRow` props doc * Pass in & cache the status of the TAC labs flag * Pass includeThreads as setting to doesRoomHaveUnreadMessages too * Fix tests * Add analytics to the TAC (#12179) * Update TAC label (#12186) * Add `IndicatorIcon` to the TAC button (#12182) Add `IndicatorIcon` to the TAC button * Threads don't have activity if the room is muted This makes it match the computation in determineUnreadState. Ideally this logic should all be in one place. * Re-use doesRoomHaveUnreadThreads for useRoomThreadNotifications This incorporates the logic of not showing unread dots if the room is muted * Add TAC description in labs (#12197) * Fox position & size of dot on the tac button IndicatorIcon doesn't like having the size of its icon adjusted and we probably shouldn't do it anyway: better to specify to the component what size we want it. * TAC: Utils tests (#12200) * Add tests for `doesRoomHaveUnreadThreads` * Add tests for `getThreadNotificationLevel` * Add test for the ThreadsActivityCentre component * Add snapshot test * Fix narrow hover background on TAC button Make the button 32x32 (and the inner icon 24x24) * Add caption for empty TAC * s/tac/threads_activity_centre/ * Fix i18n & add tests * Add playwright tests for the TAC (#12227) * Fox comments --------- Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2024-02-07 14:49:40 +01:00
/**
* Returns true if this room has unread threads.
* @param room The room to check
* @returns {boolean} True if the given room has unread threads
*/
export function doesRoomHaveUnreadThreads(room: Room): boolean {
if (getRoomNotifsState(room.client, room.roomId) === RoomNotifState.Mute) {
// No unread for muted rooms, nor their threads
// NB. This logic duplicated in RoomNotifs.determineUnreadState
return false;
}
for (const thread of room.getThreads()) {
if (doesTimelineHaveUnreadMessages(room, thread.timeline)) {
// We found an unread, so the room has an unread thread
return true;
}
}
// If we got here then no threads were found with unread messages.
return false;
}
export function doesRoomOrThreadHaveUnreadMessages(roomOrThread: Room | Thread): boolean {
const room = roomOrThread instanceof Thread ? roomOrThread.room : roomOrThread;
const events = roomOrThread instanceof Thread ? roomOrThread.timeline : room.getLiveTimeline().getEvents();
return doesTimelineHaveUnreadMessages(room, events);
}
/**
* Look backwards through the timeline and find the last event that is
* "important" in the sense of isImportantEvent.
*
* @returns the latest important event, or null if none were found
*/
function findLatestImportantEvent(client: MatrixClient, timeline: Array<MatrixEvent>): MatrixEvent | null {
for (let index = timeline.length - 1; index >= 0; index--) {
const event = timeline[index];
if (isImportantEvent(client, event)) {
return event;
}
}
return null;
}
/**
* Given this event does not have a receipt, is it important enough to make
* this room unread?
*/
function isImportantEvent(client: MatrixClient, event: MatrixEvent): boolean {
return !shouldHideEvent(event) && eventTriggersUnreadCount(client, event);
}