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:
parent
613e6d6503
commit
c980fddfa1
1 changed files with 55 additions and 29 deletions
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue