@@ -24,7 +24,7 @@ import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
24
24
import { EventType , RelationType } from "matrix-js-sdk/src/@types/event" ;
25
25
import { SyncState } from "matrix-js-sdk/src/sync" ;
26
26
import { RoomMember , RoomMemberEvent } from "matrix-js-sdk/src/models/room-member" ;
27
- import { debounce , throttle } from "lodash" ;
27
+ import { debounce , findLastIndex , throttle } from "lodash" ;
28
28
import { logger } from "matrix-js-sdk/src/logger" ;
29
29
import { ClientEvent } from "matrix-js-sdk/src/client" ;
30
30
import { Thread , ThreadEvent } from "matrix-js-sdk/src/models/thread" ;
@@ -73,6 +73,12 @@ const debuglog = (...args: any[]): void => {
73
73
}
74
74
} ;
75
75
76
+ const overlaysBefore = ( overlayEvent : MatrixEvent , mainEvent : MatrixEvent ) : boolean =>
77
+ overlayEvent . localTimestamp < mainEvent . localTimestamp ;
78
+
79
+ const overlaysAfter = ( overlayEvent : MatrixEvent , mainEvent : MatrixEvent ) : boolean =>
80
+ overlayEvent . localTimestamp >= mainEvent . localTimestamp ;
81
+
76
82
interface IProps {
77
83
// The js-sdk EventTimelineSet object for the timeline sequence we are
78
84
// representing. This may or may not have a room, depending on what it's
@@ -83,7 +89,6 @@ interface IProps {
83
89
// added to support virtual rooms
84
90
// events from the overlay timeline set will be added by localTimestamp
85
91
// into the main timeline
86
- // back paging not yet supported
87
92
overlayTimelineSet ?: EventTimelineSet ;
88
93
// filter events from overlay timeline
89
94
overlayTimelineSetFilter ?: ( event : MatrixEvent ) => boolean ;
@@ -506,16 +511,53 @@ class TimelinePanel extends React.Component<IProps, IState> {
506
511
// this particular event should be the first or last to be unpaginated.
507
512
const eventId = scrollToken ;
508
513
509
- const marker = this . state . events . findIndex ( ( ev ) => {
510
- return ev . getId ( ) === eventId ;
511
- } ) ;
514
+ // The event in question could belong to either the main timeline or
515
+ // overlay timeline; let's check both
516
+ const mainEvents = this . timelineWindow ?. getEvents ( ) ?? [ ] ;
517
+ const overlayEvents = this . overlayTimelineWindow ?. getEvents ( ) ?? [ ] ;
518
+
519
+ let marker = mainEvents . findIndex ( ( ev ) => ev . getId ( ) === eventId ) ;
520
+ let overlayMarker : number ;
521
+ if ( marker === - 1 ) {
522
+ // The event must be from the overlay timeline instead
523
+ overlayMarker = overlayEvents . findIndex ( ( ev ) => ev . getId ( ) === eventId ) ;
524
+ marker = backwards
525
+ ? findLastIndex ( mainEvents , ( ev ) => overlaysAfter ( overlayEvents [ overlayMarker ] , ev ) )
526
+ : mainEvents . findIndex ( ( ev ) => overlaysBefore ( overlayEvents [ overlayMarker ] , ev ) ) ;
527
+ } else {
528
+ overlayMarker = backwards
529
+ ? findLastIndex ( overlayEvents , ( ev ) => overlaysBefore ( ev , mainEvents [ marker ] ) )
530
+ : overlayEvents . findIndex ( ( ev ) => overlaysAfter ( ev , mainEvents [ marker ] ) ) ;
531
+ }
532
+
533
+ // The number of events to unpaginate from the main timeline
534
+ let count : number ;
535
+ if ( marker === - 1 ) {
536
+ count = 0 ;
537
+ } else {
538
+ count = backwards ? marker + 1 : mainEvents . length - marker ;
539
+ }
512
540
513
- const count = backwards ? marker + 1 : this . state . events . length - marker ;
541
+ // The number of events to unpaginate from the overlay timeline
542
+ let overlayCount : number ;
543
+ if ( overlayMarker === - 1 ) {
544
+ overlayCount = 0 ;
545
+ } else {
546
+ overlayCount = backwards ? overlayMarker + 1 : overlayEvents . length - overlayMarker ;
547
+ }
514
548
515
549
if ( count > 0 ) {
516
550
debuglog ( "Unpaginating" , count , "in direction" , dir ) ;
517
551
this . timelineWindow ?. unpaginate ( count , backwards ) ;
552
+ }
518
553
554
+ if ( overlayCount > 0 ) {
555
+ debuglog ( "Unpaginating" , count , "from overlay timeline in direction" , dir ) ;
556
+ this . overlayTimelineWindow ?. unpaginate ( overlayCount , backwards ) ;
557
+ }
558
+
559
+ // If either timeline window shrunk
560
+ if ( count > 0 || overlayCount > 0 ) {
519
561
const { events, liveEvents, firstVisibleEventIndex } = this . getEvents ( ) ;
520
562
this . buildLegacyCallEventGroupers ( events ) ;
521
563
this . setState ( {
@@ -572,11 +614,15 @@ class TimelinePanel extends React.Component<IProps, IState> {
572
614
debuglog ( "Initiating paginate; backwards:" + backwards ) ;
573
615
this . setState < null > ( { [ paginatingKey ] : true } ) ;
574
616
575
- return this . onPaginationRequest ( this . timelineWindow , dir , PAGINATE_SIZE ) . then ( ( r ) => {
617
+ return this . onPaginationRequest ( this . timelineWindow , dir , PAGINATE_SIZE ) . then ( async ( r ) => {
576
618
if ( this . unmounted ) {
577
619
return false ;
578
620
}
579
621
622
+ if ( this . overlayTimelineWindow ) {
623
+ await this . extendOverlayWindowToCoverMainWindow ( ) ;
624
+ }
625
+
580
626
debuglog ( "paginate complete backwards:" + backwards + "; success:" + r ) ;
581
627
582
628
const { events, liveEvents, firstVisibleEventIndex } = this . getEvents ( ) ;
@@ -769,8 +815,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
769
815
} ) ;
770
816
} ;
771
817
818
+ private hasTimelineSetFor ( roomId : string ) : boolean {
819
+ return roomId === this . props . timelineSet . room ?. roomId || roomId === this . props . overlayTimelineSet ?. room ?. roomId ;
820
+ }
821
+
772
822
private onRoomTimelineReset = ( room : Room , timelineSet : EventTimelineSet ) : void => {
773
- if ( timelineSet !== this . props . timelineSet ) return ;
823
+ if ( timelineSet !== this . props . timelineSet && timelineSet !== this . props . overlayTimelineSet ) return ;
774
824
775
825
if ( this . canResetTimeline ( ) ) {
776
826
this . loadTimeline ( ) ;
@@ -783,7 +833,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
783
833
if ( this . unmounted ) return ;
784
834
785
835
// ignore events for other rooms
786
- if ( room !== this . props . timelineSet . room ) return ;
836
+ if ( ! this . hasTimelineSetFor ( room . roomId ) ) return ;
787
837
788
838
// we could skip an update if the event isn't in our timeline,
789
839
// but that's probably an early optimisation.
@@ -796,10 +846,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
796
846
}
797
847
798
848
// ignore events for other rooms
799
- const roomId = thread . roomId ;
800
- if ( roomId !== this . props . timelineSet . room ?. roomId ) {
801
- return ;
802
- }
849
+ if ( ! this . hasTimelineSetFor ( thread . roomId ) ) return ;
803
850
804
851
// we could skip an update if the event isn't in our timeline,
805
852
// but that's probably an early optimisation.
@@ -818,9 +865,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
818
865
819
866
// ignore events for other rooms
820
867
const roomId = ev . getRoomId ( ) ;
821
- if ( roomId !== this . props . timelineSet . room ?. roomId ) {
822
- return ;
823
- }
868
+ if ( roomId === undefined || ! this . hasTimelineSetFor ( roomId ) ) return ;
824
869
825
870
// we could skip an update if the event isn't in our timeline,
826
871
// but that's probably an early optimisation.
@@ -834,7 +879,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
834
879
if ( this . unmounted ) return ;
835
880
836
881
// ignore events for other rooms
837
- if ( member . roomId !== this . props . timelineSet . room ?. roomId ) return ;
882
+ if ( ! this . hasTimelineSetFor ( member . roomId ) ) return ;
838
883
839
884
// ignore events for other users
840
885
if ( member . userId != MatrixClientPeg . get ( ) . credentials ?. userId ) return ;
@@ -857,7 +902,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
857
902
if ( this . unmounted ) return ;
858
903
859
904
// ignore events for other rooms
860
- if ( replacedEvent . getRoomId ( ) !== this . props . timelineSet . room ?. roomId ) return ;
905
+ const roomId = replacedEvent . getRoomId ( ) ;
906
+ if ( roomId === undefined || ! this . hasTimelineSetFor ( roomId ) ) return ;
861
907
862
908
// we could skip an update if the event isn't in our timeline,
863
909
// but that's probably an early optimisation.
@@ -877,7 +923,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
877
923
if ( this . unmounted ) return ;
878
924
879
925
// ignore events for other rooms
880
- if ( room !== this . props . timelineSet . room ) return ;
926
+ if ( ! this . hasTimelineSetFor ( room . roomId ) ) return ;
881
927
882
928
this . reloadEvents ( ) ;
883
929
} ;
@@ -905,7 +951,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
905
951
// Can be null for the notification timeline, etc.
906
952
if ( ! this . props . timelineSet . room ) return ;
907
953
908
- if ( ev . getRoomId ( ) !== this . props . timelineSet . room . roomId ) return ;
954
+ const roomId = ev . getRoomId ( ) ;
955
+ if ( roomId === undefined || ! this . hasTimelineSetFor ( roomId ) ) return ;
909
956
910
957
if ( ! this . state . events . includes ( ev ) ) return ;
911
958
@@ -1380,6 +1427,48 @@ class TimelinePanel extends React.Component<IProps, IState> {
1380
1427
} ) ;
1381
1428
}
1382
1429
1430
+ private async extendOverlayWindowToCoverMainWindow ( ) : Promise < void > {
1431
+ const mainWindow = this . timelineWindow ! ;
1432
+ const overlayWindow = this . overlayTimelineWindow ! ;
1433
+ const mainEvents = mainWindow . getEvents ( ) ;
1434
+
1435
+ if ( mainEvents . length > 0 ) {
1436
+ let paginationRequests : Promise < unknown > [ ] ;
1437
+
1438
+ // Keep paginating until the main window is covered
1439
+ do {
1440
+ paginationRequests = [ ] ;
1441
+ const overlayEvents = overlayWindow . getEvents ( ) ;
1442
+
1443
+ if (
1444
+ overlayWindow . canPaginate ( EventTimeline . BACKWARDS ) &&
1445
+ ( overlayEvents . length === 0 ||
1446
+ overlaysAfter ( overlayEvents [ 0 ] , mainEvents [ 0 ] ) ||
1447
+ ! mainWindow . canPaginate ( EventTimeline . BACKWARDS ) )
1448
+ ) {
1449
+ // Paginating backwards could reveal more events to be overlaid in the main window
1450
+ paginationRequests . push (
1451
+ this . onPaginationRequest ( overlayWindow , EventTimeline . BACKWARDS , PAGINATE_SIZE ) ,
1452
+ ) ;
1453
+ }
1454
+
1455
+ if (
1456
+ overlayWindow . canPaginate ( EventTimeline . FORWARDS ) &&
1457
+ ( overlayEvents . length === 0 ||
1458
+ overlaysBefore ( overlayEvents . at ( - 1 ) ! , mainEvents . at ( - 1 ) ! ) ||
1459
+ ! mainWindow . canPaginate ( EventTimeline . FORWARDS ) )
1460
+ ) {
1461
+ // Paginating forwards could reveal more events to be overlaid in the main window
1462
+ paginationRequests . push (
1463
+ this . onPaginationRequest ( overlayWindow , EventTimeline . FORWARDS , PAGINATE_SIZE ) ,
1464
+ ) ;
1465
+ }
1466
+
1467
+ await Promise . all ( paginationRequests ) ;
1468
+ } while ( paginationRequests . length > 0 ) ;
1469
+ }
1470
+ }
1471
+
1383
1472
/**
1384
1473
* (re)-load the event timeline, and initialise the scroll state, centered
1385
1474
* around the given event.
@@ -1417,8 +1506,14 @@ class TimelinePanel extends React.Component<IProps, IState> {
1417
1506
1418
1507
this . setState (
1419
1508
{
1420
- canBackPaginate : ! ! this . timelineWindow ?. canPaginate ( EventTimeline . BACKWARDS ) ,
1421
- canForwardPaginate : ! ! this . timelineWindow ?. canPaginate ( EventTimeline . FORWARDS ) ,
1509
+ canBackPaginate :
1510
+ ( this . timelineWindow ?. canPaginate ( EventTimeline . BACKWARDS ) ||
1511
+ this . overlayTimelineWindow ?. canPaginate ( EventTimeline . BACKWARDS ) ) ??
1512
+ false ,
1513
+ canForwardPaginate :
1514
+ ( this . timelineWindow ?. canPaginate ( EventTimeline . FORWARDS ) ||
1515
+ this . overlayTimelineWindow ?. canPaginate ( EventTimeline . FORWARDS ) ) ??
1516
+ false ,
1422
1517
timelineLoading : false ,
1423
1518
} ,
1424
1519
( ) => {
@@ -1494,21 +1589,21 @@ class TimelinePanel extends React.Component<IProps, IState> {
1494
1589
// This is a hot-path optimization by skipping a promise tick
1495
1590
// by repeating a no-op sync branch in
1496
1591
// TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline
1497
- if ( this . props . timelineSet . getTimelineForEvent ( eventId ) ) {
1592
+ if ( this . props . timelineSet . getTimelineForEvent ( eventId ) && ! this . overlayTimelineWindow ) {
1498
1593
// if we've got an eventId, and the timeline exists, we can skip
1499
1594
// the promise tick.
1500
1595
this . timelineWindow . load ( eventId , INITIAL_SIZE ) ;
1501
- this . overlayTimelineWindow ?. load ( undefined , INITIAL_SIZE ) ;
1502
1596
// in this branch this method will happen in sync time
1503
1597
onLoaded ( ) ;
1504
1598
return ;
1505
1599
}
1506
1600
1507
1601
const prom = this . timelineWindow . load ( eventId , INITIAL_SIZE ) . then ( async ( ) : Promise < void > => {
1508
1602
if ( this . overlayTimelineWindow ) {
1509
- // @ TODO(kerrya) use timestampToEvent to load the overlay timeline
1603
+ // TODO: use timestampToEvent to load the overlay timeline
1510
1604
// with more correct position when main TL eventId is truthy
1511
1605
await this . overlayTimelineWindow . load ( undefined , INITIAL_SIZE ) ;
1606
+ await this . extendOverlayWindowToCoverMainWindow ( ) ;
1512
1607
}
1513
1608
} ) ;
1514
1609
this . buildLegacyCallEventGroupers ( ) ;
@@ -1541,23 +1636,31 @@ class TimelinePanel extends React.Component<IProps, IState> {
1541
1636
this . reloadEvents ( ) ;
1542
1637
}
1543
1638
1544
- // get the list of events from the timeline window and the pending event list
1639
+ // get the list of events from the timeline windows and the pending event list
1545
1640
private getEvents ( ) : Pick < IState , "events" | "liveEvents" | "firstVisibleEventIndex" > {
1546
- const mainEvents : MatrixEvent [ ] = this . timelineWindow ?. getEvents ( ) || [ ] ;
1547
- const eventFilter = this . props . overlayTimelineSetFilter || Boolean ;
1548
- const overlayEvents = this . overlayTimelineWindow ?. getEvents ( ) . filter ( eventFilter ) || [ ] ;
1641
+ const mainEvents = this . timelineWindow ?. getEvents ( ) ?? [ ] ;
1642
+ let overlayEvents = this . overlayTimelineWindow ?. getEvents ( ) ?? [ ] ;
1643
+ if ( this . props . overlayTimelineSetFilter !== undefined ) {
1644
+ overlayEvents = overlayEvents . filter ( this . props . overlayTimelineSetFilter ) ;
1645
+ }
1549
1646
1550
1647
// maintain the main timeline event order as returned from the HS
1551
1648
// merge overlay events at approximately the right position based on local timestamp
1552
1649
const events = overlayEvents . reduce (
1553
1650
( acc : MatrixEvent [ ] , overlayEvent : MatrixEvent ) => {
1554
1651
// find the first main tl event with a later timestamp
1555
- const index = acc . findIndex ( ( event ) => event . localTimestamp > overlayEvent . localTimestamp ) ;
1652
+ const index = acc . findIndex ( ( event ) => overlaysBefore ( overlayEvent , event ) ) ;
1556
1653
// insert overlay event into timeline at approximately the right place
1557
- if ( index > - 1 ) {
1558
- acc . splice ( index , 0 , overlayEvent ) ;
1654
+ if ( index === - 1 ) {
1655
+ if ( ! this . timelineWindow ?. canPaginate ( EventTimeline . FORWARDS ) ) {
1656
+ acc . push ( overlayEvent ) ;
1657
+ }
1658
+ } else if ( index === 0 ) {
1659
+ if ( ! this . timelineWindow ?. canPaginate ( EventTimeline . BACKWARDS ) ) {
1660
+ acc . unshift ( overlayEvent ) ;
1661
+ }
1559
1662
} else {
1560
- acc . push ( overlayEvent ) ;
1663
+ acc . splice ( index , 0 , overlayEvent ) ;
1561
1664
}
1562
1665
return acc ;
1563
1666
} ,
@@ -1574,7 +1677,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
1574
1677
client . decryptEventIfNeeded ( event ) ;
1575
1678
} ) ;
1576
1679
1577
- const firstVisibleEventIndex = this . checkForPreJoinUISI ( mainEvents ) ;
1680
+ const firstVisibleEventIndex = this . checkForPreJoinUISI ( events ) ;
1578
1681
1579
1682
// Hold onto the live events separately. The read receipt and read marker
1580
1683
// should use this list, so that they don't advance into pending events.
0 commit comments