Fix unread bug (#1454)

* remove unread info on mark as read

* fix roomId is not provided to markAsRead

* fix auto mark as read
This commit is contained in:
Ajay Bura 2023-10-19 17:40:01 +11:00 committed by GitHub
parent 613e6d6503
commit c980fddfa1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -132,6 +132,7 @@ import { usePowerLevelsAPI } from '../../hooks/usePowerLevels';
import { MessageEvent } from '../../../types/matrix/room'; import { MessageEvent } from '../../../types/matrix/room';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import { useKeyDown } from '../../hooks/useKeyDown'; import { useKeyDown } from '../../hooks/useKeyDown';
import cons from '../../../client/state/cons';
const TimelineFloat = as<'div', css.TimelineFloatVariants>( const TimelineFloat = as<'div', css.TimelineFloatVariants>(
({ position, className, ...props }, ref) => ( ({ position, className, ...props }, ref) => (
@ -479,7 +480,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
} }
const atBottomAnchorRef = useRef<HTMLElement>(null); const atBottomAnchorRef = useRef<HTMLElement>(null);
const [atBottom, setAtBottom] = useState<boolean>(); const [atBottom, setAtBottom] = useState<boolean>(true);
const atBottomRef = useRef(atBottom); const atBottomRef = useRef(atBottom);
atBottomRef.current = atBottom; atBottomRef.current = atBottom;
@ -538,6 +539,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
typeof timeline.linkedTimelines[0]?.getPaginationToken(Direction.Backward) === 'string'; typeof timeline.linkedTimelines[0]?.getPaginationToken(Direction.Backward) === 'string';
const rangeAtStart = timeline.range.start === 0; const rangeAtStart = timeline.range.start === 0;
const rangeAtEnd = timeline.range.end === eventsLength; const rangeAtEnd = timeline.range.end === eventsLength;
const atLiveEndRef = useRef(liveTimelineLinked && rangeAtEnd);
atLiveEndRef.current = liveTimelineLinked && rangeAtEnd;
const handleTimelinePagination = useTimelinePagination( const handleTimelinePagination = useTimelinePagination(
mx, mx,
@ -599,8 +602,12 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
room, room,
useCallback( useCallback(
(mEvt: MatrixEvent) => { (mEvt: MatrixEvent) => {
// if user is at bottom of timeline
// keep paginating timeline and conditionally mark as read
// otherwise we update timeline without paginating
// so timeline can be updated with evt like: edits, reactions etc
if (atBottomRef.current && document.hasFocus()) { if (atBottomRef.current && document.hasFocus()) {
if (!unreadInfo && mEvt.getSender() !== mx.getUserId()) { if (!unreadInfo) {
markAsRead(mEvt.getRoomId()); markAsRead(mEvt.getRoomId());
} }
@ -620,7 +627,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
setUnreadInfo(getRoomUnreadInfo(room)); setUnreadInfo(getRoomUnreadInfo(room));
} }
}, },
[mx, room, unreadInfo] [room, unreadInfo]
) )
); );
@ -635,8 +642,14 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
// Stay at bottom when room editor resize // Stay at bottom when room editor resize
useResizeObserver( useResizeObserver(
useCallback( useMemo(() => {
(entries) => { let mounted = false;
return (entries) => {
if (!mounted) {
// skip initial mounting call
mounted = true;
return;
}
if (!roomInputRef.current) return; if (!roomInputRef.current) return;
const editorBaseEntry = getResizeObserverEntry(roomInputRef.current, entries); const editorBaseEntry = getResizeObserverEntry(roomInputRef.current, entries);
const scrollElement = getScrollElement(); const scrollElement = getScrollElement();
@ -645,12 +658,23 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
if (atBottomRef.current) { if (atBottomRef.current) {
scrollToBottom(scrollElement); scrollToBottom(scrollElement);
} }
}, };
[getScrollElement, roomInputRef] }, [getScrollElement, roomInputRef]),
),
useCallback(() => roomInputRef.current, [roomInputRef]) useCallback(() => roomInputRef.current, [roomInputRef])
); );
const tryAutoMarkAsRead = useCallback(() => {
if (!unreadInfo) {
markAsRead(room.roomId);
return;
}
const evtTimeline = getEventTimeline(room, unreadInfo.readUptoEventId);
const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward);
if (latestTimeline === room.getLiveTimeline()) {
markAsRead(room.roomId);
}
}, [room, unreadInfo]);
const debounceSetAtBottom = useDebounce( const debounceSetAtBottom = useDebounce(
useCallback((entry: IntersectionObserverEntry) => { useCallback((entry: IntersectionObserverEntry) => {
if (!entry.isIntersecting) setAtBottom(false); if (!entry.isIntersecting) setAtBottom(false);
@ -664,9 +688,12 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
if (!target) return; if (!target) return;
const targetEntry = getIntersectionObserverEntry(target, entries); const targetEntry = getIntersectionObserverEntry(target, entries);
if (targetEntry) debounceSetAtBottom(targetEntry); if (targetEntry) debounceSetAtBottom(targetEntry);
if (targetEntry?.isIntersecting) setAtBottom(true); if (targetEntry?.isIntersecting && atLiveEndRef.current) {
setAtBottom(true);
tryAutoMarkAsRead();
}
}, },
[debounceSetAtBottom] [debounceSetAtBottom, tryAutoMarkAsRead]
), ),
useCallback( useCallback(
() => ({ () => ({
@ -711,10 +738,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
// Scroll to bottom on initial timeline load // Scroll to bottom on initial timeline load
useLayoutEffect(() => { useLayoutEffect(() => {
const scrollEl = scrollRef.current; const scrollEl = scrollRef.current;
if (scrollEl) scrollToBottom(scrollEl); if (scrollEl) {
scrollToBottom(scrollEl);
}
}, []); }, []);
// Scroll to last read message if it is linked to live timeline // if live timeline is linked and unreadInfo change
// Scroll to last read message
useLayoutEffect(() => { useLayoutEffect(() => {
const { readUptoEventId, inLiveTimeline, scrollTo } = unreadInfo ?? {}; const { readUptoEventId, inLiveTimeline, scrollTo } = unreadInfo ?? {};
if (readUptoEventId && inLiveTimeline && scrollTo) { if (readUptoEventId && inLiveTimeline && scrollTo) {
@ -722,12 +752,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const evtTimeline = getEventTimeline(room, readUptoEventId); const evtTimeline = getEventTimeline(room, readUptoEventId);
const absoluteIndex = const absoluteIndex =
evtTimeline && getEventIdAbsoluteIndex(linkedTimelines, evtTimeline, readUptoEventId); evtTimeline && getEventIdAbsoluteIndex(linkedTimelines, evtTimeline, readUptoEventId);
if (absoluteIndex) if (absoluteIndex) {
scrollToItem(absoluteIndex, { scrollToItem(absoluteIndex, {
behavior: 'instant', behavior: 'instant',
align: 'start', align: 'start',
stopInView: true, stopInView: true,
}); });
}
} }
}, [room, unreadInfo, scrollToItem]); }, [room, unreadInfo, scrollToItem]);
@ -755,21 +786,17 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
} }
}, [scrollToBottomCount]); }, [scrollToBottomCount]);
// send readReceipts when reach bottom // Remove unreadInfo on mark as read
useEffect(() => { useEffect(() => {
if (liveTimelineLinked && rangeAtEnd && atBottom && document.hasFocus()) { const handleFullRead = (rId: string) => {
if (!unreadInfo) { if (rId !== room.roomId) return;
markAsRead(room.roomId); setUnreadInfo(undefined);
return; };
} initMatrix.notifications?.on(cons.events.notifications.FULL_READ, handleFullRead);
const evtTimeline = getEventTimeline(room, unreadInfo.readUptoEventId); return () => {
const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward); initMatrix.notifications?.removeListener(cons.events.notifications.FULL_READ, handleFullRead);
if (latestTimeline === room.getLiveTimeline()) { };
markAsRead(); }, [room]);
setUnreadInfo(undefined);
}
}
}, [room, unreadInfo, liveTimelineLinked, rangeAtEnd, atBottom]);
// scroll out of view msg editor in view. // scroll out of view msg editor in view.
useEffect(() => { useEffect(() => {
@ -802,7 +829,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(room.roomId); markAsRead(room.roomId);
setUnreadInfo(undefined);
}; };
const handleOpenReply: MouseEventHandler<HTMLButtonElement> = useCallback( const handleOpenReply: MouseEventHandler<HTMLButtonElement> = useCallback(
@ -1722,7 +1748,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
<span ref={atBottomAnchorRef} /> <span ref={atBottomAnchorRef} />
</Box> </Box>
</Scroll> </Scroll>
{(atBottom === false || !liveTimelineLinked || !rangeAtEnd) && ( {!atBottom && (
<TimelineFloat position="Bottom"> <TimelineFloat position="Bottom">
<Chip <Chip
variant="SurfaceVariant" variant="SurfaceVariant"