@@ -25,11 +25,9 @@ use eyeball_im::VectorDiff;
25
25
use futures:: SendGallery ;
26
26
use futures_core:: Stream ;
27
27
use imbl:: Vector ;
28
- #[ cfg( feature = "unstable-msc4274" ) ]
29
- use matrix_sdk:: attachment:: { AttachmentInfo , Thumbnail } ;
30
28
use matrix_sdk:: {
31
29
Result ,
32
- attachment:: AttachmentConfig ,
30
+ attachment:: { AttachmentInfo , Thumbnail } ,
33
31
deserialized_responses:: TimelineEvent ,
34
32
event_cache:: { EventCacheDropHandles , RoomEventCache } ,
35
33
executor:: JoinHandle ,
@@ -43,24 +41,19 @@ use matrix_sdk::{
43
41
use mime:: Mime ;
44
42
use pinned_events_loader:: PinnedEventsRoom ;
45
43
use ruma:: {
46
- EventId , OwnedEventId , UserId ,
44
+ EventId , OwnedEventId , OwnedTransactionId , UserId ,
47
45
api:: client:: receipt:: create_receipt:: v3:: ReceiptType ,
48
46
events:: {
49
- AnyMessageLikeEventContent , AnySyncTimelineEvent ,
47
+ AnyMessageLikeEventContent , AnySyncTimelineEvent , Mentions ,
50
48
poll:: unstable_start:: { NewUnstablePollStartEventContent , UnstablePollStartEventContent } ,
51
49
receipt:: { Receipt , ReceiptThread } ,
52
50
room:: {
53
- message:: { ReplyWithinThread , RoomMessageEventContentWithoutRelation } ,
51
+ message:: { FormattedBody , ReplyWithinThread , RoomMessageEventContentWithoutRelation } ,
54
52
pinned_events:: RoomPinnedEventsEventContent ,
55
53
} ,
56
54
} ,
57
55
room_version_rules:: RoomVersionRules ,
58
56
} ;
59
- #[ cfg( feature = "unstable-msc4274" ) ]
60
- use ruma:: {
61
- OwnedTransactionId ,
62
- events:: { Mentions , room:: message:: FormattedBody } ,
63
- } ;
64
57
use subscriber:: TimelineWithDropHandle ;
65
58
use thiserror:: Error ;
66
59
use tracing:: { instrument, trace, warn} ;
@@ -176,6 +169,22 @@ pub enum DateDividerMode {
176
169
Monthly ,
177
170
}
178
171
172
+ /// Configuration for sending an attachment.
173
+ ///
174
+ /// Like [`matrix_sdk::attachment::AttachmentConfig`], but instead of the
175
+ /// `reply` field, there's only a `replied_to` event id; it's the timeline
176
+ /// deciding to fill the rest of the reply parameters.
177
+ #[ derive( Debug , Default ) ]
178
+ pub struct AttachmentConfig {
179
+ pub txn_id : Option < OwnedTransactionId > ,
180
+ pub info : Option < AttachmentInfo > ,
181
+ pub thumbnail : Option < Thumbnail > ,
182
+ pub caption : Option < String > ,
183
+ pub formatted_caption : Option < FormattedBody > ,
184
+ pub mentions : Option < Mentions > ,
185
+ pub replied_to : Option < OwnedEventId > ,
186
+ }
187
+
179
188
impl Timeline {
180
189
/// Returns the room for this timeline.
181
190
pub fn room ( & self ) -> & Room {
@@ -290,44 +299,21 @@ impl Timeline {
290
299
// thread relation ourselves.
291
300
if let AnyMessageLikeEventContent :: RoomMessage ( ref room_msg_content) = content
292
301
&& room_msg_content. relates_to . is_none ( )
293
- && let Some ( thread_root ) = self . controller . thread_root ( )
302
+ && self . controller . is_threaded ( )
294
303
{
295
- // The latest event id is used for the reply-to fallback, for clients which
296
- // don't handle threads. It should be correctly set to the latest
297
- // event in the thread, which the timeline instance might or might
298
- // not know about; in this case, we do a best effort of filling it, and resort
299
- // to using the thread root if we don't know about any event.
300
- //
301
- // Note: we could trigger a back-pagination if the timeline is empty, and wait
302
- // for the results, if the timeline is too often empty.
303
- let latest_event_id = self
304
- . controller
305
- . items ( )
304
+ let reply = self
305
+ . infer_reply ( None )
306
306
. await
307
- . iter ( )
308
- . rev ( )
309
- . find_map ( |item| {
310
- if let TimelineItemKind :: Event ( event) = item. kind ( ) {
311
- event. event_id ( ) . map ( ToOwned :: to_owned)
312
- } else {
313
- None
314
- }
315
- } )
316
- . unwrap_or ( thread_root) ;
317
-
307
+ . expect ( "a reply will always be set for threaded timelines" ) ;
318
308
let content = self
319
309
. room ( )
320
310
. make_reply_event (
321
311
// Note: this `.into()` gets rid of the relation, but we've checked previously
322
312
// that the `relates_to` field wasn't set.
323
313
room_msg_content. clone ( ) . into ( ) ,
324
- Reply {
325
- event_id : latest_event_id,
326
- enforce_thread : EnforceThread :: Threaded ( ReplyWithinThread :: No ) ,
327
- } ,
314
+ reply,
328
315
)
329
316
. await ?;
330
-
331
317
Ok ( self . room ( ) . send_queue ( ) . send ( content. into ( ) ) . await ?)
332
318
} else {
333
319
// Otherwise, we send the message as is.
@@ -362,19 +348,62 @@ impl Timeline {
362
348
content : RoomMessageEventContentWithoutRelation ,
363
349
replied_to : OwnedEventId ,
364
350
) -> Result < ( ) , Error > {
365
- let enforce_thread = if self . controller . thread_root ( ) . is_some ( ) {
366
- EnforceThread :: Threaded ( ReplyWithinThread :: Yes )
367
- } else {
368
- EnforceThread :: MaybeThreaded
369
- } ;
370
- let content = self
371
- . room ( )
372
- . make_reply_event ( content, Reply { event_id : replied_to, enforce_thread } )
373
- . await ?;
351
+ let reply = self
352
+ . infer_reply ( Some ( replied_to) )
353
+ . await
354
+ . expect ( "the reply will always be set because we provided a replied-to event id" ) ;
355
+ let content = self . room ( ) . make_reply_event ( content, reply) . await ?;
374
356
self . send ( content. into ( ) ) . await ?;
375
357
Ok ( ( ) )
376
358
}
377
359
360
+ /// Given a message or media to send, and an optional `replied_to` event,
361
+ /// automatically fills the [`Reply`] information based on the current
362
+ /// timeline focus.
363
+ pub ( crate ) async fn infer_reply ( & self , replied_to : Option < OwnedEventId > ) -> Option < Reply > {
364
+ // If there's a replied-to event id, the reply is pretty straightforward, and we
365
+ // should only infer the `EnforceThread` based on the current focus.
366
+ if let Some ( replied_to) = replied_to {
367
+ let enforce_thread = if self . controller . is_threaded ( ) {
368
+ EnforceThread :: Threaded ( ReplyWithinThread :: Yes )
369
+ } else {
370
+ EnforceThread :: MaybeThreaded
371
+ } ;
372
+ return Some ( Reply { event_id : replied_to, enforce_thread } ) ;
373
+ }
374
+
375
+ let thread_root = self . controller . thread_root ( ) ?;
376
+
377
+ // The latest event id is used for the reply-to fallback, for clients which
378
+ // don't handle threads. It should be correctly set to the latest
379
+ // event in the thread, which the timeline instance might or might
380
+ // not know about; in this case, we do a best effort of filling it, and resort
381
+ // to using the thread root if we don't know about any event.
382
+ //
383
+ // Note: we could trigger a back-pagination if the timeline is empty, and wait
384
+ // for the results, if the timeline is too often empty.
385
+
386
+ let latest_event_id = self
387
+ . controller
388
+ . items ( )
389
+ . await
390
+ . iter ( )
391
+ . rev ( )
392
+ . find_map ( |item| {
393
+ if let TimelineItemKind :: Event ( event) = item. kind ( ) {
394
+ event. event_id ( ) . map ( ToOwned :: to_owned)
395
+ } else {
396
+ None
397
+ }
398
+ } )
399
+ . unwrap_or ( thread_root) ;
400
+
401
+ Some ( Reply {
402
+ event_id : latest_event_id,
403
+ enforce_thread : EnforceThread :: Threaded ( ReplyWithinThread :: No ) ,
404
+ } )
405
+ }
406
+
378
407
/// Edit an event given its [`TimelineEventItemId`] and some new content.
379
408
///
380
409
/// Only supports events for which [`EventTimelineItem::is_editable()`]
0 commit comments