Skip to content

Commit 70a14ba

Browse files
authored
Merge pull request #666 from GetStream/fix-jump-to-message-sometimes-not-working
fix: jump to message sometimes not working
2 parents 2b1e00d + 4b7b9f2 commit 70a14ba

File tree

1 file changed

+80
-53
lines changed

1 file changed

+80
-53
lines changed

projects/stream-chat-angular/src/lib/message-list/message-list.component.ts

Lines changed: 80 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export class MessageListComponent
148148
private checkIfUnreadNotificationIsVisibleTimeout?: ReturnType<
149149
typeof setTimeout
150150
>;
151+
private jumpToMessageTimeouts: ReturnType<typeof setTimeout>[] = [];
151152
private jumpToLatestButtonVisibilityTimeout?: ReturnType<typeof setTimeout>;
152153
private forceRepaintSubject = new Subject<void>();
153154
private messageIdToAnchorTo?: string;
@@ -202,6 +203,11 @@ export class MessageListComponent
202203
if (this.checkIfUnreadNotificationIsVisibleTimeout) {
203204
clearTimeout(this.checkIfUnreadNotificationIsVisibleTimeout);
204205
}
206+
this.jumpToMessageTimeouts.forEach((timeout) =>
207+
clearTimeout(timeout)
208+
);
209+
this.jumpToMessageTimeouts = [];
210+
this.highlightedMessageId = undefined;
205211
this.isUnreadNotificationVisible = false;
206212
this.parsedDates = new Map();
207213
this.resetScrollState();
@@ -445,6 +451,7 @@ export class MessageListComponent
445451
if (this.jumpToLatestButtonVisibilityTimeout) {
446452
clearTimeout(this.jumpToLatestButtonVisibilityTimeout);
447453
}
454+
this.jumpToMessageTimeouts.forEach((timeout) => clearTimeout(timeout));
448455
this.disposeVirtualizedList();
449456
}
450457

@@ -501,22 +508,10 @@ export class MessageListComponent
501508
this.isJumpToLatestButtonVisible = true;
502509
}
503510
}
511+
504512
this.scroll$.next();
505-
let scrollPosition = this.getScrollPosition();
506513

507-
const isUserScrolled =
508-
(this.direction === 'bottom-to-top'
509-
? scrollPosition !== 'bottom'
510-
: scrollPosition !== 'top') || !this.isLatestMessageInList;
511-
if (this.isUserScrolled !== isUserScrolled) {
512-
this.ngZone.run(() => {
513-
this.isUserScrolled = isUserScrolled;
514-
if (!this.isUserScrolled) {
515-
this.newMessageCountWhileBeingScrolled = 0;
516-
}
517-
this.cdRef.detectChanges();
518-
});
519-
}
514+
this.checkIfUserScrolled();
520515

521516
if (this.hideJumpToLatestButtonDuringScroll) {
522517
if (this.isJumpToLatestButtonVisible) {
@@ -534,35 +529,6 @@ export class MessageListComponent
534529
}
535530
}, 100);
536531
}
537-
538-
const prevScrollPosition = this.scrollPosition$.getValue();
539-
540-
if (this.direction === 'top-to-bottom') {
541-
if (scrollPosition === 'top') {
542-
scrollPosition = 'bottom';
543-
} else if (scrollPosition === 'bottom') {
544-
scrollPosition = 'top';
545-
}
546-
}
547-
548-
if (prevScrollPosition !== scrollPosition && !this.isJumpingToMessage) {
549-
if (scrollPosition === 'top' || scrollPosition === 'bottom') {
550-
this.virtualizedList?.virtualizedItems$
551-
.pipe(take(1))
552-
.subscribe((items) => {
553-
this.messageIdToAnchorTo =
554-
scrollPosition === 'top'
555-
? items[0]?.id
556-
: items[items.length - 1]?.id;
557-
this.anchorMessageTopOffset = document
558-
.getElementById(this.messageIdToAnchorTo)
559-
?.getBoundingClientRect()?.top;
560-
});
561-
}
562-
this.ngZone.run(() => {
563-
this.scrollPosition$.next(scrollPosition);
564-
});
565-
}
566532
}
567533

568534
jumpToFirstUnreadMessage() {
@@ -611,6 +577,53 @@ export class MessageListComponent
611577
: this.emptyThreadMessageListTemplate;
612578
}
613579

