Message: Auto-scroll when last message height changes (#2323)
This commit is contained in:
parent
964a5001ab
commit
d8661e223e
@ -60,7 +60,7 @@ import useNativeCopySelectedMessages from '../../hooks/useNativeCopySelectedMess
|
|||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||||
import { useResizeObserver } from '../../hooks/useResizeObserver';
|
import useResizeObserver from '../../hooks/useResizeObserver';
|
||||||
|
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import MessageListContent from './MessageListContent';
|
import MessageListContent from './MessageListContent';
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { selectIsAlwaysHighPriorityEmoji, selectIsSetPremium } from '../../../gl
|
|||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||||
import { useResizeObserver } from '../../../hooks/useResizeObserver';
|
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import StickerButton from '../../common/StickerButton';
|
import StickerButton from '../../common/StickerButton';
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { fastRaf } from '../../../../util/schedulers';
|
|||||||
import AbsoluteVideo from '../../../../util/AbsoluteVideo';
|
import AbsoluteVideo from '../../../../util/AbsoluteVideo';
|
||||||
import { REM } from '../../../common/helpers/mediaDimensions';
|
import { REM } from '../../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
import { useResizeObserver } from '../../../../hooks/useResizeObserver';
|
import useResizeObserver from '../../../../hooks/useResizeObserver';
|
||||||
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
||||||
|
|
||||||
const SIZE = 1.25 * REM;
|
const SIZE = 1.25 * REM;
|
||||||
|
|||||||
@ -101,6 +101,8 @@ import { calculateAlbumLayout } from './helpers/calculateAlbumLayout';
|
|||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
import calculateAuthorWidth from './helpers/calculateAuthorWidth';
|
import calculateAuthorWidth from './helpers/calculateAuthorWidth';
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import { isAnimatingScroll } from '../../../util/fastSmoothScroll';
|
||||||
|
|
||||||
import { useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
@ -111,6 +113,7 @@ import useInnerHandlers from './hooks/useInnerHandlers';
|
|||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { isElementInViewport } from '../../../util/isElementInViewport';
|
import { isElementInViewport } from '../../../util/isElementInViewport';
|
||||||
import { getCustomEmojiSize } from '../composer/helpers/customEmoji';
|
import { getCustomEmojiSize } from '../composer/helpers/customEmoji';
|
||||||
|
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
@ -249,6 +252,7 @@ const APPENDIX_NOT_OWN = { __html: '<svg width="9" height="20" xmlns="http://www
|
|||||||
const APPEARANCE_DELAY = 10;
|
const APPEARANCE_DELAY = 10;
|
||||||
const NO_MEDIA_CORNERS_THRESHOLD = 18;
|
const NO_MEDIA_CORNERS_THRESHOLD = 18;
|
||||||
const QUICK_REACTION_SIZE = 1.75 * REM;
|
const QUICK_REACTION_SIZE = 1.75 * REM;
|
||||||
|
const BOTTOM_FOCUS_SCROLL_THRESHOLD = 5;
|
||||||
|
|
||||||
const Message: FC<OwnProps & StateProps> = ({
|
const Message: FC<OwnProps & StateProps> = ({
|
||||||
message,
|
message,
|
||||||
@ -339,6 +343,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const messageHeightRef = useRef(0);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const [isTranscriptionHidden, setTranscriptionHidden] = useState(false);
|
const [isTranscriptionHidden, setTranscriptionHidden] = useState(false);
|
||||||
@ -585,6 +591,30 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
useFocusMessage(ref, chatId, isFocused, focusDirection, noFocusHighlight, isResizingContainer);
|
useFocusMessage(ref, chatId, isFocused, focusDirection, noFocusHighlight, isResizingContainer);
|
||||||
|
|
||||||
|
const shouldFocusOnResize = isLastInGroup;
|
||||||
|
|
||||||
|
const handleResize = useCallback((entry: ResizeObserverEntry) => {
|
||||||
|
const lastHeight = messageHeightRef.current;
|
||||||
|
|
||||||
|
const newHeight = entry.target.clientHeight;
|
||||||
|
messageHeightRef.current = newHeight;
|
||||||
|
if (isAnimatingScroll() || !lastHeight || newHeight <= lastHeight) return;
|
||||||
|
|
||||||
|
const container = entry.target.closest<HTMLDivElement>('.MessageList');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const resizeDiff = newHeight - lastHeight;
|
||||||
|
const { offsetHeight, scrollHeight, scrollTop } = container;
|
||||||
|
const currentScrollBottom = Math.round(scrollHeight - scrollTop - offsetHeight);
|
||||||
|
const previousScrollBottom = currentScrollBottom - resizeDiff;
|
||||||
|
|
||||||
|
if (previousScrollBottom <= BOTTOM_FOCUS_SCROLL_THRESHOLD) {
|
||||||
|
focusLastMessage();
|
||||||
|
}
|
||||||
|
}, [focusLastMessage]);
|
||||||
|
|
||||||
|
useResizeObserver(shouldFocusOnResize ? ref : undefined, handleResize, true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const bottomMarker = bottomMarkerRef.current;
|
const bottomMarker = bottomMarkerRef.current;
|
||||||
if (hasUnreadReaction && bottomMarker && isElementInViewport(bottomMarker)) {
|
if (hasUnreadReaction && bottomMarker && isElementInViewport(bottomMarker)) {
|
||||||
|
|||||||
@ -44,7 +44,6 @@ import {
|
|||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
selectFirstUnreadId,
|
selectFirstUnreadId,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectIsChatWithBot,
|
|
||||||
selectIsServiceChatReady,
|
selectIsServiceChatReady,
|
||||||
selectLocalAnimatedEmojiEffect,
|
selectLocalAnimatedEmojiEffect,
|
||||||
selectLocalAnimatedEmoji,
|
selectLocalAnimatedEmoji,
|
||||||
@ -205,20 +204,6 @@ addActionHandler('apiUpdate', (global, actions, update) => {
|
|||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
// Scroll down if bot message height is changed with an updated inline keyboard.
|
|
||||||
// A drawback: this will scroll down even if the previous scroll was not at bottom.
|
|
||||||
if (
|
|
||||||
currentMessage
|
|
||||||
&& chat
|
|
||||||
&& !message.isOutgoing
|
|
||||||
&& chat.lastMessage?.id === message.id
|
|
||||||
&& selectIsChatWithBot(global, chat)
|
|
||||||
&& selectIsMessageInCurrentMessageList(global, chatId, message as ApiMessage)
|
|
||||||
&& selectIsViewportNewest(global, chatId, message.repliesThreadInfo?.threadId || MAIN_THREAD_ID)
|
|
||||||
) {
|
|
||||||
actions.focusLastMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
|
|
||||||
import { round } from '../util/math';
|
import { round } from '../util/math';
|
||||||
|
|
||||||
import { useResizeObserver } from './useResizeObserver';
|
import useResizeObserver from './useResizeObserver';
|
||||||
|
|
||||||
const ANIMATION_END_TIMEOUT = 500;
|
const ANIMATION_END_TIMEOUT = 500;
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { throttle } from '../util/schedulers';
|
|||||||
|
|
||||||
const THROTTLE = 300;
|
const THROTTLE = 300;
|
||||||
|
|
||||||
export function useResizeObserver(
|
export default function useResizeObserver(
|
||||||
ref: React.RefObject<HTMLElement> | undefined,
|
ref: React.RefObject<HTMLElement> | undefined,
|
||||||
onResize: (entry: ResizeObserverEntry) => void,
|
onResize: (entry: ResizeObserverEntry) => void,
|
||||||
withThrottle = false,
|
withThrottle = false,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user