mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 06:20:28 +03:00 
			
		
		
		
	scroll to bottom in unfocused window but stop sending read receipt (#2214)
* scroll to bottom in unfocused window but stop sending read receipt * send read-receipt when new message are in view after regaining focus
This commit is contained in:
		
							parent
							
								
									59e8d66255
								
							
						
					
					
						commit
						b63868bbb5
					
				
					 2 changed files with 82 additions and 37 deletions
				
			
		| 
						 | 
				
			
			@ -586,15 +586,19 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
 | 
			
		|||
        // so timeline can be updated with evt like: edits, reactions etc
 | 
			
		||||
        if (atBottomRef.current) {
 | 
			
		||||
          if (document.hasFocus() && (!unreadInfo || mEvt.getSender() === mx.getUserId())) {
 | 
			
		||||
            // Check if the document is in focus (user is actively viewing the app),
 | 
			
		||||
            // and either there are no unread messages or the latest message is from the current user.
 | 
			
		||||
            // If either condition is met, trigger the markAsRead function to send a read receipt.
 | 
			
		||||
            requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!));
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (document.hasFocus()) {
 | 
			
		||||
            scrollToBottomRef.current.count += 1;
 | 
			
		||||
            scrollToBottomRef.current.smooth = true;
 | 
			
		||||
          } else if (!unreadInfo) {
 | 
			
		||||
          if (!document.hasFocus() && !unreadInfo) {
 | 
			
		||||
            setUnreadInfo(getRoomUnreadInfo(room));
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          scrollToBottomRef.current.count += 1;
 | 
			
		||||
          scrollToBottomRef.current.smooth = true;
 | 
			
		||||
 | 
			
		||||
          setTimeline((ct) => ({
 | 
			
		||||
            ...ct,
 | 
			
		||||
            range: {
 | 
			
		||||
| 
						 | 
				
			
			@ -613,6 +617,36 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
 | 
			
		|||
    )
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleOpenEvent = useCallback(
 | 
			
		||||
    async (
 | 
			
		||||
      evtId: string,
 | 
			
		||||
      highlight = true,
 | 
			
		||||
      onScroll: ((scrolled: boolean) => void) | undefined = undefined
 | 
			
		||||
    ) => {
 | 
			
		||||
      const evtTimeline = getEventTimeline(room, evtId);
 | 
			
		||||
      const absoluteIndex =
 | 
			
		||||
        evtTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, evtTimeline, evtId);
 | 
			
		||||
 | 
			
		||||
      if (typeof absoluteIndex === 'number') {
 | 
			
		||||
        const scrolled = scrollToItem(absoluteIndex, {
 | 
			
		||||
          behavior: 'smooth',
 | 
			
		||||
          align: 'center',
 | 
			
		||||
          stopInView: true,
 | 
			
		||||
        });
 | 
			
		||||
        if (onScroll) onScroll(scrolled);
 | 
			
		||||
        setFocusItem({
 | 
			
		||||
          index: absoluteIndex,
 | 
			
		||||
          scrollTo: false,
 | 
			
		||||
          highlight,
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        setTimeline(getEmptyTimeline());
 | 
			
		||||
        loadEventTimeline(evtId);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    [room, timeline, scrollToItem, loadEventTimeline]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useLiveTimelineRefresh(
 | 
			
		||||
    room,
 | 
			
		||||
    useCallback(() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -646,16 +680,17 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
 | 
			
		|||
  );
 | 
			
		||||
 | 
			
		||||
  const tryAutoMarkAsRead = useCallback(() => {
 | 
			
		||||
    if (!unreadInfo) {
 | 
			
		||||
    const readUptoEventId = readUptoEventIdRef.current;
 | 
			
		||||
    if (!readUptoEventId) {
 | 
			
		||||
      requestAnimationFrame(() => markAsRead(mx, room.roomId));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const evtTimeline = getEventTimeline(room, unreadInfo.readUptoEventId);
 | 
			
		||||
    const evtTimeline = getEventTimeline(room, readUptoEventId);
 | 
			
		||||
    const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward);
 | 
			
		||||
    if (latestTimeline === room.getLiveTimeline()) {
 | 
			
		||||
      requestAnimationFrame(() => markAsRead(mx, room.roomId));
 | 
			
		||||
    }
 | 
			
		||||
  }, [mx, room, unreadInfo]);
 | 
			
		||||
  }, [mx, room]);
 | 
			
		||||
 | 
			
		||||
  const debounceSetAtBottom = useDebounce(
 | 
			
		||||
    useCallback((entry: IntersectionObserverEntry) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -672,7 +707,9 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
 | 
			
		|||
        if (targetEntry) debounceSetAtBottom(targetEntry);
 | 
			
		||||
        if (targetEntry?.isIntersecting && atLiveEndRef.current) {
 | 
			
		||||
          setAtBottom(true);
 | 
			
		||||
          tryAutoMarkAsRead();
 | 
			
		||||
          if (document.hasFocus()) {
 | 
			
		||||
            tryAutoMarkAsRead();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      [debounceSetAtBottom, tryAutoMarkAsRead]
 | 
			
		||||
| 
						 | 
				
			
			@ -691,10 +728,20 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
 | 
			
		|||
    useCallback(
 | 
			
		||||
      (inFocus) => {
 | 
			
		||||
        if (inFocus && atBottomRef.current) {
 | 
			
		||||
          if (unreadInfo?.inLiveTimeline) {
 | 
			
		||||
            handleOpenEvent(unreadInfo.readUptoEventId, false, (scrolled) => {
 | 
			
		||||
              // the unread event is already in view
 | 
			
		||||
              // so, try mark as read;
 | 
			
		||||
              if (!scrolled) {
 | 
			
		||||
                tryAutoMarkAsRead();
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          tryAutoMarkAsRead();
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      [tryAutoMarkAsRead]
 | 
			
		||||
      [tryAutoMarkAsRead, unreadInfo, handleOpenEvent]
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -832,27 +879,9 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
 | 
			
		|||
    async (evt) => {
 | 
			
		||||
      const targetId = evt.currentTarget.getAttribute('data-event-id');
 | 
			
		||||
      if (!targetId) return;
 | 
			
		||||
      const replyTimeline = getEventTimeline(room, targetId);
 | 
			
		||||
      const absoluteIndex =
 | 
			
		||||
        replyTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, replyTimeline, targetId);
 | 
			
		||||
 | 
			
		||||
      if (typeof absoluteIndex === 'number') {
 | 
			
		||||
        scrollToItem(absoluteIndex, {
 | 
			
		||||
          behavior: 'smooth',
 | 
			
		||||
          align: 'center',
 | 
			
		||||
          stopInView: true,
 | 
			
		||||
        });
 | 
			
		||||
        setFocusItem({
 | 
			
		||||
          index: absoluteIndex,
 | 
			
		||||
          scrollTo: false,
 | 
			
		||||
          highlight: true,
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        setTimeline(getEmptyTimeline());
 | 
			
		||||
        loadEventTimeline(targetId);
 | 
			
		||||
      }
 | 
			
		||||
      handleOpenEvent(targetId);
 | 
			
		||||
    },
 | 
			
		||||
    [room, timeline, scrollToItem, loadEventTimeline]
 | 
			
		||||
    [handleOpenEvent]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleUserClick: MouseEventHandler<HTMLButtonElement> = useCallback(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,8 +26,23 @@ export type ScrollToOptions = {
 | 
			
		|||
  stopInView?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ScrollToElement = (element: HTMLElement, opts?: ScrollToOptions) => void;
 | 
			
		||||
export type ScrollToItem = (index: number, opts?: ScrollToOptions) => void;
 | 
			
		||||
/**
 | 
			
		||||
 * Scrolls the page to a specified element in the DOM.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {HTMLElement} element - The DOM element to scroll to.
 | 
			
		||||
 * @param {ScrollToOptions} [opts] - Optional configuration for the scroll behavior (e.g., smooth scrolling, alignment).
 | 
			
		||||
 * @returns {boolean} - Returns `true` if the scroll was successful, otherwise returns `false`.
 | 
			
		||||
 */
 | 
			
		||||
export type ScrollToElement = (element: HTMLElement, opts?: ScrollToOptions) => boolean;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Scrolls the page to an item at the specified index within a scrollable container.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {number} index - The index of the item to scroll to.
 | 
			
		||||
 * @param {ScrollToOptions} [opts] - Optional configuration for the scroll behavior (e.g., smooth scrolling, alignment).
 | 
			
		||||
 * @returns {boolean} - Returns `true` if the scroll was successful, otherwise returns `false`.
 | 
			
		||||
 */
 | 
			
		||||
export type ScrollToItem = (index: number, opts?: ScrollToOptions) => boolean;
 | 
			
		||||
 | 
			
		||||
type HandleObserveAnchor = (element: HTMLElement | null) => void;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -186,10 +201,10 @@ export const useVirtualPaginator = <TScrollElement extends HTMLElement>(
 | 
			
		|||
  const scrollToElement = useCallback<ScrollToElement>(
 | 
			
		||||
    (element, opts) => {
 | 
			
		||||
      const scrollElement = getScrollElement();
 | 
			
		||||
      if (!scrollElement) return;
 | 
			
		||||
      if (!scrollElement) return false;
 | 
			
		||||
 | 
			
		||||
      if (opts?.stopInView && isInScrollView(scrollElement, element)) {
 | 
			
		||||
        return;
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      let scrollTo = element.offsetTop;
 | 
			
		||||
      if (opts?.align === 'center' && canFitInScrollView(scrollElement, element)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -207,6 +222,7 @@ export const useVirtualPaginator = <TScrollElement extends HTMLElement>(
 | 
			
		|||
        top: scrollTo - (opts?.offset ?? 0),
 | 
			
		||||
        behavior: opts?.behavior,
 | 
			
		||||
      });
 | 
			
		||||
      return true;
 | 
			
		||||
    },
 | 
			
		||||
    [getScrollElement]
 | 
			
		||||
  );
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +231,7 @@ export const useVirtualPaginator = <TScrollElement extends HTMLElement>(
 | 
			
		|||
    (index, opts) => {
 | 
			
		||||
      const { range: currentRange, limit: currentLimit, count: currentCount } = propRef.current;
 | 
			
		||||
 | 
			
		||||
      if (index < 0 || index >= currentCount) return;
 | 
			
		||||
      if (index < 0 || index >= currentCount) return false;
 | 
			
		||||
      // index is not in range change range
 | 
			
		||||
      // and trigger scrollToItem in layoutEffect hook
 | 
			
		||||
      if (index < currentRange.start || index >= currentRange.end) {
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +243,7 @@ export const useVirtualPaginator = <TScrollElement extends HTMLElement>(
 | 
			
		|||
          index,
 | 
			
		||||
          opts,
 | 
			
		||||
        };
 | 
			
		||||
        return;
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // find target or it's previous rendered element to scroll to
 | 
			
		||||
| 
						 | 
				
			
			@ -241,9 +257,9 @@ export const useVirtualPaginator = <TScrollElement extends HTMLElement>(
 | 
			
		|||
          top: opts?.offset ?? 0,
 | 
			
		||||
          behavior: opts?.behavior,
 | 
			
		||||
        });
 | 
			
		||||
        return;
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      scrollToElement(itemElement, opts);
 | 
			
		||||
      return scrollToElement(itemElement, opts);
 | 
			
		||||
    },
 | 
			
		||||
    [getScrollElement, scrollToElement, getItemElement, onRangeChange]
 | 
			
		||||
  );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue