diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index 3d28e6a64..8ce096d90 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -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 = ({ if (isSavedMessages) { content = ( @@ -140,9 +143,11 @@ const Avatar: FC = ({ } else if (isDeleted) { content = ( @@ -150,9 +155,11 @@ const Avatar: FC = ({ } else if (isReplies) { content = ( @@ -205,11 +212,12 @@ const Avatar: FC = ({ ); const hasMedia = Boolean(isSavedMessages || imgBlobUrl); - const handleClick = useCallback((e: ReactMouseEvent) => { + + const { handleClick, handleMouseDown } = useFastClick((e: ReactMouseEvent) => { if (onClick) { onClick(e, hasMedia); } - }, [onClick, hasMedia]); + }); const senderId = (user || chat) && (user || chat)!.id; @@ -217,9 +225,10 @@ const Avatar: FC = ({
{typeof content === 'string' ? renderText(content, [size === 'jumbo' ? 'hq_emoji' : 'emoji']) : content}
diff --git a/src/components/common/ChatForumLastMessage.tsx b/src/components/common/ChatForumLastMessage.tsx index 7c1f5a78d..0ee7b4b58 100644 --- a/src/components/common/ChatForumLastMessage.tsx +++ b/src/components/common/ChatForumLastMessage.tsx @@ -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 = ({ const [isReversedCorner, setIsReversedCorner] = useState(false); const [overwrittenWidth, setOverwrittenWidth] = useState(undefined); - function handleOpenTopic(e: React.MouseEvent) { + const { + handleClick: handleOpenTopicClick, + handleMouseDown: handleOpenTopicMouseDown, + } = useFastClick((e: React.MouseEvent) => { 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 = ({ lastActiveTopic.unreadCount && styles.unread, )} ref={mainColumnRef} - onMouseDown={IS_TOUCH_ENV ? undefined : handleOpenTopic} - onClick={IS_TOUCH_ENV ? handleOpenTopic : undefined} + onClick={handleOpenTopicClick} + onMouseDown={handleOpenTopicMouseDown} > = ({
{lastMessage} {!overwrittenWidth && !isReversedCorner && ( diff --git a/src/components/common/EmbeddedMessage.tsx b/src/components/common/EmbeddedMessage.tsx index 3d32970df..29f38fdde 100644 --- a/src/components/common/EmbeddedMessage.tsx +++ b/src/components/common/EmbeddedMessage.tsx @@ -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 = ({ const senderTitle = sender ? getSenderTitle(lang, sender) : message?.forwardInfo?.hiddenUserName; - const handleClick = useCallback((e: React.MouseEvent) => { - if (e.type === 'mousedown' && e.button !== MouseButton.Main) { - return; - } - - onClick?.(); - }, [onClick]); + const { handleClick, handleMouseDown } = useFastClick(onClick); return (
= ({ 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)}
diff --git a/src/components/left/main/ForumPanel.tsx b/src/components/left/main/ForumPanel.tsx index d685d9b5b..fa63e6757 100644 --- a/src/components/left/main/ForumPanel.tsx +++ b/src/components/left/main/ForumPanel.tsx @@ -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 = ({ useHistoryBack({ isActive: isVisible, onBack: handleClose, + hash: chat ? createLocationHash(chat.id, 'thread', MAIN_THREAD_ID) : undefined, }); useEffect(() => (isVisible ? captureEscKeyListener(handleClose) : undefined), [handleClose, isVisible]); diff --git a/src/components/middle/HeaderPinnedMessage.tsx b/src/components/middle/HeaderPinnedMessage.tsx index 33ddb0d04..89854e415 100644 --- a/src/components/middle/HeaderPinnedMessage.tsx +++ b/src/components/middle/HeaderPinnedMessage.tsx @@ -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 = ({ const [noHoverColor, markNoHoverColor, unmarkNoHoverColor] = useFlag(); - const handleClick = useCallback((e: React.MouseEvent) => { - 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 = ({ />
= ({ const componentRef = useRef(null); const shouldAnimateTools = useRef(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 = ({ return ( <> {(isLeftColumnHideable || currentTransitionKey > 0) && renderBackButton(shouldShowCloseButton, true)} -
+
{isUserId(chatId) ? ( = ({ 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 = ({ {isTranslated && ( @@ -130,10 +134,10 @@ const MessageMeta: FC = ({ {message.forwardInfo?.isImported && ( <> - + {formatDateTimeToString(message.forwardInfo.date * 1000, lang.code, true)} - {lang('ImportedMessage')} + {lang('ImportedMessage')} )} {message.isEdited && `${lang('EditedMessage')} `} diff --git a/src/components/ui/ListItem.tsx b/src/components/ui/ListItem.tsx index 5a4245ff8..19feb8297 100644 --- a/src/components/ui/ListItem.tsx +++ b/src/components/ui/ListItem.tsx @@ -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; @@ -62,6 +65,7 @@ interface OwnProps { onSecondaryIconClick?: (e: React.MouseEvent) => void; onDragEnter?: (e: React.DragEvent) => void; } + const ListItem: FC = ({ ref, buttonRef, @@ -162,15 +166,20 @@ const ListItem: FC = ({ } }, [allowDisabledClick, clickArg, disabled, markIsTouched, onClick, ripple, unmarkIsTouched, href]); - const handleSecondaryIconClick = (e: React.MouseEvent) => { + const { + handleClick: handleSecondaryIconClick, + handleMouseDown: handleSecondaryIconMouseDown, + } = useFastClick((e: React.MouseEvent) => { if ((disabled && !allowDisabledClick) || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return; + e.stopPropagation(); + if (onSecondaryIconClick) { onSecondaryIconClick(e); } else { handleContextMenu(e); } - }; + }); const handleMouseDown = useCallback((e: React.MouseEvent) => { if (inactive || IS_TOUCH_ENV) { @@ -242,8 +251,8 @@ const ListItem: FC = ({ round color="translucent" size="smaller" - onClick={IS_TOUCH_ENV ? handleSecondaryIconClick : undefined} - onMouseDown={!IS_TOUCH_ENV ? handleSecondaryIconClick : undefined} + onClick={handleSecondaryIconClick} + onMouseDown={handleSecondaryIconMouseDown} > diff --git a/src/components/ui/Tab.tsx b/src/components/ui/Tab.tsx index 1022d1c14..7ae809307 100644 --- a/src/components/ui/Tab.tsx +++ b/src/components/ui/Tab.tsx @@ -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 = ({ handleContextMenuHide, isContextMenuOpen, } = useContextMenuHandlers(tabRef, !contextActions); - const handleClick = useCallback((e: React.MouseEvent) => { + const { handleClick, handleMouseDown } = useFastClick((e: React.MouseEvent) => { if (contextActions && (e.button === MouseButton.Secondary || !onClick)) { handleBeforeContextMenu(e); } @@ -122,7 +123,7 @@ const Tab: FC = ({ } onClick?.(clickArg!); - }, [clickArg, contextActions, handleBeforeContextMenu, onClick]); + }); const getTriggerElement = useCallback(() => tabRef.current, []); @@ -157,8 +158,8 @@ const Tab: FC = ({ return (
diff --git a/src/hooks/useFastClick.ts b/src/hooks/useFastClick.ts new file mode 100644 index 000000000..418a98a81 --- /dev/null +++ b/src/hooks/useFastClick.ts @@ -0,0 +1,20 @@ +import type React from '../lib/teact/teact'; + +import { IS_TOUCH_ENV, MouseButton } from '../util/windowEnvironment'; + +type EventArg = React.MouseEvent; +type EventHandler = (e: EventArg) => void; + +export function useFastClick(callback?: EventHandler) { + const wrapperHandler = callback ? (e: EventArg) => { + if (e.type === 'mousedown' && e.button !== MouseButton.Main) { + return; + } + + callback(e); + } : undefined; + + return IS_TOUCH_ENV + ? { handleClick: wrapperHandler } + : { handleMouseDown: wrapperHandler }; +}