Fix chat IDs being stuck in URL (#3105)

This commit is contained in:
Alexander Zinchuk 2023-05-02 15:22:55 +04:00
parent 61a26749d0
commit 56f41804c1
10 changed files with 100 additions and 56 deletions

View File

@ -28,6 +28,7 @@ import renderText from './helpers/renderText';
import useMedia from '../../hooks/useMedia';
import useMediaTransition from '../../hooks/useMediaTransition';
import useLang from '../../hooks/useLang';
import { useFastClick } from '../../hooks/useFastClick';
import OptimizedVideo from '../ui/OptimizedVideo';
@ -130,9 +131,11 @@ const Avatar: FC<OwnProps> = ({
if (isSavedMessages) {
content = (
<i
className={buildClassName(cn.icon,
className={buildClassName(
cn.icon,
'icon',
'icon-avatar-saved-messages')}
'icon-avatar-saved-messages',
)}
role="img"
aria-label={author}
/>
@ -140,9 +143,11 @@ const Avatar: FC<OwnProps> = ({
} else if (isDeleted) {
content = (
<i
className={buildClassName(cn.icon,
className={buildClassName(
cn.icon,
'icon',
'icon-avatar-deleted-account')}
'icon-avatar-deleted-account',
)}
role="img"
aria-label={author}
/>
@ -150,9 +155,11 @@ const Avatar: FC<OwnProps> = ({
} else if (isReplies) {
content = (
<i
className={buildClassName(cn.icon,
className={buildClassName(
cn.icon,
'icon',
'icon-reply-filled')}
'icon-reply-filled',
)}
role="img"
aria-label={author}
/>
@ -205,11 +212,12 @@ const Avatar: FC<OwnProps> = ({
);
const hasMedia = Boolean(isSavedMessages || imgBlobUrl);
const handleClick = useCallback((e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => {
const { handleClick, handleMouseDown } = useFastClick((e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => {
if (onClick) {
onClick(e, hasMedia);
}
}, [onClick, hasMedia]);
});
const senderId = (user || chat) && (user || chat)!.id;
@ -217,9 +225,10 @@ const Avatar: FC<OwnProps> = ({
<div
ref={ref}
className={fullClassName}
onClick={handleClick}
data-test-sender-id={IS_TEST ? senderId : undefined}
aria-label={typeof content === 'string' ? author : undefined}
onClick={handleClick}
onMouseDown={handleMouseDown}
>
{typeof content === 'string' ? renderText(content, [size === 'jumbo' ? 'hq_emoji' : 'emoji']) : content}
</div>

View File

@ -11,11 +11,11 @@ import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import type { FC } from '../../lib/teact/teact';
import type { ApiChat } from '../../api/types';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { getOrderedTopics } from '../../global/helpers';
import { getIsMobile } from '../../hooks/useAppLayout';
import useLang from '../../hooks/useLang';
import { useFastClick } from '../../hooks/useFastClick';
import { REM } from './helpers/mediaDimensions';
import renderText from './helpers/renderText';
@ -59,17 +59,22 @@ const ChatForumLastMessage: FC<OwnProps> = ({
const [isReversedCorner, setIsReversedCorner] = useState(false);
const [overwrittenWidth, setOverwrittenWidth] = useState<number | undefined>(undefined);
function handleOpenTopic(e: React.MouseEvent<HTMLDivElement>) {
const {
handleClick: handleOpenTopicClick,
handleMouseDown: handleOpenTopicMouseDown,
} = useFastClick((e: React.MouseEvent<HTMLDivElement>) => {
if (lastActiveTopic.unreadCount === 0) return;
e.stopPropagation();
e.preventDefault();
openChat({
id: chat.id,
threadId: lastActiveTopic.id,
shouldReplaceHistory: true,
noForumTopicPanel: getIsMobile(),
});
}
});
useEffect(() => {
const lastMessageElement = lastMessageRef.current;
@ -105,8 +110,8 @@ const ChatForumLastMessage: FC<OwnProps> = ({
lastActiveTopic.unreadCount && styles.unread,
)}
ref={mainColumnRef}
onMouseDown={IS_TOUCH_ENV ? undefined : handleOpenTopic}
onClick={IS_TOUCH_ENV ? handleOpenTopic : undefined}
onClick={handleOpenTopicClick}
onMouseDown={handleOpenTopicMouseDown}
>
<TopicIcon
topic={lastActiveTopic}
@ -144,8 +149,8 @@ const ChatForumLastMessage: FC<OwnProps> = ({
<div
className={buildClassName(styles.lastMessage, lastActiveTopic?.unreadCount && styles.unread)}
ref={lastMessageRef}
onMouseDown={IS_TOUCH_ENV ? undefined : handleOpenTopic}
onClick={IS_TOUCH_ENV ? handleOpenTopic : undefined}
onClick={handleOpenTopicClick}
onMouseDown={handleOpenTopicMouseDown}
>
{lastMessage}
{!overwrittenWidth && !isReversedCorner && (

View File

@ -1,5 +1,5 @@
import type { FC } from '../../lib/teact/teact';
import React, { useCallback, useRef } from '../../lib/teact/teact';
import React, { useRef } from '../../lib/teact/teact';
import type {
ApiUser, ApiMessage, ApiChat,
@ -18,7 +18,6 @@ import { getPictogramDimensions } from './helpers/mediaDimensions';
import buildClassName from '../../util/buildClassName';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { IS_TOUCH_ENV, MouseButton } from '../../util/windowEnvironment';
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
import useMedia from '../../hooks/useMedia';
import useThumbnail from '../../hooks/useThumbnail';
@ -29,6 +28,7 @@ import MessageSummary from './MessageSummary';
import MediaSpoiler from './MediaSpoiler';
import './EmbeddedMessage.scss';
import { useFastClick } from '../../hooks/useFastClick';
type OwnProps = {
className?: string;
@ -72,13 +72,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
const senderTitle = sender ? getSenderTitle(lang, sender) : message?.forwardInfo?.hiddenUserName;
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (e.type === 'mousedown' && e.button !== MouseButton.Main) {
return;
}
onClick?.();
}, [onClick]);
const { handleClick, handleMouseDown } = useFastClick(onClick);
return (
<div
@ -88,8 +82,8 @@ const EmbeddedMessage: FC<OwnProps> = ({
className,
sender && !noUserColors && `color-${getUserColorKey(sender)}`,
)}
onClick={message && IS_TOUCH_ENV ? handleClick : undefined}
onMouseDown={message && !IS_TOUCH_ENV ? handleClick : undefined}
onClick={message && handleClick}
onMouseDown={message && handleMouseDown}
>
{mediaThumbnail && renderPictogram(mediaThumbnail, mediaBlobUrl, isRoundVideo, isProtected, isSpoiler)}
<div className="message-text">

View File

@ -20,6 +20,7 @@ import { getOrderedTopics } from '../../../global/helpers';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { createLocationHash } from '../../../util/routing';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
@ -139,6 +140,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
useHistoryBack({
isActive: isVisible,
onBack: handleClose,
hash: chat ? createLocationHash(chat.id, 'thread', MAIN_THREAD_ID) : undefined,
});
useEffect(() => (isVisible ? captureEscKeyListener(handleClose) : undefined), [handleClose, isVisible]);

View File

@ -10,13 +10,14 @@ import {
getMessageMediaHash, getMessageSingleInlineButton,
} from '../../global/helpers';
import buildClassName from '../../util/buildClassName';
import { IS_TOUCH_ENV, MouseButton } from '../../util/windowEnvironment';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import useMedia from '../../hooks/useMedia';
import useThumbnail from '../../hooks/useThumbnail';
import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
import { useFastClick } from '../../hooks/useFastClick';
import useAsyncRendering from '../right/hooks/useAsyncRendering';
import RippleEffect from '../ui/RippleEffect';
@ -78,13 +79,7 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
const [noHoverColor, markNoHoverColor, unmarkNoHoverColor] = useFlag();
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (e.type === 'mousedown' && e.button !== MouseButton.Main) {
return;
}
onClick?.(e);
}, [onClick]);
const { handleClick, handleMouseDown } = useFastClick(onClick);
function renderPictogram(thumbDataUri?: string, blobUrl?: string, spoiler?: boolean) {
const { width, height } = getPictogramDimensions();
@ -148,8 +143,8 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
/>
<div
className={buildClassName(styles.pinnedMessage, noHoverColor && styles.noHover)}
onClick={IS_TOUCH_ENV ? handleClick : undefined}
onMouseDown={!IS_TOUCH_ENV ? handleClick : undefined}
onClick={handleClick}
onMouseDown={handleMouseDown}
dir={lang.isRtl ? 'rtl' : undefined}
>
<PinnedMessageNavigation

View File

@ -67,6 +67,7 @@ import GroupCallTopPane from '../calls/group/GroupCallTopPane';
import ChatReportPanel from './ChatReportPanel';
import './MiddleHeader.scss';
import { useFastClick } from '../../hooks/useFastClick';
const ANIMATION_DURATION = 350;
const BACK_BUTTON_INACTIVE_TIME = 450;
@ -178,9 +179,9 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const componentRef = useRef<HTMLDivElement>(null);
const shouldAnimateTools = useRef<boolean>(true);
const handleHeaderClick = useCallback(() => {
const { handleClick: handleHeaderClick, handleMouseDown: handleHeaderMouseDown } = useFastClick(() => {
openChatWithInfo({ id: chatId, threadId });
}, [openChatWithInfo, chatId, threadId]);
});
const handleUnpinMessage = useCallback((messageId: number) => {
pinMessage({ messageId, isUnpin: true });
@ -350,7 +351,11 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
return (
<>
{(isLeftColumnHideable || currentTransitionKey > 0) && renderBackButton(shouldShowCloseButton, true)}
<div className="chat-info-wrapper" onClick={handleHeaderClick}>
<div
className="chat-info-wrapper"
onClick={handleHeaderClick}
onMouseDown={handleHeaderMouseDown}
>
{isUserId(chatId) ? (
<PrivateChatInfo
key={chatId}

View File

@ -18,6 +18,7 @@ import MessageOutgoingStatus from '../../common/MessageOutgoingStatus';
import AnimatedCounter from '../../common/AnimatedCounter';
import './MessageMeta.scss';
import { useFastClick } from '../../../hooks/useFastClick';
type OwnProps = {
message: ApiMessage;
@ -53,13 +54,15 @@ const MessageMeta: FC<OwnProps> = ({
const lang = useLang();
const [isActivated, markActivated] = useFlag();
const handleClick = (e: React.MouseEvent) => {
const { handleClick, handleMouseDown } = useFastClick(onClick);
function handleImportedClick(e: React.MouseEvent) {
e.stopPropagation();
showNotification({
message: lang('ImportedInfo'),
});
};
}
function handleOpenThread(e: React.MouseEvent) {
e.stopPropagation();
@ -99,7 +102,8 @@ const MessageMeta: FC<OwnProps> = ({
<span
className={fullClassName}
dir={lang.isRtl ? 'rtl' : 'ltr'}
onClick={onClick}
onClick={handleClick}
onMouseDown={handleMouseDown}
data-ignore-on-paste
>
{isTranslated && (
@ -130,10 +134,10 @@ const MessageMeta: FC<OwnProps> = ({
<span className="message-time" title={title} onMouseEnter={markActivated}>
{message.forwardInfo?.isImported && (
<>
<span className="message-imported" onClick={handleClick}>
<span className="message-imported" onClick={handleImportedClick}>
{formatDateTimeToString(message.forwardInfo.date * 1000, lang.code, true)}
</span>
<span className="message-imported" onClick={handleClick}>{lang('ImportedMessage')}</span>
<span className="message-imported" onClick={handleImportedClick}>{lang('ImportedMessage')}</span>
</>
)}
{message.isEdited && `${lang('EditedMessage')} `}

View File

@ -10,6 +10,7 @@ import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
import useMenuPosition from '../../hooks/useMenuPosition';
import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
import { useFastClick } from '../../hooks/useFastClick';
import RippleEffect from './RippleEffect';
import Menu from './Menu';
@ -31,7 +32,9 @@ type MenuItemContextActionSeparator = {
key?: string;
};
export type MenuItemContextAction = MenuItemContextActionItem | MenuItemContextActionSeparator;
export type MenuItemContextAction =
MenuItemContextActionItem
| MenuItemContextActionSeparator;
interface OwnProps {
ref?: RefObject<HTMLDivElement>;
@ -62,6 +65,7 @@ interface OwnProps {
onSecondaryIconClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
onDragEnter?: (e: React.DragEvent<HTMLDivElement>) => void;
}
const ListItem: FC<OwnProps> = ({
ref,
buttonRef,
@ -162,15 +166,20 @@ const ListItem: FC<OwnProps> = ({
}
}, [allowDisabledClick, clickArg, disabled, markIsTouched, onClick, ripple, unmarkIsTouched, href]);
const handleSecondaryIconClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
const {
handleClick: handleSecondaryIconClick,
handleMouseDown: handleSecondaryIconMouseDown,
} = useFastClick((e: React.MouseEvent<HTMLButtonElement>) => {
if ((disabled && !allowDisabledClick) || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return;
e.stopPropagation();
if (onSecondaryIconClick) {
onSecondaryIconClick(e);
} else {
handleContextMenu(e);
}
};
});
const handleMouseDown = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
if (inactive || IS_TOUCH_ENV) {
@ -242,8 +251,8 @@ const ListItem: FC<OwnProps> = ({
round
color="translucent"
size="smaller"
onClick={IS_TOUCH_ENV ? handleSecondaryIconClick : undefined}
onMouseDown={!IS_TOUCH_ENV ? handleSecondaryIconClick : undefined}
onClick={handleSecondaryIconClick}
onMouseDown={handleSecondaryIconMouseDown}
>
<i className={`icon icon-${secondaryIcon}`} />
</Button>

View File

@ -9,17 +9,18 @@ import { requestForcedReflow, requestMutation } from '../../lib/fasterdom/faster
import type { FC } from '../../lib/teact/teact';
import type { MenuItemContextAction } from './ListItem';
import { IS_TOUCH_ENV, MouseButton } from '../../util/windowEnvironment';
import { MouseButton } from '../../util/windowEnvironment';
import forceReflow from '../../util/forceReflow';
import buildClassName from '../../util/buildClassName';
import renderText from '../common/helpers/renderText';
import useMenuPosition from '../../hooks/useMenuPosition';
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
import { useFastClick } from '../../hooks/useFastClick';
import Menu from './Menu';
import MenuItem from './MenuItem';
import MenuSeparator from './MenuSeparator';
import MenuSeparator from './MenuSeparator';
import './Tab.scss';
type OwnProps = {
@ -112,7 +113,7 @@ const Tab: FC<OwnProps> = ({
handleContextMenuHide, isContextMenuOpen,
} = useContextMenuHandlers(tabRef, !contextActions);
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
const { handleClick, handleMouseDown } = useFastClick((e: React.MouseEvent<HTMLDivElement>) => {
if (contextActions && (e.button === MouseButton.Secondary || !onClick)) {
handleBeforeContextMenu(e);
}
@ -122,7 +123,7 @@ const Tab: FC<OwnProps> = ({
}
onClick?.(clickArg!);
}, [clickArg, contextActions, handleBeforeContextMenu, onClick]);
});
const getTriggerElement = useCallback(() => tabRef.current, []);
@ -157,8 +158,8 @@ const Tab: FC<OwnProps> = ({
return (
<div
className={buildClassName('Tab', onClick && 'Tab--interactive', className)}
onClick={IS_TOUCH_ENV ? handleClick : undefined}
onMouseDown={!IS_TOUCH_ENV ? handleClick : undefined}
onClick={handleClick}
onMouseDown={handleMouseDown}
onContextMenu={handleContextMenu}
ref={tabRef}
>

20
src/hooks/useFastClick.ts Normal file
View File

@ -0,0 +1,20 @@
import type React from '../lib/teact/teact';
import { IS_TOUCH_ENV, MouseButton } from '../util/windowEnvironment';
type EventArg<E> = React.MouseEvent<E>;
type EventHandler<E> = (e: EventArg<E>) => void;
export function useFastClick<T extends HTMLDivElement | HTMLButtonElement>(callback?: EventHandler<T>) {
const wrapperHandler = callback ? (e: EventArg<T>) => {
if (e.type === 'mousedown' && e.button !== MouseButton.Main) {
return;
}
callback(e);
} : undefined;
return IS_TOUCH_ENV
? { handleClick: wrapperHandler }
: { handleMouseDown: wrapperHandler };
}