Message List: Fix scroll to bottom ignoring local messages (#6418)
This commit is contained in:
parent
cb6f814c36
commit
b497a3c0ea
@ -181,7 +181,6 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
openChat,
|
openChat,
|
||||||
openSavedDialog,
|
openSavedDialog,
|
||||||
toggleChatInfo,
|
toggleChatInfo,
|
||||||
focusLastMessage,
|
|
||||||
focusMessage,
|
focusMessage,
|
||||||
loadTopics,
|
loadTopics,
|
||||||
openForumPanel,
|
openForumPanel,
|
||||||
@ -191,6 +190,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
openFrozenAccountModal,
|
openFrozenAccountModal,
|
||||||
updateChatMutedState,
|
updateChatMutedState,
|
||||||
openQuickPreview,
|
openQuickPreview,
|
||||||
|
scrollMessageListToBottom,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
@ -293,7 +293,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
openChat({ id: chatId, noForumTopicPanel, shouldReplaceHistory: true }, { forceOnHeavyAnimation: true });
|
openChat({ id: chatId, noForumTopicPanel, shouldReplaceHistory: true }, { forceOnHeavyAnimation: true });
|
||||||
|
|
||||||
if (isSelected && canScrollDown) {
|
if (isSelected && canScrollDown) {
|
||||||
focusLastMessage();
|
scrollMessageListToBottom();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -102,7 +102,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
const {
|
const {
|
||||||
openThread,
|
openThread,
|
||||||
deleteTopic,
|
deleteTopic,
|
||||||
focusLastMessage,
|
scrollMessageListToBottom,
|
||||||
setViewForumAsMessages,
|
setViewForumAsMessages,
|
||||||
updateTopicMutedState,
|
updateTopicMutedState,
|
||||||
openQuickPreview,
|
openQuickPreview,
|
||||||
@ -168,7 +168,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
setViewForumAsMessages({ chatId, isEnabled: false });
|
setViewForumAsMessages({ chatId, isEnabled: false });
|
||||||
|
|
||||||
if (canScrollDown) {
|
if (canScrollDown) {
|
||||||
focusLastMessage();
|
scrollMessageListToBottom();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import type { MessageListType, ThreadId } from '../../types';
|
|||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import { selectChat, selectCurrentMessageList, selectCurrentMiddleSearch } from '../../global/selectors';
|
import { selectChat, selectCurrentMessageList, selectCurrentMiddleSearch } from '../../global/selectors';
|
||||||
import animateScroll from '../../util/animateScroll';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
@ -32,8 +31,6 @@ type StateProps = {
|
|||||||
mentionsCount?: number;
|
mentionsCount?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FOCUS_MARGIN = 20;
|
|
||||||
|
|
||||||
const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
||||||
withScrollDown,
|
withScrollDown,
|
||||||
canPost,
|
canPost,
|
||||||
@ -49,7 +46,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
focusNextReply, focusNextReaction, focusNextMention, fetchUnreadReactions,
|
focusNextReply, focusNextReaction, focusNextMention, fetchUnreadReactions,
|
||||||
readAllMentions, readAllReactions, fetchUnreadMentions,
|
readAllMentions, readAllReactions, fetchUnreadMentions, scrollMessageListToBottom,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
const elementRef = useRef<HTMLDivElement>();
|
||||||
@ -99,21 +96,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
|||||||
if (messageListType === 'thread') {
|
if (messageListType === 'thread') {
|
||||||
focusNextReply();
|
focusNextReply();
|
||||||
} else {
|
} else {
|
||||||
const messagesContainer = elementRef.current!.parentElement!.querySelector<HTMLDivElement>(
|
scrollMessageListToBottom();
|
||||||
'.Transition_slide-active > .MessageList',
|
|
||||||
)!;
|
|
||||||
const messageElements = messagesContainer.querySelectorAll<HTMLDivElement>('.message-list-item');
|
|
||||||
const lastMessageElement = messageElements[messageElements.length - 1];
|
|
||||||
if (!lastMessageElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
animateScroll({
|
|
||||||
container: messagesContainer,
|
|
||||||
element: lastMessageElement,
|
|
||||||
position: 'end',
|
|
||||||
margin: FOCUS_MARGIN,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -321,6 +321,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-bottom-marker.with-sponsored {
|
||||||
|
position: relative;
|
||||||
|
top: -1rem; // Prevent overlapping with the sponsored message
|
||||||
|
}
|
||||||
|
|
||||||
@media (pointer: coarse) {
|
@media (pointer: coarse) {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,7 @@ import { isLocalMessageId } from '../../util/keys/messageKey';
|
|||||||
import resetScroll from '../../util/resetScroll';
|
import resetScroll from '../../util/resetScroll';
|
||||||
import { debounce, onTickEnd } from '../../util/schedulers';
|
import { debounce, onTickEnd } from '../../util/schedulers';
|
||||||
import getOffsetToContainer from '../../util/visibility/getOffsetToContainer';
|
import getOffsetToContainer from '../../util/visibility/getOffsetToContainer';
|
||||||
|
import { REM } from '../common/helpers/mediaDimensions';
|
||||||
import { groupMessages } from './helpers/groupMessages';
|
import { groupMessages } from './helpers/groupMessages';
|
||||||
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ import useContainerHeight from './hooks/useContainerHeight';
|
|||||||
import useStickyDates from './hooks/useStickyDates';
|
import useStickyDates from './hooks/useStickyDates';
|
||||||
|
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import Transition from '../ui/Transition.tsx';
|
import Transition from '../ui/Transition';
|
||||||
import ContactGreeting from './ContactGreeting';
|
import ContactGreeting from './ContactGreeting';
|
||||||
import MessageListAccountInfo from './MessageListAccountInfo';
|
import MessageListAccountInfo from './MessageListAccountInfo';
|
||||||
import MessageListContent from './MessageListContent';
|
import MessageListContent from './MessageListContent';
|
||||||
@ -144,6 +145,7 @@ type StateProps = {
|
|||||||
translationLanguage?: string;
|
translationLanguage?: string;
|
||||||
shouldAutoTranslate?: boolean;
|
shouldAutoTranslate?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
shouldScrollToBottom?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Content {
|
enum Content {
|
||||||
@ -168,7 +170,7 @@ const BOTTOM_THRESHOLD = 50;
|
|||||||
const UNREAD_DIVIDER_TOP = 10;
|
const UNREAD_DIVIDER_TOP = 10;
|
||||||
const SCROLL_DEBOUNCE = 200;
|
const SCROLL_DEBOUNCE = 200;
|
||||||
const MESSAGE_ANIMATION_DURATION = 500;
|
const MESSAGE_ANIMATION_DURATION = 500;
|
||||||
const BOTTOM_FOCUS_MARGIN = 20;
|
const BOTTOM_FOCUS_MARGIN = 0.5 * REM;
|
||||||
const SELECT_MODE_ANIMATION_DURATION = 200;
|
const SELECT_MODE_ANIMATION_DURATION = 200;
|
||||||
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
||||||
|
|
||||||
@ -186,6 +188,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
canPost,
|
canPost,
|
||||||
isSynced,
|
isSynced,
|
||||||
isActive,
|
isActive,
|
||||||
|
shouldScrollToBottom,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
isChatMonoforum,
|
isChatMonoforum,
|
||||||
isReady,
|
isReady,
|
||||||
@ -622,11 +625,11 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
if (wasMessageAdded && isAtBottom && !isAlreadyFocusing) {
|
if (wasMessageAdded && isAtBottom && !isAlreadyFocusing) {
|
||||||
// Break out of `forceLayout`
|
// Break out of `forceLayout`
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
const shouldScrollToBottom = !isBackgroundModeActive() || !firstUnreadElement;
|
const isScrollToBottom = !isBackgroundModeActive() || !firstUnreadElement;
|
||||||
animateScroll({
|
animateScroll({
|
||||||
container,
|
container,
|
||||||
element: shouldScrollToBottom ? lastItemElement : firstUnreadElement,
|
element: isScrollToBottom ? lastItemElement : firstUnreadElement,
|
||||||
position: shouldScrollToBottom ? 'end' : 'start',
|
position: isScrollToBottom ? 'end' : 'start',
|
||||||
margin: BOTTOM_FOCUS_MARGIN,
|
margin: BOTTOM_FOCUS_MARGIN,
|
||||||
forceDuration: noMessageSendingAnimation ? 0 : undefined,
|
forceDuration: noMessageSendingAnimation ? 0 : undefined,
|
||||||
});
|
});
|
||||||
@ -800,10 +803,11 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
photoChangeDate={photoChangeDate}
|
photoChangeDate={photoChangeDate}
|
||||||
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
||||||
isQuickPreview={isQuickPreview}
|
isQuickPreview={isQuickPreview}
|
||||||
|
canPost={canPost}
|
||||||
|
shouldScrollToBottom={shouldScrollToBottom}
|
||||||
onScrollDownToggle={onScrollDownToggle}
|
onScrollDownToggle={onScrollDownToggle}
|
||||||
onNotchToggle={onNotchToggle}
|
onNotchToggle={onNotchToggle}
|
||||||
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
||||||
canPost={canPost}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Loading color="white" backgroundColor="dark" />
|
<Loading color="white" backgroundColor="dark" />
|
||||||
@ -827,6 +831,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId, threadId, type }): Complete<StateProps> => {
|
(global, { chatId, threadId, type }): Complete<StateProps> => {
|
||||||
|
const tabState = selectTabState(global);
|
||||||
const currentUserId = global.currentUserId!;
|
const currentUserId = global.currentUserId!;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const userFullInfo = selectUserFullInfo(global, chatId);
|
const userFullInfo = selectUserFullInfo(global, chatId);
|
||||||
@ -883,6 +888,13 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isActive = currentMessageList && currentMessageList.chatId === chatId
|
const isActive = currentMessageList && currentMessageList.chatId === chatId
|
||||||
&& currentMessageList.threadId === threadId && currentMessageList.type === type;
|
&& currentMessageList.threadId === threadId && currentMessageList.type === type;
|
||||||
|
|
||||||
|
const {
|
||||||
|
chatId: focusedChatId,
|
||||||
|
threadId: focusedThreadId,
|
||||||
|
messageId: focusedMessageId,
|
||||||
|
} = tabState.focusedMessage || {};
|
||||||
|
const shouldScrollToBottom = focusedChatId === chatId && focusedThreadId === threadId && !focusedMessageId;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isActive,
|
isActive,
|
||||||
areAdsEnabled,
|
areAdsEnabled,
|
||||||
@ -925,6 +937,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canTranslate,
|
canTranslate,
|
||||||
translationLanguage,
|
translationLanguage,
|
||||||
shouldAutoTranslate,
|
shouldAutoTranslate,
|
||||||
|
shouldScrollToBottom,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MessageList));
|
)(MessageList));
|
||||||
|
|||||||
28
src/components/middle/MessageListBottomMarker.tsx
Normal file
28
src/components/middle/MessageListBottomMarker.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { memo, useRef } from '@teact';
|
||||||
|
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useFocusMessageListElement from './message/hooks/useFocusMessageListElement';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
isJustAdded?: boolean;
|
||||||
|
isFocused?: boolean;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MessageListBottomMarker = ({ isJustAdded, isFocused, className }: OwnProps) => {
|
||||||
|
const ref = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
useFocusMessageListElement({
|
||||||
|
elementRef: ref,
|
||||||
|
isJustAdded,
|
||||||
|
isFocused,
|
||||||
|
noFocusHighlight: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={buildClassName('list-bottom-marker', className)} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(MessageListBottomMarker);
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ElementRef, FC } from '../../lib/teact/teact';
|
import type { ElementRef } from '../../lib/teact/teact';
|
||||||
import { getIsHeavyAnimating, memo } from '../../lib/teact/teact';
|
import { getIsHeavyAnimating, memo } from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal } from '../../global';
|
import { getActions, getGlobal } from '../../global';
|
||||||
|
|
||||||
@ -43,6 +43,7 @@ import Message from './message/Message';
|
|||||||
import SenderGroupContainer from './message/SenderGroupContainer';
|
import SenderGroupContainer from './message/SenderGroupContainer';
|
||||||
import SponsoredMessage from './message/SponsoredMessage';
|
import SponsoredMessage from './message/SponsoredMessage';
|
||||||
import MessageListAccountInfo from './MessageListAccountInfo';
|
import MessageListAccountInfo from './MessageListAccountInfo';
|
||||||
|
import MessageListBottomMarker from './MessageListBottomMarker';
|
||||||
|
|
||||||
import actionMessageStyles from './message/ActionMessage.module.scss';
|
import actionMessageStyles from './message/ActionMessage.module.scss';
|
||||||
|
|
||||||
@ -75,15 +76,16 @@ interface OwnProps {
|
|||||||
noAppearanceAnimation: boolean;
|
noAppearanceAnimation: boolean;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
isQuickPreview?: boolean;
|
isQuickPreview?: boolean;
|
||||||
|
canPost?: boolean;
|
||||||
|
shouldScrollToBottom?: boolean;
|
||||||
onScrollDownToggle?: BooleanToVoidFunction;
|
onScrollDownToggle?: BooleanToVoidFunction;
|
||||||
onNotchToggle?: AnyToVoidFunction;
|
onNotchToggle?: AnyToVoidFunction;
|
||||||
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
|
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
|
||||||
canPost?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
||||||
|
|
||||||
const MessageListContent: FC<OwnProps> = ({
|
const MessageListContent = ({
|
||||||
canShowAds,
|
canShowAds,
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
@ -112,17 +114,19 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
noAppearanceAnimation,
|
noAppearanceAnimation,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
isQuickPreview,
|
isQuickPreview,
|
||||||
|
shouldScrollToBottom,
|
||||||
|
canPost,
|
||||||
onScrollDownToggle,
|
onScrollDownToggle,
|
||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
onIntersectPinnedMessage,
|
onIntersectPinnedMessage,
|
||||||
canPost,
|
}: OwnProps) => {
|
||||||
}) => {
|
|
||||||
const { openHistoryCalendar } = getActions();
|
const { openHistoryCalendar } = getActions();
|
||||||
|
|
||||||
const getIsHeavyAnimating2 = getIsHeavyAnimating;
|
const getIsHeavyAnimating2 = getIsHeavyAnimating;
|
||||||
const getIsReady = useDerivedSignal(() => isReady && !getIsHeavyAnimating2(), [isReady, getIsHeavyAnimating2]);
|
const getIsReady = useDerivedSignal(() => isReady && !getIsHeavyAnimating2(), [isReady, getIsHeavyAnimating2]);
|
||||||
|
|
||||||
const areDatesClickable = !isSavedDialog && !isSchedule;
|
const areDatesClickable = !isSavedDialog && !isSchedule;
|
||||||
|
const shouldRenderSponsoredMessage = canShowAds && isViewportNewest;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
observeIntersectionForReading,
|
observeIntersectionForReading,
|
||||||
@ -135,17 +139,17 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
backwardsTriggerRef,
|
backwardsTriggerRef,
|
||||||
forwardsTriggerRef,
|
forwardsTriggerRef,
|
||||||
fabTriggerRef,
|
fabTriggerRef,
|
||||||
} = useScrollHooks(
|
} = useScrollHooks({
|
||||||
type,
|
type,
|
||||||
containerRef,
|
containerRef,
|
||||||
messageIds,
|
messageIds,
|
||||||
getContainerHeight,
|
getContainerHeight,
|
||||||
isViewportNewest,
|
isViewportNewest,
|
||||||
isUnread,
|
isUnread,
|
||||||
|
isReady,
|
||||||
onScrollDownToggle,
|
onScrollDownToggle,
|
||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
isReady,
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -457,7 +461,15 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
key="fab-trigger"
|
key="fab-trigger"
|
||||||
className="fab-trigger"
|
className="fab-trigger"
|
||||||
/>
|
/>
|
||||||
{canShowAds && isViewportNewest && (
|
{isViewportNewest && (
|
||||||
|
<MessageListBottomMarker
|
||||||
|
key="bottom-marker"
|
||||||
|
isJustAdded={isNewMessage}
|
||||||
|
isFocused={shouldScrollToBottom}
|
||||||
|
className={shouldRenderSponsoredMessage ? 'with-sponsored' : undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{shouldRenderSponsoredMessage && (
|
||||||
<SponsoredMessage
|
<SponsoredMessage
|
||||||
key={chatId}
|
key={chatId}
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
|
|||||||
@ -21,17 +21,27 @@ const NOTCH_THRESHOLD = 1; // Notch has zero height so we at least need a 1px ma
|
|||||||
const CONTAINER_HEIGHT_DEBOUNCE = 200;
|
const CONTAINER_HEIGHT_DEBOUNCE = 200;
|
||||||
const TOOLS_FREEZE_TIMEOUT = 350; // Approximate message sending animation duration
|
const TOOLS_FREEZE_TIMEOUT = 350; // Approximate message sending animation duration
|
||||||
|
|
||||||
export default function useScrollHooks(
|
export default function useScrollHooks({
|
||||||
type: MessageListType,
|
type,
|
||||||
containerRef: ElementRef<HTMLDivElement>,
|
containerRef,
|
||||||
messageIds: number[],
|
messageIds,
|
||||||
getContainerHeight: Signal<number | undefined>,
|
getContainerHeight,
|
||||||
isViewportNewest: boolean,
|
isViewportNewest,
|
||||||
isUnread: boolean,
|
isUnread,
|
||||||
onScrollDownToggle: BooleanToVoidFunction | undefined,
|
isReady,
|
||||||
onNotchToggle: AnyToVoidFunction | undefined,
|
onScrollDownToggle,
|
||||||
isReady: boolean,
|
onNotchToggle,
|
||||||
) {
|
}: {
|
||||||
|
type: MessageListType;
|
||||||
|
containerRef: ElementRef<HTMLDivElement>;
|
||||||
|
messageIds: number[];
|
||||||
|
getContainerHeight: Signal<number | undefined>;
|
||||||
|
isViewportNewest: boolean;
|
||||||
|
isUnread: boolean;
|
||||||
|
isReady: boolean;
|
||||||
|
onScrollDownToggle: BooleanToVoidFunction | undefined;
|
||||||
|
onNotchToggle: AnyToVoidFunction | undefined;
|
||||||
|
}) {
|
||||||
const { loadViewportMessages } = getActions();
|
const { loadViewportMessages } = getActions();
|
||||||
|
|
||||||
const [loadMoreBackwards, loadMoreForwards] = useMemo(
|
const [loadMoreBackwards, loadMoreForwards] = useMemo(
|
||||||
@ -136,7 +146,18 @@ export default function useScrollHooks(
|
|||||||
if (isReady) {
|
if (isReady) {
|
||||||
toggleScrollTools();
|
toggleScrollTools();
|
||||||
}
|
}
|
||||||
}, [isReady, toggleScrollTools]);
|
}, [isReady]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.addEventListener('scrollend', toggleScrollTools);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
container.removeEventListener('scrollend', toggleScrollTools);
|
||||||
|
};
|
||||||
|
}, [containerRef]);
|
||||||
|
|
||||||
const freezeShortly = useLastCallback(() => {
|
const freezeShortly = useLastCallback(() => {
|
||||||
freezeForFab();
|
freezeForFab();
|
||||||
|
|||||||
@ -44,7 +44,7 @@ import useMessageResizeObserver from '../../../hooks/useResizeMessageObserver';
|
|||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import { type OnIntersectPinnedMessage } from '../hooks/usePinnedMessage';
|
import { type OnIntersectPinnedMessage } from '../hooks/usePinnedMessage';
|
||||||
import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter';
|
import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter';
|
||||||
import useFocusMessage from './hooks/useFocusMessage';
|
import useFocusMessageListElement from './hooks/useFocusMessageListElement';
|
||||||
|
|
||||||
import ActionMessageText from './ActionMessageText';
|
import ActionMessageText from './ActionMessageText';
|
||||||
import ChannelPhoto from './actions/ChannelPhoto';
|
import ChannelPhoto from './actions/ChannelPhoto';
|
||||||
@ -176,9 +176,8 @@ const ActionMessage = ({
|
|||||||
replyMessage,
|
replyMessage,
|
||||||
id,
|
id,
|
||||||
);
|
);
|
||||||
useFocusMessage({
|
useFocusMessageListElement({
|
||||||
elementRef: ref,
|
elementRef: ref,
|
||||||
chatId,
|
|
||||||
isFocused,
|
isFocused,
|
||||||
focusDirection,
|
focusDirection,
|
||||||
noFocusHighlight,
|
noFocusHighlight,
|
||||||
|
|||||||
@ -154,7 +154,7 @@ import useMessageResizeObserver from '../../../hooks/useResizeMessageObserver';
|
|||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useTextLanguage from '../../../hooks/useTextLanguage';
|
import useTextLanguage from '../../../hooks/useTextLanguage';
|
||||||
import useDetectChatLanguage from './hooks/useDetectChatLanguage';
|
import useDetectChatLanguage from './hooks/useDetectChatLanguage';
|
||||||
import useFocusMessage from './hooks/useFocusMessage';
|
import useFocusMessageListElement from './hooks/useFocusMessageListElement';
|
||||||
import useInnerHandlers from './hooks/useInnerHandlers';
|
import useInnerHandlers from './hooks/useInnerHandlers';
|
||||||
import useMessageTranslation from './hooks/useMessageTranslation';
|
import useMessageTranslation from './hooks/useMessageTranslation';
|
||||||
import useOuterHandlers from './hooks/useOuterHandlers';
|
import useOuterHandlers from './hooks/useOuterHandlers';
|
||||||
@ -462,7 +462,7 @@ const Message = ({
|
|||||||
openSuggestedPostApprovalModal,
|
openSuggestedPostApprovalModal,
|
||||||
disableContextMenuHint,
|
disableContextMenuHint,
|
||||||
animateUnreadReaction,
|
animateUnreadReaction,
|
||||||
focusLastMessage,
|
focusMessage,
|
||||||
markMentionsRead,
|
markMentionsRead,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
@ -698,15 +698,24 @@ const Message = ({
|
|||||||
requestEffect();
|
requestEffect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleFocusSelf = useLastCallback(() => {
|
||||||
|
focusMessage({
|
||||||
|
chatId,
|
||||||
|
threadId,
|
||||||
|
messageId,
|
||||||
|
noHighlight: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLastInList) {
|
if (!isLastInList) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withVoiceTranscription && transcribedText) {
|
if (withVoiceTranscription && transcribedText) {
|
||||||
focusLastMessage();
|
handleFocusSelf();
|
||||||
}
|
}
|
||||||
}, [focusLastMessage, isLastInList, transcribedText, withVoiceTranscription]);
|
}, [isLastInList, transcribedText, withVoiceTranscription]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = ref.current;
|
const element = ref.current;
|
||||||
@ -882,9 +891,8 @@ const Message = ({
|
|||||||
replyStory,
|
replyStory,
|
||||||
);
|
);
|
||||||
|
|
||||||
useFocusMessage({
|
useFocusMessageListElement({
|
||||||
elementRef: ref,
|
elementRef: ref,
|
||||||
chatId,
|
|
||||||
isFocused,
|
isFocused,
|
||||||
focusDirection,
|
focusDirection,
|
||||||
noFocusHighlight,
|
noFocusHighlight,
|
||||||
|
|||||||
@ -9,15 +9,16 @@ import {
|
|||||||
requestForcedReflow, requestMeasure, requestMutation,
|
requestForcedReflow, requestMeasure, requestMutation,
|
||||||
} from '../../../../lib/fasterdom/fasterdom';
|
} from '../../../../lib/fasterdom/fasterdom';
|
||||||
import animateScroll from '../../../../util/animateScroll';
|
import animateScroll from '../../../../util/animateScroll';
|
||||||
|
import { REM } from '../../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
// This is used when the viewport was replaced.
|
// This is used when the viewport was replaced.
|
||||||
const BOTTOM_FOCUS_OFFSET = 500;
|
const BOTTOM_FOCUS_OFFSET = 500;
|
||||||
const RELOCATED_FOCUS_OFFSET = SCROLL_MAX_DISTANCE;
|
const RELOCATED_FOCUS_OFFSET = SCROLL_MAX_DISTANCE;
|
||||||
const FOCUS_MARGIN = 20;
|
const FOCUS_MARGIN = 1.25 * REM;
|
||||||
|
const BOTTOM_FOCUS_MARGIN = 0.5 * REM;
|
||||||
|
|
||||||
export default function useFocusMessage({
|
export default function useFocusMessageListElement({
|
||||||
elementRef,
|
elementRef,
|
||||||
chatId,
|
|
||||||
isFocused,
|
isFocused,
|
||||||
focusDirection,
|
focusDirection,
|
||||||
noFocusHighlight,
|
noFocusHighlight,
|
||||||
@ -27,7 +28,6 @@ export default function useFocusMessage({
|
|||||||
scrollTargetPosition,
|
scrollTargetPosition,
|
||||||
}: {
|
}: {
|
||||||
elementRef: ElementRef<HTMLDivElement>;
|
elementRef: ElementRef<HTMLDivElement>;
|
||||||
chatId: string;
|
|
||||||
isFocused?: boolean;
|
isFocused?: boolean;
|
||||||
focusDirection?: FocusDirection;
|
focusDirection?: FocusDirection;
|
||||||
noFocusHighlight?: boolean;
|
noFocusHighlight?: boolean;
|
||||||
@ -43,10 +43,12 @@ export default function useFocusMessage({
|
|||||||
isRelocatedRef.current = false;
|
isRelocatedRef.current = false;
|
||||||
|
|
||||||
if (isFocused && elementRef.current) {
|
if (isFocused && elementRef.current) {
|
||||||
const messagesContainer = elementRef.current.closest<HTMLDivElement>('.MessageList')!;
|
const messagesContainer = elementRef.current.closest<HTMLDivElement>('.MessageList');
|
||||||
|
if (!messagesContainer) return;
|
||||||
|
|
||||||
// `noFocusHighlight` is always called with “scroll-to-bottom” buttons
|
// `noFocusHighlight` is always called with “scroll-to-bottom” buttons
|
||||||
const isToBottom = noFocusHighlight;
|
const isToBottom = noFocusHighlight;
|
||||||
const scrollPosition = scrollTargetPosition || isToBottom ? 'end' : 'centerOrTop';
|
const scrollPosition = scrollTargetPosition || (isToBottom ? 'end' : 'centerOrTop');
|
||||||
|
|
||||||
const exec = () => {
|
const exec = () => {
|
||||||
const maxDistance = focusDirection !== undefined
|
const maxDistance = focusDirection !== undefined
|
||||||
@ -56,7 +58,7 @@ export default function useFocusMessage({
|
|||||||
container: messagesContainer,
|
container: messagesContainer,
|
||||||
element: elementRef.current!,
|
element: elementRef.current!,
|
||||||
position: scrollPosition,
|
position: scrollPosition,
|
||||||
margin: FOCUS_MARGIN,
|
margin: isToBottom ? BOTTOM_FOCUS_MARGIN : FOCUS_MARGIN,
|
||||||
maxDistance,
|
maxDistance,
|
||||||
forceDirection: focusDirection,
|
forceDirection: focusDirection,
|
||||||
forceNormalContainerHeight: isResizingContainer,
|
forceNormalContainerHeight: isResizingContainer,
|
||||||
@ -85,6 +87,6 @@ export default function useFocusMessage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
elementRef, chatId, isFocused, focusDirection, noFocusHighlight, isResizingContainer, isQuote, scrollTargetPosition,
|
elementRef, isFocused, focusDirection, noFocusHighlight, isResizingContainer, isQuote, scrollTargetPosition,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -169,6 +169,7 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
|
|||||||
direction = LoadMoreDirection.Around,
|
direction = LoadMoreDirection.Around,
|
||||||
isBudgetPreload = false,
|
isBudgetPreload = false,
|
||||||
shouldForceRender = false,
|
shouldForceRender = false,
|
||||||
|
forceLastSlice = false,
|
||||||
onLoaded,
|
onLoaded,
|
||||||
onError,
|
onError,
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
@ -199,7 +200,9 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
|
|||||||
const listedIds = selectListedIds(global, chatId, threadId);
|
const listedIds = selectListedIds(global, chatId, threadId);
|
||||||
|
|
||||||
if (!viewportIds || !viewportIds.length || direction === LoadMoreDirection.Around) {
|
if (!viewportIds || !viewportIds.length || direction === LoadMoreDirection.Around) {
|
||||||
const offsetId = selectFocusedMessageId(global, chatId, tabId) || selectRealLastReadId(global, chatId, threadId);
|
const offsetId = !forceLastSlice ? (
|
||||||
|
selectFocusedMessageId(global, chatId, tabId) || selectRealLastReadId(global, chatId, threadId)
|
||||||
|
) : undefined;
|
||||||
const isOutlying = Boolean(offsetId && listedIds && !listedIds.includes(offsetId));
|
const isOutlying = Boolean(offsetId && listedIds && !listedIds.includes(offsetId));
|
||||||
const historyIds = (isOutlying
|
const historyIds = (isOutlying
|
||||||
? selectOutlyingListByMessageId(global, chatId, threadId, offsetId!)
|
? selectOutlyingListByMessageId(global, chatId, threadId, offsetId!)
|
||||||
@ -222,17 +225,19 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
|
|||||||
onLoaded?.();
|
onLoaded?.();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const offsetId = direction === LoadMoreDirection.Backwards ? viewportIds[0] : viewportIds[viewportIds.length - 1];
|
const offsetId = !forceLastSlice ? (
|
||||||
|
direction === LoadMoreDirection.Backwards ? viewportIds[0] : viewportIds[viewportIds.length - 1]
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
// Prevent requests with local offsets
|
// Prevent requests with local offsets
|
||||||
if (isLocalMessageId(offsetId)) return;
|
if (offsetId && isLocalMessageId(offsetId)) return;
|
||||||
|
|
||||||
// Prevent unnecessary requests in threads
|
// Prevent unnecessary requests in threads
|
||||||
if (offsetId === threadId && direction === LoadMoreDirection.Backwards) return;
|
if (offsetId === threadId && direction === LoadMoreDirection.Backwards) return;
|
||||||
|
|
||||||
const isOutlying = Boolean(listedIds && !listedIds.includes(offsetId));
|
const isOutlying = Boolean(listedIds && offsetId && !listedIds.includes(offsetId));
|
||||||
const historyIds = (isOutlying
|
const historyIds = (isOutlying
|
||||||
? selectOutlyingListByMessageId(global, chatId, threadId, offsetId) : listedIds)!;
|
? selectOutlyingListByMessageId(global, chatId, threadId, offsetId!) : listedIds)!;
|
||||||
if (historyIds?.length) {
|
if (historyIds?.length) {
|
||||||
const {
|
const {
|
||||||
newViewportIds, areSomeLocal, areAllLocal,
|
newViewportIds, areSomeLocal, areAllLocal,
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import parseHtmlAsFormattedText from '../../../util/parseHtmlAsFormattedText';
|
|||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import versionNotification from '../../../versionNotification.txt';
|
import versionNotification from '../../../versionNotification.txt';
|
||||||
import {
|
import {
|
||||||
getIsSavedDialog,
|
|
||||||
getMediaFilename,
|
getMediaFilename,
|
||||||
getMediaFormat,
|
getMediaFormat,
|
||||||
getMediaHash,
|
getMediaHash,
|
||||||
@ -41,7 +40,6 @@ import {
|
|||||||
replaceTabThreadParam,
|
replaceTabThreadParam,
|
||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
toggleMessageSelection,
|
toggleMessageSelection,
|
||||||
updateFocusDirection,
|
|
||||||
updateFocusedMessage,
|
updateFocusedMessage,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
@ -60,7 +58,6 @@ import {
|
|||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectIsViewportNewest,
|
selectIsViewportNewest,
|
||||||
selectMessageIdsByGroupId,
|
selectMessageIdsByGroupId,
|
||||||
selectPinnedIds,
|
|
||||||
selectReplyStack,
|
selectReplyStack,
|
||||||
selectRequestedChatTranslationLanguage,
|
selectRequestedChatTranslationLanguage,
|
||||||
selectRequestedMessageTranslationLanguage,
|
selectRequestedMessageTranslationLanguage,
|
||||||
@ -323,52 +320,6 @@ addActionHandler('closePollResults', (global, actions, payload): ActionReturnTyp
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('focusLastMessage', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
|
||||||
if (!currentMessageList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { chatId, threadId, type } = currentMessageList;
|
|
||||||
|
|
||||||
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
|
||||||
|
|
||||||
let lastMessageId: number | undefined;
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
|
||||||
if (type === 'pinned') {
|
|
||||||
const pinnedMessageIds = selectPinnedIds(global, chatId, MAIN_THREAD_ID);
|
|
||||||
if (!pinnedMessageIds?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastMessageId = pinnedMessageIds[pinnedMessageIds.length - 1];
|
|
||||||
} else {
|
|
||||||
lastMessageId = selectChatLastMessageId(global, chatId);
|
|
||||||
}
|
|
||||||
} else if (isSavedDialog) {
|
|
||||||
lastMessageId = selectChatLastMessageId(global, String(threadId), 'saved');
|
|
||||||
} else {
|
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
|
||||||
|
|
||||||
lastMessageId = threadInfo?.lastMessageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lastMessageId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.focusMessage({
|
|
||||||
chatId,
|
|
||||||
threadId,
|
|
||||||
messageListType: type,
|
|
||||||
messageId: lastMessageId,
|
|
||||||
noHighlight: true,
|
|
||||||
noForumTopicPanel: true,
|
|
||||||
tabId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('focusNextReply', (global, actions, payload): ActionReturnType => {
|
addActionHandler('focusNextReply', (global, actions, payload): ActionReturnType => {
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
@ -381,7 +332,7 @@ addActionHandler('focusNextReply', (global, actions, payload): ActionReturnType
|
|||||||
const replyStack = selectReplyStack(global, chatId, threadId, tabId);
|
const replyStack = selectReplyStack(global, chatId, threadId, tabId);
|
||||||
|
|
||||||
if (!replyStack || replyStack.length === 0) {
|
if (!replyStack || replyStack.length === 0) {
|
||||||
actions.focusLastMessage({ tabId });
|
actions.scrollMessageListToBottom({ tabId });
|
||||||
} else {
|
} else {
|
||||||
const messageId = replyStack.pop();
|
const messageId = replyStack.pop();
|
||||||
|
|
||||||
@ -441,13 +392,11 @@ addActionHandler('focusMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
}
|
}
|
||||||
blurTimeout = window.setTimeout(() => {
|
blurTimeout = window.setTimeout(() => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateFocusedMessage({ global }, tabId);
|
global = updateFocusedMessage(global, undefined, tabId);
|
||||||
global = updateFocusDirection(global, undefined, tabId);
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}, noHighlight ? FOCUS_NO_HIGHLIGHT_DURATION : FOCUS_DURATION);
|
}, noHighlight ? FOCUS_NO_HIGHLIGHT_DURATION : FOCUS_DURATION);
|
||||||
|
|
||||||
global = updateFocusedMessage({
|
global = updateFocusedMessage(global, {
|
||||||
global,
|
|
||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
threadId,
|
threadId,
|
||||||
@ -456,8 +405,8 @@ addActionHandler('focusMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
quote,
|
quote,
|
||||||
quoteOffset,
|
quoteOffset,
|
||||||
scrollTargetPosition,
|
scrollTargetPosition,
|
||||||
|
direction: undefined,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
global = updateFocusDirection(global, undefined, tabId);
|
|
||||||
|
|
||||||
if (replyMessageId) {
|
if (replyMessageId) {
|
||||||
const replyStack = selectReplyStack(global, chatId, threadId, tabId) || [];
|
const replyStack = selectReplyStack(global, chatId, threadId, tabId) || [];
|
||||||
@ -465,7 +414,7 @@ addActionHandler('focusMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldSwitchChat) {
|
if (shouldSwitchChat) {
|
||||||
global = updateFocusDirection(global, FocusDirection.Static, tabId);
|
global = updateFocusedMessage(global, { direction: FocusDirection.Static }, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||||
@ -489,7 +438,7 @@ addActionHandler('focusMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
|
|
||||||
if (viewportIds && !shouldSwitchChat) {
|
if (viewportIds && !shouldSwitchChat) {
|
||||||
const direction = messageId > viewportIds[0] ? FocusDirection.Down : FocusDirection.Up;
|
const direction = messageId > viewportIds[0] ? FocusDirection.Down : FocusDirection.Up;
|
||||||
global = updateFocusDirection(global, direction, tabId);
|
global = updateFocusedMessage(global, { direction }, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAnimatingScroll()) {
|
if (isAnimatingScroll()) {
|
||||||
@ -516,6 +465,50 @@ addActionHandler('focusMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('scrollMessageListToBottom', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
|
if (!currentMessageList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { chatId, threadId } = currentMessageList;
|
||||||
|
|
||||||
|
global = updateFocusedMessage(global, {
|
||||||
|
chatId,
|
||||||
|
threadId,
|
||||||
|
messageId: undefined,
|
||||||
|
scrollTargetPosition: 'end',
|
||||||
|
direction: FocusDirection.Down,
|
||||||
|
noHighlight: true,
|
||||||
|
}, tabId);
|
||||||
|
|
||||||
|
setGlobal(global, { forceOnHeavyAnimation: true });
|
||||||
|
|
||||||
|
// Reuse part of `focusMessage`
|
||||||
|
if (blurTimeout) {
|
||||||
|
clearTimeout(blurTimeout);
|
||||||
|
blurTimeout = undefined;
|
||||||
|
}
|
||||||
|
blurTimeout = window.setTimeout(() => {
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateFocusedMessage(global, undefined, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
}, FOCUS_NO_HIGHLIGHT_DURATION);
|
||||||
|
|
||||||
|
if (isAnimatingScroll()) {
|
||||||
|
cancelScrollBlockingAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.loadViewportMessages({
|
||||||
|
chatId,
|
||||||
|
threadId,
|
||||||
|
tabId,
|
||||||
|
shouldForceRender: true,
|
||||||
|
forceLastSlice: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('setShouldPreventComposerAnimation', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setShouldPreventComposerAnimation', (global, actions, payload): ActionReturnType => {
|
||||||
const { shouldPreventComposerAnimation, tabId = getCurrentTabId() } = payload;
|
const { shouldPreventComposerAnimation, tabId = getCurrentTabId() } = payload;
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
|
|||||||
@ -4,10 +4,8 @@ import type {
|
|||||||
ApiWebPageFull,
|
ApiWebPageFull,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type {
|
import type {
|
||||||
FocusDirection,
|
|
||||||
MessageList,
|
MessageList,
|
||||||
MessageListType,
|
MessageListType,
|
||||||
ScrollTargetPosition,
|
|
||||||
TabThread,
|
TabThread,
|
||||||
Thread,
|
Thread,
|
||||||
ThreadId,
|
ThreadId,
|
||||||
@ -719,40 +717,16 @@ export function updateQuickReplyMessages<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateFocusedMessage<T extends GlobalState>(
|
export function updateFocusedMessage<T extends GlobalState>(
|
||||||
{
|
global: T, update: Partial<TabState['focusedMessage']> | undefined, ...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
global,
|
|
||||||
chatId,
|
|
||||||
messageId,
|
|
||||||
threadId = MAIN_THREAD_ID,
|
|
||||||
noHighlight = false,
|
|
||||||
isResizingContainer = false,
|
|
||||||
quote,
|
|
||||||
quoteOffset,
|
|
||||||
scrollTargetPosition,
|
|
||||||
}: {
|
|
||||||
global: T;
|
|
||||||
chatId?: string;
|
|
||||||
messageId?: number;
|
|
||||||
threadId?: ThreadId;
|
|
||||||
noHighlight?: boolean;
|
|
||||||
isResizingContainer?: boolean;
|
|
||||||
quote?: string;
|
|
||||||
quoteOffset?: number;
|
|
||||||
scrollTargetPosition?: ScrollTargetPosition;
|
|
||||||
},
|
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
|
||||||
): T {
|
): T {
|
||||||
|
if (!update) {
|
||||||
|
return updateTabState(global, { focusedMessage: undefined }, tabId);
|
||||||
|
}
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
focusedMessage: {
|
focusedMessage: {
|
||||||
...selectTabState(global, tabId).focusedMessage,
|
...selectTabState(global, tabId).focusedMessage,
|
||||||
chatId,
|
...update,
|
||||||
threadId,
|
|
||||||
messageId,
|
|
||||||
noHighlight,
|
|
||||||
isResizingContainer,
|
|
||||||
quote,
|
|
||||||
quoteOffset,
|
|
||||||
scrollTargetPosition,
|
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
}
|
}
|
||||||
@ -789,18 +763,6 @@ export function deleteSponsoredMessage<T extends GlobalState>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateFocusDirection<T extends GlobalState>(
|
|
||||||
global: T, direction?: FocusDirection,
|
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
|
||||||
): T {
|
|
||||||
return updateTabState(global, {
|
|
||||||
focusedMessage: {
|
|
||||||
...selectTabState(global, tabId).focusedMessage,
|
|
||||||
direction,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enterMessageSelectMode<T extends GlobalState>(
|
export function enterMessageSelectMode<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type {
|
|
||||||
ApiChat, ApiChatFullInfo, ApiChatType,
|
|
||||||
} from '../../api/types';
|
|
||||||
import type { ChatListType } from '../../types';
|
import type { ChatListType } from '../../types';
|
||||||
import type { GlobalState, TabArgs } from '../types';
|
import type { GlobalState, TabArgs } from '../types';
|
||||||
|
import {
|
||||||
|
type ApiChat, type ApiChatFullInfo, type ApiChatType,
|
||||||
|
} from '../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SAVED_FOLDER_ID, SERVICE_NOTIFICATIONS_USER_ID,
|
ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SAVED_FOLDER_ID, SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
|
|||||||
@ -469,6 +469,7 @@ export interface ActionPayloads {
|
|||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: ThreadId;
|
threadId?: ThreadId;
|
||||||
shouldForceRender?: boolean;
|
shouldForceRender?: boolean;
|
||||||
|
forceLastSlice?: boolean;
|
||||||
onLoaded?: NoneToVoidFunction;
|
onLoaded?: NoneToVoidFunction;
|
||||||
onError?: NoneToVoidFunction;
|
onError?: NoneToVoidFunction;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
@ -1024,8 +1025,8 @@ export interface ActionPayloads {
|
|||||||
scrollTargetPosition?: ScrollTargetPosition;
|
scrollTargetPosition?: ScrollTargetPosition;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
scrollMessageListToBottom: WithTabId | undefined;
|
||||||
|
|
||||||
focusLastMessage: WithTabId | undefined;
|
|
||||||
updateDraftReplyInfo: Partial<ApiInputMessageReplyInfo> & WithTabId;
|
updateDraftReplyInfo: Partial<ApiInputMessageReplyInfo> & WithTabId;
|
||||||
resetDraftReplyInfo: WithTabId | undefined;
|
resetDraftReplyInfo: WithTabId | undefined;
|
||||||
updateDraftSuggestedPostInfo: Partial<ApiInputSuggestedPostInfo> & WithTabId;
|
updateDraftSuggestedPostInfo: Partial<ApiInputSuggestedPostInfo> & WithTabId;
|
||||||
|
|||||||
@ -16,7 +16,7 @@ function useMessageResizeObserver(
|
|||||||
shouldFocusOnResize = false,
|
shouldFocusOnResize = false,
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
focusLastMessage,
|
scrollMessageListToBottom,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const messageHeightRef = useRef(0);
|
const messageHeightRef = useRef(0);
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ function useMessageResizeObserver(
|
|||||||
const previousScrollBottom = currentScrollBottom - resizeDiff;
|
const previousScrollBottom = currentScrollBottom - resizeDiff;
|
||||||
|
|
||||||
if (previousScrollBottom <= BOTTOM_FOCUS_SCROLL_THRESHOLD) {
|
if (previousScrollBottom <= BOTTOM_FOCUS_SCROLL_THRESHOLD) {
|
||||||
focusLastMessage();
|
scrollMessageListToBottom();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user