History support and animation when navigating between chats (#1289)
This commit is contained in:
parent
e69ea676f3
commit
7574b6445b
@ -167,7 +167,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}, [animationLevel, orderDiff, animationType]);
|
}, [animationLevel, orderDiff, animationType]);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
openChat({ id: chatId });
|
openChat({ id: chatId, shouldReplaceHistory: true });
|
||||||
|
|
||||||
if (isSelected && messageListType === 'thread') {
|
if (isSelected && messageListType === 'thread') {
|
||||||
focusLastMessage();
|
focusLastMessage();
|
||||||
|
|||||||
@ -154,7 +154,7 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
|||||||
if (!digit) return;
|
if (!digit) return;
|
||||||
|
|
||||||
if (digit === SAVED_MESSAGES_HOTKEY) {
|
if (digit === SAVED_MESSAGES_HOTKEY) {
|
||||||
openChat({ id: currentUserId });
|
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -173,7 +173,7 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const position = Number(digit) - 1;
|
const position = Number(digit) - 1;
|
||||||
if (position > orderedIds.length - 1) return;
|
if (position > orderedIds.length - 1) return;
|
||||||
|
|
||||||
openChat({ id: orderedIds[position] });
|
openChat({ id: orderedIds[position], shouldReplaceHistory: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.altKey) {
|
if (e.altKey) {
|
||||||
|
|||||||
@ -51,7 +51,7 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(id: number) => {
|
(id: number) => {
|
||||||
openChat({ id });
|
openChat({ id, shouldReplaceHistory: true });
|
||||||
},
|
},
|
||||||
[openChat],
|
[openChat],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -138,7 +138,7 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}, [searchQuery, onSearchQuery]);
|
}, [searchQuery, onSearchQuery]);
|
||||||
|
|
||||||
const handleSelectSaved = useCallback(() => {
|
const handleSelectSaved = useCallback(() => {
|
||||||
openChat({ id: currentUserId });
|
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
||||||
}, [currentUserId, openChat]);
|
}, [currentUserId, openChat]);
|
||||||
|
|
||||||
const handleDarkModeToggle = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
const handleDarkModeToggle = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
||||||
|
|||||||
@ -80,7 +80,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const handleChatClick = useCallback(
|
const handleChatClick = useCallback(
|
||||||
(id: number) => {
|
(id: number) => {
|
||||||
openChat({ id });
|
openChat({ id, shouldReplaceHistory: true });
|
||||||
|
|
||||||
if (id !== currentUserId) {
|
if (id !== currentUserId) {
|
||||||
addRecentlyFoundChatId({ id });
|
addRecentlyFoundChatId({ id });
|
||||||
|
|||||||
@ -60,7 +60,7 @@ const RecentContacts: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(id: number) => {
|
(id: number) => {
|
||||||
openChat({ id });
|
openChat({ id, shouldReplaceHistory: true });
|
||||||
onReset();
|
onReset();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
addRecentlyFoundChatId({ id });
|
addRecentlyFoundChatId({ id });
|
||||||
|
|||||||
@ -82,6 +82,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.messages-layout {
|
.messages-layout {
|
||||||
|
--slide-transition: 450ms cubic-bezier(0.25, 1, 0.5, 1);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, {
|
|||||||
import { withGlobal } from '../../lib/teact/teactn';
|
import { withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiChatBannedRights, MAIN_THREAD_ID } from '../../api/types';
|
import { ApiChatBannedRights, MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { GlobalActions, MessageListType } from '../../global/types';
|
import { GlobalActions, MessageListType, MessageList as GlobalMessageList } from '../../global/types';
|
||||||
import { ThemeKey } from '../../types';
|
import { ThemeKey } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -36,7 +36,6 @@ import {
|
|||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
selectThreadOriginChat,
|
|
||||||
} from '../../modules/selectors';
|
} from '../../modules/selectors';
|
||||||
import { getCanPostInChat, getMessageSendingRestrictionReason, isChatPrivate } from '../../modules/helpers';
|
import { getCanPostInChat, getMessageSendingRestrictionReason, isChatPrivate } from '../../modules/helpers';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
@ -48,6 +47,7 @@ import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
|||||||
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
|
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||||
|
import { createMessageHash } from '../../util/routing';
|
||||||
|
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
import MiddleHeader from './MiddleHeader';
|
import MiddleHeader from './MiddleHeader';
|
||||||
@ -83,8 +83,9 @@ type StateProps = {
|
|||||||
isMobileSearchActive?: boolean;
|
isMobileSearchActive?: boolean;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
animationLevel?: number;
|
animationLevel?: number;
|
||||||
originChatId?: number;
|
|
||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
|
currentTransitionKey: number;
|
||||||
|
messageLists?: GlobalMessageList[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, (
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
@ -103,7 +104,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
messageListType,
|
messageListType,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
isPinnedMessageList,
|
isPinnedMessageList,
|
||||||
isScheduledMessageList,
|
messageLists,
|
||||||
canPost,
|
canPost,
|
||||||
currentUserBannedRights,
|
currentUserBannedRights,
|
||||||
defaultBannedRights,
|
defaultBannedRights,
|
||||||
@ -119,8 +120,8 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
isMobileSearchActive,
|
isMobileSearchActive,
|
||||||
isSelectModeActive,
|
isSelectModeActive,
|
||||||
animationLevel,
|
animationLevel,
|
||||||
originChatId,
|
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
|
currentTransitionKey,
|
||||||
openChat,
|
openChat,
|
||||||
unpinAllMessages,
|
unpinAllMessages,
|
||||||
loadUser,
|
loadUser,
|
||||||
@ -260,26 +261,12 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const closeChat = () => {
|
const closeChat = () => {
|
||||||
if (renderingThreadId !== MAIN_THREAD_ID) {
|
openChat({ id: undefined }, true);
|
||||||
openChat({ id: originChatId, threadId: MAIN_THREAD_ID }, true);
|
|
||||||
} else if (isPinnedMessageList || isScheduledMessageList) {
|
|
||||||
openChat({ id: chatId, type: 'thread' });
|
|
||||||
} else {
|
|
||||||
openChat({ id: undefined }, true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useHistoryBack(renderingChatId && renderingThreadId, closeChat, openChat, {
|
useHistoryBack(renderingChatId && renderingThreadId,
|
||||||
id: chatId,
|
closeChat, undefined, undefined, undefined,
|
||||||
threadId: MAIN_THREAD_ID,
|
messageLists ? messageLists.map(createMessageHash) : []);
|
||||||
});
|
|
||||||
|
|
||||||
const isDiscussion = renderingChatId && renderingThreadId !== MAIN_THREAD_ID;
|
|
||||||
|
|
||||||
useHistoryBack(isDiscussion || isPinnedMessageList || isScheduledMessageList, closeChat, openChat, {
|
|
||||||
id: chatId,
|
|
||||||
threadId: renderingThreadId,
|
|
||||||
});
|
|
||||||
|
|
||||||
useHistoryBack(isMobileSearchActive, closeLocalTextSearch);
|
useHistoryBack(isMobileSearchActive, closeLocalTextSearch);
|
||||||
useHistoryBack(isSelectModeActive, exitMessageSelectMode);
|
useHistoryBack(isSelectModeActive, exitMessageSelectMode);
|
||||||
@ -320,7 +307,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
|||||||
/>
|
/>
|
||||||
<Transition
|
<Transition
|
||||||
name={shouldSkipHistoryAnimations ? 'none' : animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'}
|
name={shouldSkipHistoryAnimations ? 'none' : animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'}
|
||||||
activeKey={renderingMessageListType === 'thread' && renderingThreadId === MAIN_THREAD_ID ? 1 : 2}
|
activeKey={currentTransitionKey}
|
||||||
shouldCleanup
|
shouldCleanup
|
||||||
>
|
>
|
||||||
{() => (
|
{() => (
|
||||||
@ -408,6 +395,7 @@ export default memo(withGlobal(
|
|||||||
isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor,
|
isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor,
|
||||||
} = global.settings.themes[theme] || {};
|
} = global.settings.themes[theme] || {};
|
||||||
|
|
||||||
|
const { messageLists } = global.messages;
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
const { isLeftColumnShown, chats: { listIds } } = global;
|
const { isLeftColumnShown, chats: { listIds } } = global;
|
||||||
|
|
||||||
@ -422,6 +410,7 @@ export default memo(withGlobal(
|
|||||||
isMobileSearchActive: Boolean(IS_SINGLE_COLUMN_LAYOUT && selectCurrentTextSearch(global)),
|
isMobileSearchActive: Boolean(IS_SINGLE_COLUMN_LAYOUT && selectCurrentTextSearch(global)),
|
||||||
isSelectModeActive: selectIsInSelectMode(global),
|
isSelectModeActive: selectIsInSelectMode(global),
|
||||||
animationLevel: global.settings.byKey.animationLevel,
|
animationLevel: global.settings.byKey.animationLevel,
|
||||||
|
currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!currentMessageList || !listIds.active) {
|
if (!currentMessageList || !listIds.active) {
|
||||||
@ -437,14 +426,12 @@ export default memo(withGlobal(
|
|||||||
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
|
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
|
||||||
const isPinnedMessageList = messageListType === 'pinned';
|
const isPinnedMessageList = messageListType === 'pinned';
|
||||||
const isScheduledMessageList = messageListType === 'scheduled';
|
const isScheduledMessageList = messageListType === 'scheduled';
|
||||||
const originChat = selectThreadOriginChat(global, chatId, threadId);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
messageListType,
|
messageListType,
|
||||||
originChatId: originChat ? originChat.id : chatId,
|
|
||||||
isPrivate: isChatPrivate(chatId),
|
isPrivate: isChatPrivate(chatId),
|
||||||
canPost: !isPinnedMessageList && (!chat || canPost) && !isBotNotStarted,
|
canPost: !isPinnedMessageList && (!chat || canPost) && !isBotNotStarted,
|
||||||
isPinnedMessageList,
|
isPinnedMessageList,
|
||||||
@ -458,6 +445,7 @@ export default memo(withGlobal(
|
|||||||
),
|
),
|
||||||
pinnedMessagesCount: pinnedIds ? pinnedIds.length : 0,
|
pinnedMessagesCount: pinnedIds ? pinnedIds.length : 0,
|
||||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||||
|
messageLists,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
|
|||||||
@ -36,7 +36,6 @@ import {
|
|||||||
selectAllowedMessageActions,
|
selectAllowedMessageActions,
|
||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectThreadTopMessageId,
|
selectThreadTopMessageId,
|
||||||
selectThreadOriginChat,
|
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
@ -88,7 +87,6 @@ type StateProps = {
|
|||||||
isRightColumnShown?: boolean;
|
isRightColumnShown?: boolean;
|
||||||
audioMessage?: ApiMessage;
|
audioMessage?: ApiMessage;
|
||||||
chatsById?: Record<number, ApiChat>;
|
chatsById?: Record<number, ApiChat>;
|
||||||
originChatId: number;
|
|
||||||
messagesCount?: number;
|
messagesCount?: number;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
isChatWithBot?: boolean;
|
isChatWithBot?: boolean;
|
||||||
@ -96,11 +94,12 @@ type StateProps = {
|
|||||||
notifySettings: NotifySettings;
|
notifySettings: NotifySettings;
|
||||||
notifyExceptions?: Record<number, NotifyException>;
|
notifyExceptions?: Record<number, NotifyException>;
|
||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
|
currentTransitionKey: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, (
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
'openChatWithInfo' | 'pinMessage' | 'focusMessage' | 'openChat' | 'loadPinnedMessages' | 'toggleLeftColumn' |
|
'openChatWithInfo' | 'pinMessage' | 'focusMessage' | 'openChat' | 'openPreviousChat' | 'loadPinnedMessages' |
|
||||||
'exitMessageSelectMode'
|
'toggleLeftColumn' | 'exitMessageSelectMode'
|
||||||
)>;
|
)>;
|
||||||
|
|
||||||
const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
@ -119,7 +118,6 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
audioMessage,
|
audioMessage,
|
||||||
chat,
|
chat,
|
||||||
chatsById,
|
chatsById,
|
||||||
originChatId,
|
|
||||||
messagesCount,
|
messagesCount,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isChatWithBot,
|
isChatWithBot,
|
||||||
@ -127,10 +125,12 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
notifySettings,
|
notifySettings,
|
||||||
notifyExceptions,
|
notifyExceptions,
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
|
currentTransitionKey,
|
||||||
openChatWithInfo,
|
openChatWithInfo,
|
||||||
pinMessage,
|
pinMessage,
|
||||||
focusMessage,
|
focusMessage,
|
||||||
openChat,
|
openChat,
|
||||||
|
openPreviousChat,
|
||||||
loadPinnedMessages,
|
loadPinnedMessages,
|
||||||
toggleLeftColumn,
|
toggleLeftColumn,
|
||||||
exitMessageSelectMode,
|
exitMessageSelectMode,
|
||||||
@ -194,7 +194,8 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
messageInput.blur();
|
messageInput.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (threadId === MAIN_THREAD_ID && messageListType === 'thread') {
|
|
||||||
|
if (threadId === MAIN_THREAD_ID && messageListType === 'thread' && currentTransitionKey === 0) {
|
||||||
if (IS_SINGLE_COLUMN_LAYOUT || shouldShowCloseButton) {
|
if (IS_SINGLE_COLUMN_LAYOUT || shouldShowCloseButton) {
|
||||||
e.stopPropagation(); // Stop propagation to prevent chat re-opening on tablets
|
e.stopPropagation(); // Stop propagation to prevent chat re-opening on tablets
|
||||||
openChat({ id: undefined });
|
openChat({ id: undefined });
|
||||||
@ -209,10 +210,10 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
exitMessageSelectMode();
|
exitMessageSelectMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
openChat({ id: originChatId, threadId: MAIN_THREAD_ID });
|
openPreviousChat();
|
||||||
}, [
|
}, [
|
||||||
openChat, originChatId, threadId, messageListType, toggleLeftColumn, isSelectModeActive, exitMessageSelectMode,
|
threadId, messageListType, currentTransitionKey, isSelectModeActive, openPreviousChat, shouldShowCloseButton,
|
||||||
shouldShowCloseButton,
|
openChat, toggleLeftColumn, exitMessageSelectMode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const unreadCount = useMemo(() => {
|
const unreadCount = useMemo(() => {
|
||||||
@ -339,7 +340,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
function renderMainThreadInfo() {
|
function renderMainThreadInfo() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLeftColumnHideable && renderBackButton(shouldShowCloseButton, unreadCount)}
|
{(isLeftColumnHideable || currentTransitionKey > 0) && renderBackButton(shouldShowCloseButton, unreadCount)}
|
||||||
<div className="chat-info-wrapper" onClick={handleHeaderClick}>
|
<div className="chat-info-wrapper" onClick={handleHeaderClick}>
|
||||||
{isChatPrivate(chatId) ? (
|
{isChatPrivate(chatId) ? (
|
||||||
<PrivateChatInfo
|
<PrivateChatInfo
|
||||||
@ -390,7 +391,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
<div className="MiddleHeader" ref={componentRef}>
|
<div className="MiddleHeader" ref={componentRef}>
|
||||||
<Transition
|
<Transition
|
||||||
name={shouldSkipHistoryAnimations ? 'none' : 'slide-fade'}
|
name={shouldSkipHistoryAnimations ? 'none' : 'slide-fade'}
|
||||||
activeKey={messageListType === 'thread' ? threadId : 1}
|
activeKey={currentTransitionKey}
|
||||||
>
|
>
|
||||||
{renderInfo}
|
{renderInfo}
|
||||||
</Transition>
|
</Transition>
|
||||||
@ -439,8 +440,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
? selectChatMessage(global, audioChatId, audioMessageId)
|
? selectChatMessage(global, audioChatId, audioMessageId)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const originChat = selectThreadOriginChat(global, chatId, threadId);
|
|
||||||
|
|
||||||
let messagesCount: number | undefined;
|
let messagesCount: number | undefined;
|
||||||
if (messageListType === 'pinned') {
|
if (messageListType === 'pinned') {
|
||||||
const pinnedIds = selectPinnedIds(global, chatId);
|
const pinnedIds = selectPinnedIds(global, chatId);
|
||||||
@ -463,7 +462,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
audioMessage,
|
audioMessage,
|
||||||
chat,
|
chat,
|
||||||
chatsById,
|
chatsById,
|
||||||
originChatId: originChat ? originChat.id : chatId,
|
|
||||||
messagesCount,
|
messagesCount,
|
||||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||||
isChatWithBot: chat && selectIsChatWithBot(global, chat),
|
isChatWithBot: chat && selectIsChatWithBot(global, chat),
|
||||||
@ -471,6 +469,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
notifySettings: selectNotifySettings(global),
|
notifySettings: selectNotifySettings(global),
|
||||||
notifyExceptions: selectNotifyExceptions(global),
|
notifyExceptions: selectNotifyExceptions(global),
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
|
currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
const messagesById = selectChatMessages(global, chatId);
|
const messagesById = selectChatMessages(global, chatId);
|
||||||
@ -514,6 +513,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
'pinMessage',
|
'pinMessage',
|
||||||
'focusMessage',
|
'focusMessage',
|
||||||
'openChat',
|
'openChat',
|
||||||
|
'openPreviousChat',
|
||||||
'loadPinnedMessages',
|
'loadPinnedMessages',
|
||||||
'toggleLeftColumn',
|
'toggleLeftColumn',
|
||||||
'exitMessageSelectMode',
|
'exitMessageSelectMode',
|
||||||
|
|||||||
@ -64,7 +64,17 @@ const Modal: FC<OwnProps & StateProps> = ({
|
|||||||
: undefined), [isOpen, onClose, onEnter]);
|
: undefined), [isOpen, onClose, onEnter]);
|
||||||
useEffect(() => (isOpen && modalRef.current ? trapFocus(modalRef.current) : undefined), [isOpen]);
|
useEffect(() => (isOpen && modalRef.current ? trapFocus(modalRef.current) : undefined), [isOpen]);
|
||||||
|
|
||||||
useHistoryBack(isOpen, onClose);
|
const { forceClose } = useHistoryBack(isOpen, onClose);
|
||||||
|
|
||||||
|
// For modals that are closed by unmounting without changing `isOpen` to `false`
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (isOpen) {
|
||||||
|
forceClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffectWithPrevDeps(([prevIsOpen]) => {
|
useEffectWithPrevDeps(([prevIsOpen]) => {
|
||||||
document.body.classList.toggle('has-open-dialog', isOpen);
|
document.body.classList.toggle('has-open-dialog', isOpen);
|
||||||
|
|||||||
@ -229,7 +229,7 @@ function reduceMessages(global: GlobalState): GlobalState['messages'] {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
byChatId,
|
byChatId,
|
||||||
messageLists: !currentMessageList || IS_SINGLE_COLUMN_LAYOUT ? undefined : [{
|
messageLists: !currentMessageList || IS_SINGLE_COLUMN_LAYOUT ? [] : [{
|
||||||
...currentMessageList,
|
...currentMessageList,
|
||||||
threadId: MAIN_THREAD_ID,
|
threadId: MAIN_THREAD_ID,
|
||||||
type: 'thread',
|
type: 'thread',
|
||||||
|
|||||||
@ -33,6 +33,7 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
|
|
||||||
messages: {
|
messages: {
|
||||||
byChatId: {},
|
byChatId: {},
|
||||||
|
messageLists: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
scheduledMessages: {
|
scheduledMessages: {
|
||||||
|
|||||||
@ -46,6 +46,12 @@ import {
|
|||||||
|
|
||||||
export type MessageListType = 'thread' | 'pinned' | 'scheduled';
|
export type MessageListType = 'thread' | 'pinned' | 'scheduled';
|
||||||
|
|
||||||
|
export interface MessageList {
|
||||||
|
chatId: number;
|
||||||
|
threadId: number;
|
||||||
|
type: MessageListType;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Thread {
|
export interface Thread {
|
||||||
listedIds?: number[];
|
listedIds?: number[];
|
||||||
outlyingIds?: number[];
|
outlyingIds?: number[];
|
||||||
@ -134,11 +140,7 @@ export type GlobalState = {
|
|||||||
byId: Record<number, ApiMessage>;
|
byId: Record<number, ApiMessage>;
|
||||||
threadsById: Record<number, Thread>;
|
threadsById: Record<number, Thread>;
|
||||||
}>;
|
}>;
|
||||||
messageLists?: {
|
messageLists: MessageList[];
|
||||||
chatId: number;
|
|
||||||
threadId: number;
|
|
||||||
type: MessageListType;
|
|
||||||
}[];
|
|
||||||
contentToBeScheduled?: {
|
contentToBeScheduled?: {
|
||||||
gif?: ApiVideo;
|
gif?: ApiVideo;
|
||||||
sticker?: ApiSticker;
|
sticker?: ApiSticker;
|
||||||
@ -439,7 +441,7 @@ export type ActionTypes = (
|
|||||||
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
||||||
'updateChat' | 'toggleSignatures' | 'loadGroupsForDiscussion' | 'linkDiscussionGroup' | 'unlinkDiscussionGroup' |
|
'updateChat' | 'toggleSignatures' | 'loadGroupsForDiscussion' | 'linkDiscussionGroup' | 'unlinkDiscussionGroup' |
|
||||||
'loadProfilePhotos' | 'loadMoreMembers' | 'setActiveChatFolder' | 'openNextChat' |
|
'loadProfilePhotos' | 'loadMoreMembers' | 'setActiveChatFolder' | 'openNextChat' |
|
||||||
'addChatMembers' | 'deleteChatMember' |
|
'addChatMembers' | 'deleteChatMember' | 'openPreviousChat' |
|
||||||
// messages
|
// messages
|
||||||
'loadViewportMessages' | 'selectMessage' | 'sendMessage' | 'cancelSendingMessage' | 'pinMessage' | 'deleteMessages' |
|
'loadViewportMessages' | 'selectMessage' | 'sendMessage' | 'cancelSendingMessage' | 'pinMessage' | 'deleteMessages' |
|
||||||
'markMessageListRead' | 'markMessagesRead' | 'loadMessage' | 'focusMessage' | 'focusLastMessage' | 'sendPollVote' |
|
'markMessageListRead' | 'markMessagesRead' | 'loadMessage' | 'focusMessage' | 'focusLastMessage' | 'sendPollVote' |
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useRef } from '../lib/teact/teact';
|
import { useCallback, useEffect, useRef } from '../lib/teact/teact';
|
||||||
|
|
||||||
import { IS_IOS } from '../util/environment';
|
import { IS_IOS } from '../util/environment';
|
||||||
import usePrevious from './usePrevious';
|
import usePrevious from './usePrevious';
|
||||||
import { getDispatch } from '../lib/teact/teactn';
|
import { getDispatch } from '../lib/teact/teactn';
|
||||||
|
import { areSortedArraysEqual } from '../util/iteratees';
|
||||||
|
|
||||||
// Carefully selected by swiping and observing visual changes
|
// Carefully selected by swiping and observing visual changes
|
||||||
// TODO: may be different on other devices such as iPad, maybe take dpi into account?
|
// TODO: may be different on other devices such as iPad, maybe take dpi into account?
|
||||||
@ -61,48 +62,19 @@ export default function useHistoryBack(
|
|||||||
onForward?: (state: any) => void,
|
onForward?: (state: any) => void,
|
||||||
currentState?: any,
|
currentState?: any,
|
||||||
shouldReplaceNext = false,
|
shouldReplaceNext = false,
|
||||||
|
hashes?: string[],
|
||||||
) {
|
) {
|
||||||
const indexRef = useRef(-1);
|
const indexRef = useRef(-1);
|
||||||
const isForward = useRef(false);
|
const isForward = useRef(false);
|
||||||
const prevIsActive = usePrevious(isActive);
|
const prevIsActive = usePrevious(isActive);
|
||||||
const isClosed = useRef(true);
|
const isClosed = useRef(true);
|
||||||
|
const indexHashRef = useRef<{ index: number; hash: string }[]>([]);
|
||||||
|
const prevHashes = usePrevious(hashes);
|
||||||
|
const isHashChangedFromEvent = useRef<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleChange = useCallback((isForceClose = false) => {
|
||||||
const handlePopState = (event: PopStateEvent) => {
|
if (!hashes) {
|
||||||
if (historyState.isHistoryAltered) {
|
if (isActive && !isForceClose) {
|
||||||
setTimeout(() => {
|
|
||||||
historyState.isHistoryAltered = false;
|
|
||||||
}, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { index: i } = event.state;
|
|
||||||
const index = i || 0;
|
|
||||||
|
|
||||||
const prev = historyState.currentIndexes[historyState.currentIndexes.indexOf(indexRef.current) - 1];
|
|
||||||
|
|
||||||
if (historyState.isDisabled) return;
|
|
||||||
|
|
||||||
if (!isClosed.current && (index === 0 || index === prev)) {
|
|
||||||
historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(indexRef.current), 1);
|
|
||||||
|
|
||||||
if (onBack) {
|
|
||||||
if (historyState.isEdge) {
|
|
||||||
getDispatch().disableHistoryAnimations();
|
|
||||||
}
|
|
||||||
onBack(!historyState.isEdge);
|
|
||||||
isClosed.current = true;
|
|
||||||
}
|
|
||||||
} else if (index === indexRef.current && isClosed.current && onForward) {
|
|
||||||
isForward.current = true;
|
|
||||||
if (historyState.isEdge) {
|
|
||||||
getDispatch().disableHistoryAnimations();
|
|
||||||
}
|
|
||||||
onForward(event.state.state);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!historyState.isDisabled && prevIsActive !== isActive) {
|
|
||||||
if (isActive) {
|
|
||||||
isClosed.current = false;
|
isClosed.current = false;
|
||||||
|
|
||||||
if (isForward.current) {
|
if (isForward.current) {
|
||||||
@ -126,6 +98,7 @@ export default function useHistoryBack(
|
|||||||
state: currentState,
|
state: currentState,
|
||||||
}, '');
|
}, '');
|
||||||
|
|
||||||
|
|
||||||
indexRef.current = index;
|
indexRef.current = index;
|
||||||
|
|
||||||
if (shouldReplaceNext) {
|
if (shouldReplaceNext) {
|
||||||
@ -133,8 +106,10 @@ export default function useHistoryBack(
|
|||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
} else if (!isClosed.current) {
|
}
|
||||||
if (indexRef.current === historyState.currentIndex || !shouldReplaceNext) {
|
|
||||||
|
if ((isForceClose || !isActive) && !isClosed.current) {
|
||||||
|
if ((indexRef.current === historyState.currentIndex || !shouldReplaceNext)) {
|
||||||
historyState.isHistoryAltered = true;
|
historyState.isHistoryAltered = true;
|
||||||
window.history.back();
|
window.history.back();
|
||||||
|
|
||||||
@ -146,9 +121,127 @@ export default function useHistoryBack(
|
|||||||
|
|
||||||
isClosed.current = true;
|
isClosed.current = true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const prev = prevHashes || [];
|
||||||
|
if (prev.length < hashes.length) {
|
||||||
|
const index = ++historyState.currentIndex;
|
||||||
|
historyState.currentIndexes.push(index);
|
||||||
|
|
||||||
|
window.history.pushState({
|
||||||
|
index,
|
||||||
|
state: currentState,
|
||||||
|
}, '', `#${hashes[hashes.length - 1]}`);
|
||||||
|
|
||||||
|
indexHashRef.current.push({
|
||||||
|
index,
|
||||||
|
hash: hashes[hashes.length - 1],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const delta = prev.length - hashes.length;
|
||||||
|
if (isHashChangedFromEvent.current) {
|
||||||
|
isHashChangedFromEvent.current = false;
|
||||||
|
} else {
|
||||||
|
if (hashes.length !== indexHashRef.current.length) {
|
||||||
|
if (delta > 0) {
|
||||||
|
const last = indexHashRef.current[indexHashRef.current.length - delta - 1];
|
||||||
|
let realDelta = delta;
|
||||||
|
if (last) {
|
||||||
|
const indexLast = historyState.currentIndexes.findIndex(
|
||||||
|
(l) => l === last.index,
|
||||||
|
);
|
||||||
|
realDelta = historyState.currentIndexes.length - indexLast - 1;
|
||||||
|
}
|
||||||
|
historyState.isHistoryAltered = true;
|
||||||
|
window.history.go(-realDelta);
|
||||||
|
const removed = indexHashRef.current.splice(indexHashRef.current.length - delta - 1, delta);
|
||||||
|
removed.forEach(({ index }) => {
|
||||||
|
historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(index), 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashes.length > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const index = ++historyState.currentIndex;
|
||||||
|
historyState.currentIndexes[historyState.currentIndexes.length - 1] = index;
|
||||||
|
|
||||||
|
window.history.replaceState({
|
||||||
|
index,
|
||||||
|
state: currentState,
|
||||||
|
}, '', `#${hashes[hashes.length - 1]}`);
|
||||||
|
|
||||||
|
indexHashRef.current[indexHashRef.current.length - 1] = {
|
||||||
|
index,
|
||||||
|
hash: hashes[hashes.length - 1],
|
||||||
|
};
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [currentState, hashes, isActive, prevHashes, shouldReplaceNext]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePopState = (event: PopStateEvent) => {
|
||||||
|
if (historyState.isHistoryAltered) {
|
||||||
|
setTimeout(() => {
|
||||||
|
historyState.isHistoryAltered = false;
|
||||||
|
}, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { index: i } = event.state;
|
||||||
|
const index = i || 0;
|
||||||
|
try {
|
||||||
|
const currIndex = hashes ? indexHashRef.current[indexHashRef.current.length - 1].index : indexRef.current;
|
||||||
|
|
||||||
|
const prev = historyState.currentIndexes[historyState.currentIndexes.indexOf(currIndex) - 1];
|
||||||
|
|
||||||
|
if (historyState.isDisabled) return;
|
||||||
|
|
||||||
|
if ((!isClosed.current && (index === 0 || index === prev)) || (hashes && (index === 0 || index === prev))) {
|
||||||
|
if (hashes) {
|
||||||
|
isHashChangedFromEvent.current = true;
|
||||||
|
indexHashRef.current.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(currIndex), 1);
|
||||||
|
|
||||||
|
if (onBack) {
|
||||||
|
if (historyState.isEdge) {
|
||||||
|
getDispatch()
|
||||||
|
.disableHistoryAnimations();
|
||||||
|
}
|
||||||
|
onBack(!historyState.isEdge);
|
||||||
|
isClosed.current = true;
|
||||||
|
}
|
||||||
|
} else if (index === currIndex && isClosed.current && onForward && !hashes) {
|
||||||
|
isForward.current = true;
|
||||||
|
if (historyState.isEdge) {
|
||||||
|
getDispatch()
|
||||||
|
.disableHistoryAnimations();
|
||||||
|
}
|
||||||
|
onForward(event.state.state);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Forward navigation for hashed is not supported
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasChanged = hashes
|
||||||
|
? (!prevHashes || !areSortedArraysEqual(prevHashes, hashes))
|
||||||
|
: prevIsActive !== isActive;
|
||||||
|
|
||||||
|
if (!historyState.isDisabled && hasChanged) {
|
||||||
|
handleChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('popstate', handlePopState);
|
window.addEventListener('popstate', handlePopState);
|
||||||
return () => window.removeEventListener('popstate', handlePopState);
|
return () => window.removeEventListener('popstate', handlePopState);
|
||||||
}, [currentState, isActive, onBack, onForward, prevIsActive, shouldReplaceNext]);
|
}, [
|
||||||
|
currentState, handleChange, hashes, isActive, onBack, onForward, prevHashes, prevIsActive, shouldReplaceNext,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
forceClose: () => handleChange(true),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { closeLocalTextSearch } from './localSearch';
|
|||||||
|
|
||||||
addReducer('openChat', (global, actions, payload) => {
|
addReducer('openChat', (global, actions, payload) => {
|
||||||
const {
|
const {
|
||||||
id, threadId = -1, type = 'thread',
|
id, threadId = -1, type = 'thread', shouldReplaceHistory = false,
|
||||||
} = payload!;
|
} = payload!;
|
||||||
|
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
@ -37,7 +37,11 @@ addReducer('openChat', (global, actions, payload) => {
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateCurrentMessageList(global, id, threadId, type);
|
return updateCurrentMessageList(global, id, threadId, type, shouldReplaceHistory);
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('openPreviousChat', (global) => {
|
||||||
|
return updateCurrentMessageList(global, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
addReducer('openChatWithInfo', (global, actions, payload) => {
|
addReducer('openChatWithInfo', (global, actions, payload) => {
|
||||||
@ -80,5 +84,5 @@ addReducer('openNextChat', (global, actions, payload) => {
|
|||||||
}
|
}
|
||||||
const nextId = orderedIds[position + targetIndexDelta];
|
const nextId = orderedIds[position + targetIndexDelta];
|
||||||
|
|
||||||
actions.openChat({ id: nextId });
|
actions.openChat({ id: nextId, shouldReplaceHistory: true });
|
||||||
});
|
});
|
||||||
|
|||||||
@ -58,16 +58,14 @@ addReducer('closeManagement', (global): GlobalState | undefined => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
addReducer('openChat', (global, actions, payload) => {
|
addReducer('openChat', (global) => {
|
||||||
if (!IS_SINGLE_COLUMN_LAYOUT && !IS_TABLET_COLUMN_LAYOUT) {
|
if (!IS_SINGLE_COLUMN_LAYOUT && !IS_TABLET_COLUMN_LAYOUT) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = payload!;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
isLeftColumnShown: id === undefined,
|
isLeftColumnShown: global.messages.messageLists.length === 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { GlobalState, MessageListType, Thread } from '../../global/types';
|
import {
|
||||||
|
GlobalState, MessageList, MessageListType, Thread,
|
||||||
|
} from '../../global/types';
|
||||||
import { ApiMessage, ApiThreadInfo, MAIN_THREAD_ID } from '../../api/types';
|
import { ApiMessage, ApiThreadInfo, MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { FocusDirection } from '../../types';
|
import { FocusDirection } from '../../types';
|
||||||
|
|
||||||
@ -21,6 +23,8 @@ import {
|
|||||||
areSortedArraysEqual, omit, pickTruthy, unique,
|
areSortedArraysEqual, omit, pickTruthy, unique,
|
||||||
} from '../../util/iteratees';
|
} from '../../util/iteratees';
|
||||||
|
|
||||||
|
const TMP_CHAT_ID = -1;
|
||||||
|
|
||||||
type MessageStoreSections = {
|
type MessageStoreSections = {
|
||||||
byId: Record<number, ApiMessage>;
|
byId: Record<number, ApiMessage>;
|
||||||
threadsById: Record<number, Thread>;
|
threadsById: Record<number, Thread>;
|
||||||
@ -31,13 +35,30 @@ export function updateCurrentMessageList(
|
|||||||
chatId: number | undefined,
|
chatId: number | undefined,
|
||||||
threadId: number = MAIN_THREAD_ID,
|
threadId: number = MAIN_THREAD_ID,
|
||||||
type: MessageListType = 'thread',
|
type: MessageListType = 'thread',
|
||||||
|
shouldReplaceHistory?: boolean,
|
||||||
): GlobalState {
|
): GlobalState {
|
||||||
|
const { messageLists } = global.messages;
|
||||||
|
let newMessageLists: MessageList[] = messageLists;
|
||||||
|
if (shouldReplaceHistory) {
|
||||||
|
newMessageLists = chatId ? [{ chatId, threadId, type }] : [];
|
||||||
|
} else if (chatId) {
|
||||||
|
const last = messageLists[messageLists.length - 1];
|
||||||
|
if (!last || last.chatId !== chatId || last.threadId !== threadId || last.type !== type) {
|
||||||
|
if (last && last.chatId === TMP_CHAT_ID) {
|
||||||
|
newMessageLists = [...messageLists.slice(0, -1), { chatId, threadId, type }];
|
||||||
|
} else {
|
||||||
|
newMessageLists = [...messageLists, { chatId, threadId, type }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newMessageLists = messageLists.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
messages: {
|
messages: {
|
||||||
...global.messages,
|
...global.messages,
|
||||||
// TODO Support stack navigation
|
messageLists: newMessageLists,
|
||||||
messageLists: chatId ? [{ chatId, threadId, type }] : undefined,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const MESSAGE_EDIT_ALLOWED_TIME_MS = 172800000; // 48 hours
|
|||||||
export function selectCurrentMessageList(global: GlobalState) {
|
export function selectCurrentMessageList(global: GlobalState) {
|
||||||
const { messageLists } = global.messages;
|
const { messageLists } = global.messages;
|
||||||
|
|
||||||
if (messageLists && messageLists.length) {
|
if (messageLists.length) {
|
||||||
return messageLists[messageLists.length - 1];
|
return messageLists[messageLists.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,21 +22,13 @@ export default function fastSmoothScroll(
|
|||||||
forceDuration?: number,
|
forceDuration?: number,
|
||||||
forceCurrentContainerHeight?: boolean,
|
forceCurrentContainerHeight?: boolean,
|
||||||
) {
|
) {
|
||||||
|
const scrollFrom = calculateScrollFrom(container, element, maxDistance, forceDirection);
|
||||||
|
|
||||||
if (forceDirection === FocusDirection.Static) {
|
if (forceDirection === FocusDirection.Static) {
|
||||||
let block!: ScrollLogicalPosition;
|
scrollWithJs(container, element, scrollFrom, position, margin, 0);
|
||||||
|
|
||||||
if (position === 'centerOrTop') {
|
|
||||||
block = element.offsetHeight < container.offsetHeight ? 'center' : 'start';
|
|
||||||
} else {
|
|
||||||
block = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
element.scrollIntoView({ block });
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollFrom = calculateScrollFrom(container, element, maxDistance, forceDirection);
|
|
||||||
|
|
||||||
if (getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MIN) {
|
if (getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MIN) {
|
||||||
forceDuration = 0;
|
forceDuration = 0;
|
||||||
|
|||||||
19
src/util/routing.ts
Normal file
19
src/util/routing.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { MessageList, MessageListType } from '../global/types';
|
||||||
|
import { MAIN_THREAD_ID } from '../api/types';
|
||||||
|
|
||||||
|
export const createMessageHash = (messageList: MessageList): string => (
|
||||||
|
messageList.chatId.toString()
|
||||||
|
+ (messageList.type !== 'thread' ? `_${messageList.type}`
|
||||||
|
: (messageList.threadId !== -1 ? `_${messageList.threadId}` : ''))
|
||||||
|
);
|
||||||
|
|
||||||
|
export const parseMessageHash = (value: string): MessageList => {
|
||||||
|
const [chatId, typeOrThreadId] = value.split('_');
|
||||||
|
const isType = ['thread', 'pinned', 'scheduled'].includes(typeOrThreadId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chatId: Number(chatId),
|
||||||
|
type: !!typeOrThreadId && isType ? (typeOrThreadId as MessageListType) : 'thread',
|
||||||
|
threadId: !!typeOrThreadId && !isType ? Number(typeOrThreadId) : MAIN_THREAD_ID,
|
||||||
|
};
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user