580+
private checkIfUserScrolled() {
581+
let scrollPosition = this.getScrollPosition();
582+
583+
const isUserScrolled =
584+
(this.direction === 'bottom-to-top'
585+
? scrollPosition !== 'bottom'
586+
: scrollPosition !== 'top') || !this.isLatestMessageInList;
587+
if (this.isUserScrolled !== isUserScrolled) {
588+
this.ngZone.run(() => {
589+
this.isUserScrolled = isUserScrolled;
590+
if (!this.isUserScrolled) {
591+
this.newMessageCountWhileBeingScrolled = 0;
592+
}
593+
this.cdRef.detectChanges();
594+
});
595+
}
596+
597+
const prevScrollPosition = this.scrollPosition$.getValue();
598+
599+
if (this.direction === 'top-to-bottom') {
600+
if (scrollPosition === 'top') {
601+
scrollPosition = 'bottom';
602+
} else if (scrollPosition === 'bottom') {
603+
scrollPosition = 'top';
604+
}
605+
}
606+
607+
if (prevScrollPosition !== scrollPosition && !this.isJumpingToMessage) {
608+
if (scrollPosition === 'top' || scrollPosition === 'bottom') {
609+
this.virtualizedList?.virtualizedItems$
610+
.pipe(take(1))
611+
.subscribe((items) => {
612+
this.messageIdToAnchorTo =
613+
scrollPosition === 'top'
614+
? items[0]?.id
615+
: items[items.length - 1]?.id;
616+
this.anchorMessageTopOffset = document
617+
.getElementById(this.messageIdToAnchorTo)
618+
?.getBoundingClientRect()?.top;
619+
});
620+
}
621+
this.ngZone.run(() => {
622+
this.scrollPosition$.next(scrollPosition);
623+
});
624+
}
625+
}
626+
614627
private preserveScrollbarPosition() {
615628
if (!this.messageIdToAnchorTo) {
616629
return;
@@ -792,32 +805,46 @@ export class MessageListComponent
792805
withRetry: boolean = true
793806
) {
794807
const element = document.getElementById(options.messageId);
808+
this.jumpToMessageTimeouts.forEach((t) => clearTimeout(t));
809+
this.jumpToMessageTimeouts = [];
795810
if (!element && withRetry) {
796811
// If the message was newly inserted into activeChannelMessages$, the message will be rendered after the current change detection cycle -> wait for this cycle to complete
797-
setTimeout(() => this.scrollMessageIntoView(options, false));
812+
this.jumpToMessageTimeouts.push(
813+
setTimeout(() => this.scrollMessageIntoView(options, false))
814+
);
798815
} else if (element) {
799816
const blockMapping: { [key: string]: ScrollLogicalPosition } = {
800817
top: 'start',
801818
bottom: 'end',
802819
middle: 'center',
803820
};
821+
// We can't know when smooth scrolling ends, so we set the behavior to instant https://github.com/w3c/csswg-drafts/issues/3744
804822
element.scrollIntoView({
823+
behavior: 'instant' as ScrollBehavior,
805824
block: blockMapping[options.position],
806825
});
807826
if (options.position !== 'middle') {
808827
options.position === 'bottom'
809828
? this.scrollToBottom()
810829
: this.scrollToTop();
811830
}
812-
setTimeout(() => {
813-
this.isJumpingToMessage = false;
814-
}, 0);
815-
setTimeout(() => {
816-
this.highlightedMessageId = undefined;
817-
this.firstUnreadMessageId = undefined;
818-
this.isJumpingToLatestUnreadMessage = false;
819-
this.cdRef.detectChanges();
820-
}, 1000);
831+
this.jumpToMessageTimeouts.push(
832+
setTimeout(() => {
833+
this.isJumpingToMessage = false;
834+
if (!this.isUserScrolled) {
835+
this.checkIfUserScrolled();
836+
}
837+
}, 200)
838+
);
839+
this.jumpToMessageTimeouts.push(
840+
setTimeout(() => {
841+
this.highlightedMessageId = undefined;
842+
this.firstUnreadMessageId = undefined;
843+
this.isJumpingToLatestUnreadMessage = false;
844+
this.jumpToMessageTimeouts = [];
845+
this.cdRef.detectChanges();
846+
}, 1000)
847+
);
821848
}
822849
}
823850

0 commit comments

Comments
 (0)