Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface IRoomEventFilter extends IFilterComponent {
types?: Array<EventType | string>;
related_by_senders?: Array<RelationType | string>;
related_by_rel_types?: string[];
unread_thread_notifications?: boolean;

// Unstable values
"io.element.relation_senders"?: Array<RelationType | string>;
Expand Down Expand Up @@ -220,6 +221,14 @@ export class Filter {
setProp(this.definition, "room.timeline.limit", limit);
}

/**
* Enable threads unread notification
* @param {boolean} enabled
*/
public setUnreadThreadNotifications(enabled: boolean) {
setProp(this.definition, "room.timeline.unread_thread_notifications", enabled);
}

setLazyLoadMembers(enabled: boolean) {
setProp(this.definition, "room.state.lazy_load_members", !!enabled);
}
Expand Down
65 changes: 35 additions & 30 deletions src/models/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,13 @@ export type RoomEventHandlerMap = {
[ThreadEvent.New]: (thread: Thread, toStartOfTimeline: boolean) => void;
} & ThreadHandlerMap & MatrixEventHandlerMap;

type NotificationCount = Partial<Record<NotificationCountType, number>>;

export class Room extends TimelineReceipts<EmittedEvents, RoomEventHandlerMap> {
public readonly reEmitter: TypedReEmitter<EmittedEvents, RoomEventHandlerMap>;
private txnToEvent: Record<string, MatrixEvent> = {}; // Pending in-flight requests { string: MatrixEvent }
private notificationCounts: Partial<Record<NotificationCountType, number>> = {};
private notificationCounts: NotificationCount = {};
public threadNotifications: Record<string, NotificationCount> = {};
private readonly timelineSets: EventTimelineSet[];
public readonly threadsTimelineSets: EventTimelineSet[] = [];
// any filtered timeline sets we're maintaining for this room
Expand Down Expand Up @@ -1120,14 +1123,30 @@ export class Room extends TimelineReceipts<EmittedEvents, RoomEventHandlerMap> {
return event;
}

/**
* Get sum of threads and roon notification counts
* @param {String} type The type of notification count to get. default: 'total'
* @return {Number} The notification count.
*/
public getTotalUnreadNotificationCount(type = NotificationCountType.Total): number {
return (this.getUnreadNotificationCount(type) ?? 0) +
this.getTotalThreadsUnreadNotificationCount(type);
}

public getTotalThreadsUnreadNotificationCount(type = NotificationCountType.Total): number {
return Object.keys(this.threadNotifications).reduce((total: number, threadId: string) => {
return total + (this.getThreadUnreadNotificationCount(threadId, type) ?? 0);
}, 0);
}

/**
* Get one of the notification counts for this room
* @param {String} type The type of notification count to get. default: 'total'
* @return {Number} The notification count, or undefined if there is no count
* for this type.
*/
public getUnreadNotificationCount(type = NotificationCountType.Total): number | undefined {
return this.notificationCounts[type];
return this.notificationCounts[type] ?? 0;
}

/**
Expand All @@ -1139,6 +1158,20 @@ export class Room extends TimelineReceipts<EmittedEvents, RoomEventHandlerMap> {
this.notificationCounts[type] = count;
}

public getThreadUnreadNotificationCount(threadId: string, type = NotificationCountType.Total): number | undefined {
return this.threadNotifications[threadId]?.[type];
}

public setThreadUnreadNotificationCount(threadId: string, type: NotificationCountType, count: number): void {
this.threadNotifications[threadId] = {
highlight: this.threadNotifications[threadId]?.highlight ?? 0,
total: this.threadNotifications[threadId]?.total ?? 0,
...{
[type]: count,
},
};
}

public setSummary(summary: IRoomSummary): void {
const heroes = summary["m.heroes"];
const joinedCount = summary["m.joined_member_count"];
Expand Down Expand Up @@ -2463,24 +2496,6 @@ export class Room extends TimelineReceipts<EmittedEvents, RoomEventHandlerMap> {
});
}

/**
* Gets the latest receipt for a given user in the room
* @param userId The id of the user for which we want the receipt
* @param ignoreSynthesized Whether to ignore synthesized receipts or not
* @param receiptType Optional. The type of the receipt we want to get
* @returns the latest receipts of the chosen type for the chosen user
*/
public getReadReceiptForUserId(
userId: string, ignoreSynthesized = false, receiptType = ReceiptType.Read,
): IWrappedReceipt | null {
const [realReceipt, syntheticReceipt] = this.receipts[receiptType]?.[userId] ?? [];
if (ignoreSynthesized) {
return realReceipt;
}

return syntheticReceipt ?? realReceipt;
}

/**
* Get the ID of the event that a given user has read up to, or null if we
* have received no read receipts from them.
Expand Down Expand Up @@ -2585,16 +2600,6 @@ export class Room extends TimelineReceipts<EmittedEvents, RoomEventHandlerMap> {
return false;
}

/**
* Get a list of receipts for the given event.
* @param {MatrixEvent} event the event to get receipts for
* @return {Object[]} A list of receipts with a userId, type and data keys or
* an empty list.
*/
public getReceiptsForEvent(event: MatrixEvent): ICachedReceipt[] {
return this.receiptCacheByEventId[event.getId()] || [];
}

/**
* Add a receipt event to the room.
* @param {MatrixEvent} event The m.receipt event.
Expand Down
23 changes: 17 additions & 6 deletions src/sync-accumulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export interface IJoinedRoom {
ephemeral: IEphemeral;
account_data: IAccountData;
unread_notifications: IUnreadNotificationCounts;
unread_thread_notifications?: {
[threadId: string]: IUnreadNotificationCounts;
};
}

export interface IStrippedState {
Expand Down Expand Up @@ -154,6 +157,9 @@ interface IRoom {
_summary: Partial<IRoomSummary>;
_accountData: { [eventType: string]: IMinimalEvent };
_unreadNotifications: Partial<IUnreadNotificationCounts>;
_unreadThreadNotifications?: {
[threadId: string]: Partial<IUnreadNotificationCounts>;
};
_readReceipts: {
[userId: string]: {
data: IMinimalEvent;
Expand Down Expand Up @@ -358,12 +364,13 @@ export class SyncAccumulator {
// Create truly empty objects so event types of 'hasOwnProperty' and co
// don't cause this code to break.
this.joinRooms[roomId] = {
_currentState: Object.create(null),
_timeline: [],
_accountData: Object.create(null),
_unreadNotifications: {},
_summary: {},
_readReceipts: {},
"_currentState": Object.create(null),
"_timeline": [],
"_accountData": Object.create(null),
"_unreadNotifications": {},
"_unreadThreadNotifications": {},
"_summary": {},
"_readReceipts": {},
};
}
const currentData = this.joinRooms[roomId];
Expand All @@ -379,6 +386,9 @@ export class SyncAccumulator {
if (data.unread_notifications) {
currentData._unreadNotifications = data.unread_notifications;
}
if (data.unread_thread_notifications) {
currentData._unreadThreadNotifications = data.unread_thread_notifications;
}
if (data.summary) {
const HEROES_KEY = "m.heroes";
const INVITED_COUNT_KEY = "m.invited_member_count";
Expand Down Expand Up @@ -537,6 +547,7 @@ export class SyncAccumulator {
prev_batch: null,
},
unread_notifications: roomData._unreadNotifications,
unread_thread_notifications: roomData._unreadThreadNotifications,
summary: roomData._summary as IRoomSummary,
};
// Add account data
Expand Down
27 changes: 25 additions & 2 deletions src/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ export class SyncApi {
const buildDefaultFilter = () => {
const filter = new Filter(client.credentials.userId);
filter.setTimelineLimit(this.opts.initialSyncLimit);
filter.setUnreadThreadNotifications(true);
return filter;
};

Expand Down Expand Up @@ -996,8 +997,8 @@ export class SyncApi {
}

const qps: ISyncParams = {
filter: filterId,
timeout: pollTimeout,
"filter": filterId,
"timeout": pollTimeout,
};

if (this.opts.disablePresence) {
Expand Down Expand Up @@ -1299,6 +1300,28 @@ export class SyncApi {
}
}

room.threadNotifications = {};
const unreadThreadNotifications = joinObj.unread_thread_notifications;
if (unreadThreadNotifications) {
Object.entries(unreadThreadNotifications).forEach(([threadId, unreadNotification]) => {
room.setThreadUnreadNotificationCount(
threadId,
NotificationCountType.Total,
unreadNotification.notification_count,
);

const hasUnreadNotification =
room.getThreadUnreadNotificationCount(threadId, NotificationCountType.Highlight) <= 0;
if (!encrypted || (encrypted && hasUnreadNotification)) {
room.setThreadUnreadNotificationCount(
threadId,
NotificationCountType.Highlight,
unreadNotification.highlight_count,
);
}
});
}

joinObj.timeline = joinObj.timeline || {} as ITimeline;

if (joinObj.isBrandNewRoom) {
Expand Down