polfan-server-js-client 0.2.95 → 0.2.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/workspace.xml +34 -26
- package/build/index.cjs.js +420 -305
- package/build/index.cjs.js.map +1 -1
- package/build/index.umd.js +1 -1
- package/build/index.umd.js.map +1 -1
- package/build/types/state-tracker/FollowedTopicsManager.d.ts +63 -0
- package/build/types/state-tracker/MessagesManager.d.ts +1 -46
- package/build/types/state-tracker/RoomsManager.d.ts +2 -0
- package/package.json +1 -1
- package/src/IndexedObjectCollection.ts +5 -6
- package/src/state-tracker/FollowedTopicsManager.ts +377 -0
- package/src/state-tracker/MessagesManager.ts +3 -331
- package/src/state-tracker/RoomsManager.ts +4 -1
|
@@ -1,42 +1,25 @@
|
|
|
1
1
|
import {ChatStateTracker} from "./ChatStateTracker";
|
|
2
2
|
import {
|
|
3
|
-
NewMessage,
|
|
4
|
-
FollowedTopic,
|
|
5
|
-
TopicFollowed,
|
|
6
3
|
RoomDeleted,
|
|
7
4
|
RoomLeft,
|
|
8
|
-
TopicDeleted,
|
|
9
|
-
FollowedTopicUpdated,
|
|
10
5
|
RoomJoined,
|
|
11
|
-
NewTopic,
|
|
12
6
|
Session,
|
|
13
7
|
Room,
|
|
14
8
|
Message, ChatLocation,
|
|
15
9
|
} from "../types/src";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
ObservableIndexedObjectCollection
|
|
19
|
-
} from "../IndexedObjectCollection";
|
|
20
|
-
import {DeferredTask, PromiseRegistry} from "./AsyncUtils";
|
|
10
|
+
import {IndexedCollection} from "../IndexedObjectCollection";
|
|
11
|
+
import {DeferredTask} from "./AsyncUtils";
|
|
21
12
|
import {RoomMessagesHistory} from "./RoomMessagesHistory";
|
|
22
13
|
|
|
23
14
|
export class MessagesManager {
|
|
24
15
|
private readonly roomHistories = new IndexedCollection<string, RoomMessagesHistory>();
|
|
25
|
-
private readonly followedTopics = new IndexedCollection<string, ObservableIndexedObjectCollection<FollowedTopic>>();
|
|
26
|
-
private readonly followedTopicsPromises = new PromiseRegistry();
|
|
27
16
|
private readonly deferredSession = new DeferredTask();
|
|
28
|
-
private readonly unreadSummariesCache = new Map<string, { mentionCount: number, isUnread: boolean }>();
|
|
29
17
|
|
|
30
18
|
public constructor(private tracker: ChatStateTracker) {
|
|
31
19
|
this.tracker.client.on('Session', ev => this.handleSession(ev));
|
|
32
20
|
this.tracker.client.on('RoomJoined', ev => this.handleRoomJoin(ev));
|
|
33
|
-
this.tracker.client.on('NewTopic', ev => this.handleNewTopic(ev));
|
|
34
|
-
this.tracker.client.on('FollowedTopicUpdated', ev => this.handleFollowedTopicUpdated(ev));
|
|
35
|
-
this.tracker.client.on('TopicFollowed', ev => this.handleTopicFollowed(ev));
|
|
36
|
-
this.tracker.client.on('NewMessage', ev => this.handleNewMessage(ev));
|
|
37
21
|
this.tracker.client.on('RoomDeleted', ev => this.handleRoomDeleted(ev));
|
|
38
22
|
this.tracker.client.on('RoomLeft', ev => this.handleRoomLeft(ev));
|
|
39
|
-
this.tracker.client.on('TopicDeleted', ev => this.handleTopicDeleted(ev));
|
|
40
23
|
}
|
|
41
24
|
|
|
42
25
|
/**
|
|
@@ -47,155 +30,6 @@ export class MessagesManager {
|
|
|
47
30
|
return this.roomHistories.get(roomId);
|
|
48
31
|
}
|
|
49
32
|
|
|
50
|
-
/**
|
|
51
|
-
* Cache followed topics for all joined rooms in a space and fetch them in bulk if necessary.
|
|
52
|
-
* Then you can get them using getRoomFollowedTopics().
|
|
53
|
-
* @see getRoomFollowedTopics
|
|
54
|
-
*/
|
|
55
|
-
public async cacheSpaceFollowedTopics(spaceId: string | null): Promise<void> {
|
|
56
|
-
if (spaceId && ! (await this.tracker.spaces.get()).has(spaceId)) {
|
|
57
|
-
throw new Error(`You are not in space ${spaceId}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const roomIds = (await this.tracker.rooms.get()).findBy('spaceId', spaceId).items.map(room => room.id);
|
|
61
|
-
|
|
62
|
-
if (! roomIds.length) {
|
|
63
|
-
// We don't need to ping server for followed topics for this space, if user has no joined rooms
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const resultPromise = this.tracker.client.send('GetFollowedTopics', {location: {spaceId}});
|
|
68
|
-
|
|
69
|
-
roomIds.forEach(roomId => this.followedTopicsPromises.register(resultPromise, roomId));
|
|
70
|
-
|
|
71
|
-
const result = await resultPromise;
|
|
72
|
-
|
|
73
|
-
if (result.error) {
|
|
74
|
-
throw result.error;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
this.setFollowedTopicsArray(roomIds, result.data.followedTopics);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get followed topics for the given room.
|
|
82
|
-
* @return Undefined if you are not in the room, collection otherwise.
|
|
83
|
-
*/
|
|
84
|
-
public async getRoomFollowedTopics(roomId: string): Promise<ObservableIndexedObjectCollection<FollowedTopic> | undefined> {
|
|
85
|
-
if (! (await this.tracker.rooms.get()).has(roomId)) {
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (! this.followedTopics.has(roomId)) {
|
|
90
|
-
if (this.followedTopicsPromises.notExist(roomId)) {
|
|
91
|
-
this.followedTopicsPromises.registerByFunction(async () => {
|
|
92
|
-
const result = await this.tracker.client.send('GetFollowedTopics', {location: {roomId}});
|
|
93
|
-
|
|
94
|
-
if (result.error) {
|
|
95
|
-
throw result.error;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.setFollowedTopicsArray([roomId], result.data.followedTopics);
|
|
99
|
-
}, roomId);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
await this.followedTopicsPromises.get(roomId);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return this.followedTopics.get(roomId);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Batch acknowledge all messages for given room.
|
|
110
|
-
*/
|
|
111
|
-
public async ackRoom(roomId: string): Promise<void> {
|
|
112
|
-
const collection = await this.getRoomFollowedTopics(roomId);
|
|
113
|
-
|
|
114
|
-
if (! collection) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
for (const followedTopic of collection.items) {
|
|
119
|
-
if (followedTopic.isUnread) {
|
|
120
|
-
await this.tracker.client.send('Ack', {location: followedTopic.location});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Calculate missed messages with mentions from any topic in given room.
|
|
127
|
-
* @return Undefined if you are not in room.
|
|
128
|
-
*/
|
|
129
|
-
public async summarizeUnreadMessages(location: ChatLocation): Promise<{ mentionCount: number, isUnread: boolean }> {
|
|
130
|
-
const cacheKey = location.topicId
|
|
131
|
-
? `topic:${location.roomId}:${location.topicId}`
|
|
132
|
-
: location.roomId
|
|
133
|
-
? `room:${location.roomId}`
|
|
134
|
-
: location.spaceId
|
|
135
|
-
? `space:${location.spaceId}`
|
|
136
|
-
: 'spaceless';
|
|
137
|
-
|
|
138
|
-
if (this.unreadSummariesCache.has(cacheKey)) {
|
|
139
|
-
return this.unreadSummariesCache.get(cacheKey)!;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let roomIds: string[] = [];
|
|
143
|
-
let targetTopicId: string | undefined;
|
|
144
|
-
const rooms = await this.tracker.rooms.get();
|
|
145
|
-
|
|
146
|
-
if (location.topicId) {
|
|
147
|
-
if (!location.roomId) {
|
|
148
|
-
throw new Error("roomId is required when querying by topicId");
|
|
149
|
-
}
|
|
150
|
-
roomIds = [location.roomId];
|
|
151
|
-
targetTopicId = location.topicId;
|
|
152
|
-
} else if (location.roomId) {
|
|
153
|
-
roomIds = [location.roomId];
|
|
154
|
-
} else if (location.spaceId) {
|
|
155
|
-
await this.cacheSpaceFollowedTopics(location.spaceId);
|
|
156
|
-
roomIds = rooms.findBy('spaceId', location.spaceId).items.map(r => r.id);
|
|
157
|
-
} else {
|
|
158
|
-
roomIds = rooms.items.filter(r => !r.spaceId).map(r => r.id);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let mentionCount = 0;
|
|
162
|
-
let isUnread = false;
|
|
163
|
-
|
|
164
|
-
for (const roomId of roomIds) {
|
|
165
|
-
const collection = await this.getRoomFollowedTopics(roomId);
|
|
166
|
-
|
|
167
|
-
if (!collection) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
for (const topic of collection.items) {
|
|
172
|
-
if (targetTopicId && topic.location.topicId !== targetTopicId) {
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (topic.isUnread) {
|
|
177
|
-
isUnread = true;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
mentionCount += (topic.mentionCount ?? 0);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const result = { mentionCount, isUnread };
|
|
185
|
-
this.unreadSummariesCache.set(cacheKey, result);
|
|
186
|
-
|
|
187
|
-
return result;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* For internal use. If you want to delete the message, execute a proper command on client object.
|
|
192
|
-
* @internal
|
|
193
|
-
*/
|
|
194
|
-
public _deleteByTopicIds(roomId: string, ...topicIds: string[]): void {
|
|
195
|
-
this.followedTopics.get(roomId)?.delete(...topicIds);
|
|
196
|
-
topicIds.forEach(topicId => this.invalidateUnreadSummaries(roomId, topicId));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
33
|
/**
|
|
200
34
|
* For internal use.
|
|
201
35
|
* @internal
|
|
@@ -220,187 +54,25 @@ export class MessagesManager {
|
|
|
220
54
|
return message || null;
|
|
221
55
|
}
|
|
222
56
|
|
|
223
|
-
/**
|
|
224
|
-
* Wyczyść cache celowo, tylko dla lokalizacji których dotyczy zmiana.
|
|
225
|
-
*/
|
|
226
|
-
private invalidateUnreadSummaries(roomId?: string, topicId?: string): void {
|
|
227
|
-
if (!roomId) {
|
|
228
|
-
this.unreadSummariesCache.clear();
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
this.unreadSummariesCache.delete(`room:${roomId}`);
|
|
233
|
-
|
|
234
|
-
if (topicId) {
|
|
235
|
-
this.unreadSummariesCache.delete(`topic:${roomId}:${topicId}`);
|
|
236
|
-
} else {
|
|
237
|
-
for (const key of this.unreadSummariesCache.keys()) {
|
|
238
|
-
if (key.startsWith(`topic:${roomId}:`)) {
|
|
239
|
-
this.unreadSummariesCache.delete(key);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
for (const key of this.unreadSummariesCache.keys()) {
|
|
245
|
-
if (key.startsWith('space:') || key === 'spaceless') {
|
|
246
|
-
this.unreadSummariesCache.delete(key);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private invalidateUnreadSummariesForRooms(roomIds: string[]): void {
|
|
252
|
-
const roomIdsSet = new Set(roomIds);
|
|
253
|
-
roomIds.forEach(roomId => {
|
|
254
|
-
this.unreadSummariesCache.delete(`room:${roomId}`);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
for (const key of this.unreadSummariesCache.keys()) {
|
|
258
|
-
if (key.startsWith('space:') || key === 'spaceless') {
|
|
259
|
-
this.unreadSummariesCache.delete(key);
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (key.startsWith('topic:')) {
|
|
263
|
-
const parts = key.split(':');
|
|
264
|
-
if (parts.length >= 2 && roomIdsSet.has(parts[1])) {
|
|
265
|
-
this.unreadSummariesCache.delete(key);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
57
|
private createHistoryForNewRoom(room: Room): void {
|
|
272
58
|
this.roomHistories.set([room.id, new RoomMessagesHistory(room, this.tracker)]);
|
|
273
59
|
}
|
|
274
60
|
|
|
275
|
-
private handleNewMessage(ev: NewMessage): void {
|
|
276
|
-
void this.updateLocallyFollowedTopicOnNewMessage(ev);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private handleFollowedTopicUpdated(ev: FollowedTopicUpdated): void {
|
|
280
|
-
this.followedTopics.get(ev.followedTopic.location.roomId)?.set(ev.followedTopic);
|
|
281
|
-
this.invalidateUnreadSummaries(ev.followedTopic.location.roomId, ev.followedTopic.location.topicId);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
private handleTopicFollowed(ev: TopicFollowed): void {
|
|
285
|
-
this.setFollowedTopicsArray([ev.followedTopic.location.roomId], [ev.followedTopic]);
|
|
286
|
-
this.invalidateUnreadSummaries(ev.followedTopic.location.roomId, ev.followedTopic.location.topicId);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
61
|
private handleRoomDeleted(ev: RoomDeleted): void {
|
|
290
62
|
this.roomHistories.delete(ev.id);
|
|
291
|
-
this.clearRoomFollowedTopicsStructures(ev.id);
|
|
292
63
|
}
|
|
293
64
|
|
|
294
65
|
private handleRoomJoin(ev: RoomJoined): void {
|
|
295
|
-
this.createHistoryForNewRoom(ev.room)
|
|
296
|
-
this.clearRoomFollowedTopicsStructures(ev.room.id);
|
|
66
|
+
this.createHistoryForNewRoom(ev.room);
|
|
297
67
|
}
|
|
298
68
|
|
|
299
69
|
private handleRoomLeft(ev: RoomLeft): void {
|
|
300
70
|
this.roomHistories.delete(ev.id);
|
|
301
|
-
this.clearRoomFollowedTopicsStructures(ev.id);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private async handleNewTopic(ev: NewTopic): Promise<void> {
|
|
305
|
-
if (this.followedTopics.has(ev.roomId)) {
|
|
306
|
-
// Check if the new topic is followed by user
|
|
307
|
-
// only if client asked for followed topics list before for this room
|
|
308
|
-
const result = await this.tracker.client.send(
|
|
309
|
-
'GetFollowedTopics',
|
|
310
|
-
{location: {roomId: ev.roomId, topicId: ev.topic.id}},
|
|
311
|
-
);
|
|
312
|
-
const followedTopic = result.data.followedTopics[0];
|
|
313
|
-
if (followedTopic) {
|
|
314
|
-
this.followedTopics.get(ev.roomId).set(followedTopic);
|
|
315
|
-
this.invalidateUnreadSummaries(ev.roomId, ev.topic.id);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
private handleTopicDeleted(ev: TopicDeleted): void {
|
|
321
|
-
this.followedTopics.get(ev.location.roomId)?.delete(ev.location.topicId);
|
|
322
|
-
this.invalidateUnreadSummaries(ev.location.roomId, ev.location.topicId);
|
|
323
71
|
}
|
|
324
72
|
|
|
325
73
|
private handleSession(ev: Session): void {
|
|
326
|
-
this.followedTopics.deleteAll();
|
|
327
|
-
this.followedTopicsPromises.forgetAll();
|
|
328
|
-
this.invalidateUnreadSummaries();
|
|
329
74
|
this.roomHistories.deleteAll();
|
|
330
75
|
ev.state.rooms.forEach(room => this.createHistoryForNewRoom(room));
|
|
331
76
|
this.deferredSession.resolve();
|
|
332
77
|
}
|
|
333
|
-
|
|
334
|
-
private async updateLocallyFollowedTopicOnNewMessage(ev: NewMessage): Promise<void> {
|
|
335
|
-
const roomFollowedTopics = this.followedTopics.get(ev.message.location.roomId);
|
|
336
|
-
const followedTopic = roomFollowedTopics?.get(ev.message.location.topicId);
|
|
337
|
-
|
|
338
|
-
if (!roomFollowedTopics || !followedTopic || ev.message.type === 'Ephemeral') {
|
|
339
|
-
// Skip if we don't follow this room or targeted topic or the message is ephemeral
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const isMe = ev.message.author.user.id === this.tracker.me?.id;
|
|
344
|
-
|
|
345
|
-
let update: Partial<FollowedTopic>;
|
|
346
|
-
|
|
347
|
-
if (isMe) {
|
|
348
|
-
// Reset missed messages count if new message is authored by me
|
|
349
|
-
update = {missed: 0, lastAckMessageId: ev.message.id};
|
|
350
|
-
} else if (ev.message.type === 'Text') {
|
|
351
|
-
// ...check for mentions if it's text message...
|
|
352
|
-
const member = await this.tracker.rooms.getMe(ev.message.location.roomId);
|
|
353
|
-
const roleIds = [...(member.spaceMember?.roles ?? []), ...member.roles];
|
|
354
|
-
const mentionHandlers = [
|
|
355
|
-
...roleIds.map(id => `<@&${id}>`),
|
|
356
|
-
`<@${ev.message.author.user.id}>`,
|
|
357
|
-
];
|
|
358
|
-
const mentionExists = mentionHandlers.some(handler => ev.message.content.includes(handler));
|
|
359
|
-
|
|
360
|
-
update = {
|
|
361
|
-
missed: followedTopic.missed === null ? null : followedTopic.missed + 1,
|
|
362
|
-
isUnread: true,
|
|
363
|
-
mentionCount: followedTopic.mentionCount + (mentionExists ? 1 : 0),
|
|
364
|
-
};
|
|
365
|
-
} else {
|
|
366
|
-
// ...or just mark as unread.
|
|
367
|
-
update = {
|
|
368
|
-
missed: followedTopic.missed === null ? null : followedTopic.missed + 1,
|
|
369
|
-
isUnread: true,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
roomFollowedTopics.set({...followedTopic, ...update});
|
|
374
|
-
this.invalidateUnreadSummaries(ev.message.location.roomId, ev.message.location.topicId);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
private setFollowedTopicsArray(roomIds: string[], followedTopics: FollowedTopic[]): void {
|
|
378
|
-
const roomToTopics: {[roomId: string]: FollowedTopic[]} = {};
|
|
379
|
-
|
|
380
|
-
// Reassign followed topics to limit collection change event emit
|
|
381
|
-
followedTopics.forEach(followedTopic => {
|
|
382
|
-
roomToTopics[followedTopic.location.roomId] ??= [];
|
|
383
|
-
roomToTopics[followedTopic.location.roomId].push(followedTopic);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
roomIds.forEach(roomId => {
|
|
387
|
-
if (! this.followedTopics.has(roomId)) {
|
|
388
|
-
this.followedTopics.set([roomId, new ObservableIndexedObjectCollection<FollowedTopic>(
|
|
389
|
-
followedTopic => followedTopic.location.topicId
|
|
390
|
-
)]);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (roomToTopics[roomId]) {
|
|
394
|
-
this.followedTopics.get(roomId).set(...roomToTopics[roomId]);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
this.invalidateUnreadSummariesForRooms(roomIds);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
private clearRoomFollowedTopicsStructures(roomId: string): void {
|
|
402
|
-
this.followedTopics.delete(roomId);
|
|
403
|
-
this.followedTopicsPromises.forget(roomId);
|
|
404
|
-
this.invalidateUnreadSummaries(roomId);
|
|
405
|
-
}
|
|
406
78
|
}
|
|
@@ -16,9 +16,11 @@ import {
|
|
|
16
16
|
import {ChatStateTracker} from "./ChatStateTracker";
|
|
17
17
|
import {DeferredTask, PromiseRegistry} from "./AsyncUtils";
|
|
18
18
|
import {MessagesManager} from "./MessagesManager";
|
|
19
|
+
import {FollowedTopicsManager} from "./FollowedTopicsManager";
|
|
19
20
|
|
|
20
21
|
export class RoomsManager {
|
|
21
22
|
public readonly messages: MessagesManager;
|
|
23
|
+
public readonly followedTopics: FollowedTopicsManager;
|
|
22
24
|
|
|
23
25
|
private readonly list = new ObservableIndexedObjectCollection<Room>('id');
|
|
24
26
|
private readonly topics = new IndexedCollection<string, ObservableIndexedObjectCollection<Topic>>();
|
|
@@ -29,6 +31,7 @@ export class RoomsManager {
|
|
|
29
31
|
|
|
30
32
|
public constructor(private tracker: ChatStateTracker) {
|
|
31
33
|
this.messages = new MessagesManager(tracker);
|
|
34
|
+
this.followedTopics = new FollowedTopicsManager(tracker);
|
|
32
35
|
|
|
33
36
|
this.tracker.client.on('NewMessage', ev => this.handleNewMessage(ev));
|
|
34
37
|
this.tracker.client.on('MessagesRedacted', ev => this.handleMessagesRedacted(ev));
|
|
@@ -136,7 +139,7 @@ export class RoomsManager {
|
|
|
136
139
|
|
|
137
140
|
for (const roomId of roomIds) {
|
|
138
141
|
const topicIds: string[] = this.topics.get(roomId)?.items.map(topic => topic.id) ?? [];
|
|
139
|
-
this.
|
|
142
|
+
this.followedTopics._deleteByTopicIds(roomId, ...topicIds);
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
this.topics.delete(...roomIds);
|