@@ -18,6 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
18
18
import { isNullOrUndefined } from "matrix-js-sdk/src/utils" ;
19
19
import { MatrixEvent } from "matrix-js-sdk/src/models/event" ;
20
20
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls" ;
21
+ import { Thread } from "matrix-js-sdk/src/models/thread" ;
21
22
import { RelationType } from "matrix-js-sdk/src/matrix" ;
22
23
23
24
import { ActionPayload } from "../../dispatcher/payloads" ;
@@ -96,6 +97,43 @@ interface IState {
96
97
// Empty because we don't actually use the state
97
98
}
98
99
100
+ export interface MessagePreview {
101
+ event : MatrixEvent ;
102
+ isThreadReply : boolean ;
103
+ text : string ;
104
+ }
105
+
106
+ const isThreadReply = ( event : MatrixEvent ) : boolean => {
107
+ // a thread root event cannot be a thread reply
108
+ if ( event . isThreadRoot ) return false ;
109
+
110
+ const thread = event . getThread ( ) ;
111
+
112
+ // it cannot be a thread reply if there is no thread
113
+ if ( ! thread ) return false ;
114
+
115
+ const relation = event . getRelation ( ) ;
116
+
117
+ if (
118
+ ! ! relation &&
119
+ relation . rel_type === RelationType . Annotation &&
120
+ relation . event_id === thread . rootEvent ?. getId ( )
121
+ ) {
122
+ // annotations on the thread root are not a thread reply
123
+ return false ;
124
+ }
125
+
126
+ return true ;
127
+ } ;
128
+
129
+ const mkMessagePreview = ( text : string , event : MatrixEvent ) : MessagePreview => {
130
+ return {
131
+ event,
132
+ text,
133
+ isThreadReply : isThreadReply ( event ) ,
134
+ } ;
135
+ } ;
136
+
99
137
export class MessagePreviewStore extends AsyncStoreWithClient < IState > {
100
138
private static readonly internalInstance = ( ( ) => {
101
139
const instance = new MessagePreviewStore ( ) ;
@@ -111,7 +149,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
111
149
}
112
150
113
151
// null indicates the preview is empty / irrelevant
114
- private previews = new Map < string , Map < TagID | TAG_ANY , [ MatrixEvent , string ] | null > > ( ) ;
152
+ private previews = new Map < string , Map < TagID | TAG_ANY , MessagePreview | null > > ( ) ;
115
153
116
154
private constructor ( ) {
117
155
super ( defaultDispatcher , { } ) ;
@@ -131,7 +169,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
131
169
* @param inTagId The tag ID in which the room resides
132
170
* @returns The preview, or null if none present.
133
171
*/
134
- public async getPreviewForRoom ( room : Room , inTagId : TagID ) : Promise < string | null > {
172
+ public async getPreviewForRoom ( room : Room , inTagId : TagID ) : Promise < MessagePreview | null > {
135
173
if ( ! room ) return null ; // invalid room, just return nothing
136
174
137
175
if ( ! this . previews . has ( room . roomId ) ) await this . generatePreview ( room , inTagId ) ;
@@ -140,9 +178,9 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
140
178
if ( ! previews ) return null ;
141
179
142
180
if ( previews . has ( inTagId ) ) {
143
- return previews . get ( inTagId ) ! [ 1 ] ;
181
+ return previews . get ( inTagId ) ! ;
144
182
}
145
- return previews . get ( TAG_ANY ) ?. [ 1 ] ?? null ;
183
+ return previews . get ( TAG_ANY ) ?? null ;
146
184
}
147
185
148
186
public generatePreviewForEvent ( event : MatrixEvent ) : string {
@@ -166,16 +204,28 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
166
204
}
167
205
168
206
private async generatePreview ( room : Room , tagId ?: TagID ) : Promise < void > {
169
- const events = room . timeline ;
207
+ const events = [ ...room . getLiveTimeline ( ) . getEvents ( ) ] ;
208
+
209
+ // add last reply from each thread
210
+ room . getThreads ( ) . forEach ( ( thread : Thread ) : void => {
211
+ const lastReply = thread . lastReply ( ) ;
212
+ if ( lastReply ) events . push ( lastReply ) ;
213
+ } ) ;
214
+
215
+ // sort events from oldest to newest
216
+ events . sort ( ( a : MatrixEvent , b : MatrixEvent ) => {
217
+ return a . getTs ( ) - b . getTs ( ) ;
218
+ } ) ;
219
+
170
220
if ( ! events ) return ; // should only happen in tests
171
221
172
222
let map = this . previews . get ( room . roomId ) ;
173
223
if ( ! map ) {
174
- map = new Map < TagID | TAG_ANY , [ MatrixEvent , string ] | null > ( ) ;
224
+ map = new Map < TagID | TAG_ANY , MessagePreview | null > ( ) ;
175
225
this . previews . set ( room . roomId , map ) ;
176
226
}
177
227
178
- const previousEventInAny = map . get ( TAG_ANY ) ?. [ 0 ] ;
228
+ const previousEventInAny = map . get ( TAG_ANY ) ?. event ;
179
229
180
230
// Set the tags so we know what to generate
181
231
if ( ! map . has ( TAG_ANY ) ) map . set ( TAG_ANY , null ) ;
@@ -196,27 +246,28 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
196
246
if ( ! previewDef ) continue ;
197
247
if ( previewDef . isState && isNullOrUndefined ( event . getStateKey ( ) ) ) continue ;
198
248
199
- const anyPreview = previewDef . previewer . getTextFor ( event ) ;
200
- if ( ! anyPreview ) continue ; // not previewable for some reason
249
+ const anyPreviewText = previewDef . previewer . getTextFor ( event ) ;
250
+ if ( ! anyPreviewText ) continue ; // not previewable for some reason
201
251
202
252
if ( ! this . shouldSkipPreview ( event , previousEventInAny ) ) {
203
- changed = changed || anyPreview !== map . get ( TAG_ANY ) ?. [ 1 ] ;
204
- map . set ( TAG_ANY , [ event , anyPreview ] ) ;
253
+ changed = changed || anyPreviewText !== map . get ( TAG_ANY ) ?. text ;
254
+ map . set ( TAG_ANY , mkMessagePreview ( anyPreviewText , event ) ) ;
205
255
}
206
256
207
257
const tagsToGenerate = Array . from ( map . keys ( ) ) . filter ( ( t ) => t !== TAG_ANY ) ; // we did the any tag above
208
258
for ( const genTagId of tagsToGenerate ) {
209
- const previousEventInTag = map . get ( genTagId ) ?. [ 0 ] ;
259
+ const previousEventInTag = map . get ( genTagId ) ?. event ;
210
260
if ( this . shouldSkipPreview ( event , previousEventInTag ) ) continue ;
211
261
212
262
const realTagId = genTagId === TAG_ANY ? undefined : genTagId ;
213
263
const preview = previewDef . previewer . getTextFor ( event , realTagId ) ;
214
- if ( preview === anyPreview ) {
215
- changed = changed || anyPreview !== map . get ( genTagId ) ?. [ 1 ] ;
264
+
265
+ if ( preview === anyPreviewText ) {
266
+ changed = changed || anyPreviewText !== map . get ( genTagId ) ?. text ;
216
267
map . delete ( genTagId ) ;
217
268
} else {
218
- changed = changed || preview !== map . get ( genTagId ) ?. [ 1 ] ;
219
- map . set ( genTagId , preview ? [ event , preview ] : null ) ;
269
+ changed = changed || preview !== map . get ( genTagId ) ?. text ;
270
+ map . set ( genTagId , preview ? mkMessagePreview ( anyPreviewText , event ) : null ) ;
220
271
}
221
272
}
222
273
@@ -230,7 +281,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
230
281
}
231
282
232
283
// At this point, we didn't generate a preview so clear it
233
- this . previews . set ( room . roomId , new Map < TagID | TAG_ANY , [ MatrixEvent , string ] | null > ( ) ) ;
284
+ this . previews . set ( room . roomId , new Map < TagID | TAG_ANY , MessagePreview | null > ( ) ) ;
234
285
this . emit ( UPDATE_EVENT , this ) ;
235
286
this . emit ( MessagePreviewStore . getPreviewChangedEventName ( room ) , room ) ;
236
287
}
0 commit comments