import type { FC } from '../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { ApiUser, ApiMessage, ApiChat, ApiSticker, } from '../../api/types'; import type { FocusDirection } from '../../types'; import { selectUser, selectChatMessage, selectIsMessageFocused, selectChat, } from '../../global/selectors'; import { getMessageHtmlId, isChatChannel } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import { renderActionMessageText } from '../common/helpers/renderActionMessageText'; import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; import useEnsureMessage from '../../hooks/useEnsureMessage'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import type { ObserveFn } from '../../hooks/useIntersectionObserver'; import { useIsIntersecting, useOnIntersect } from '../../hooks/useIntersectionObserver'; import useFocusMessage from './message/hooks/useFocusMessage'; import useLang from '../../hooks/useLang'; import useFlag from '../../hooks/useFlag'; import useShowTransition from '../../hooks/useShowTransition'; import ContextMenuContainer from './message/ContextMenuContainer.async'; import AnimatedIconFromSticker from '../common/AnimatedIconFromSticker'; type OwnProps = { message: ApiMessage; observeIntersectionForReading?: ObserveFn; observeIntersectionForLoading?: ObserveFn; observeIntersectionForPlaying?: ObserveFn; isEmbedded?: boolean; appearanceOrder?: number; isLastInList?: boolean; memoFirstUnreadIdRef?: { current: number | undefined }; }; type StateProps = { usersById: Record; senderUser?: ApiUser; senderChat?: ApiChat; targetUserIds?: string[]; targetMessage?: ApiMessage; targetChatId?: string; isFocused: boolean; focusDirection?: FocusDirection; noFocusHighlight?: boolean; premiumGiftSticker?: ApiSticker; }; const APPEARANCE_DELAY = 10; const ActionMessage: FC = ({ message, observeIntersectionForReading, observeIntersectionForLoading, observeIntersectionForPlaying, isEmbedded, appearanceOrder = 0, isLastInList, usersById, senderUser, senderChat, targetUserIds, targetMessage, targetChatId, isFocused, focusDirection, noFocusHighlight, premiumGiftSticker, memoFirstUnreadIdRef, }) => { const { openPremiumModal, requestConfetti } = getActions(); const lang = useLang(); // eslint-disable-next-line no-null/no-null const ref = useRef(null); useOnIntersect(ref, observeIntersectionForReading); useEnsureMessage(message.chatId, message.replyToMessageId, targetMessage); useFocusMessage(ref, message.chatId, isFocused, focusDirection, noFocusHighlight); const noAppearanceAnimation = appearanceOrder <= 0; const [isShown, markShown] = useFlag(noAppearanceAnimation); const isGift = Boolean(message.content.action?.text.startsWith('ActionGift')); useEffect(() => { if (noAppearanceAnimation) { return; } setTimeout(markShown, appearanceOrder * APPEARANCE_DELAY); }, [appearanceOrder, markShown, noAppearanceAnimation]); const isVisible = useIsIntersecting(ref, observeIntersectionForPlaying); const shouldShowConfettiRef = useRef((() => { const isUnread = memoFirstUnreadIdRef?.current && message.id >= memoFirstUnreadIdRef.current; return isGift && !message.isOutgoing && isUnread; })()); useEffect(() => { if (isVisible && shouldShowConfettiRef.current) { shouldShowConfettiRef.current = false; requestConfetti(); } }, [isVisible, requestConfetti]); const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false); const targetUsers = useMemo(() => { return targetUserIds ? targetUserIds.map((userId) => usersById?.[userId]).filter(Boolean) : undefined; }, [targetUserIds, usersById]); const content = renderActionMessageText( lang, message, senderUser, senderChat, targetUsers, targetMessage, targetChatId, { isEmbedded }, observeIntersectionForLoading, observeIntersectionForPlaying, ); const { isContextMenuOpen, contextMenuPosition, handleBeforeContextMenu, handleContextMenu, handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(ref); const isContextMenuShown = contextMenuPosition !== undefined; const handleMouseDown = (e: React.MouseEvent) => { preventMessageInputBlur(e); handleBeforeContextMenu(e); }; const handlePremiumGiftClick = () => { openPremiumModal({ isGift: true, fromUserId: senderUser?.id, toUserId: targetUserIds?.[0], monthsAmount: message.content.action?.months || 0, }); }; if (isEmbedded) { return {content}; } function renderGift() { return ( {lang('ActionGiftPremiumTitle')} {lang('ActionGiftPremiumSubtitle', lang('Months', message.content.action?.months, 'i'))} {lang('ActionGiftPremiumView')} ); } const className = buildClassName( 'ActionMessage message-list-item', isFocused && !noFocusHighlight && 'focused', isGift && 'premium-gift', isContextMenuShown && 'has-menu-open', isLastInList && 'last-in-list', transitionClassNames, ); return (
{content} {isGift && renderGift()} {contextMenuPosition && ( )}
); }; export default memo(withGlobal( (global, { message }): StateProps => { const { byId: usersById } = global.users; const userId = message.senderId; const { targetUserIds, targetChatId } = message.content.action || {}; const targetMessageId = message.replyToMessageId; const targetMessage = targetMessageId ? selectChatMessage(global, message.chatId, targetMessageId) : undefined; const isFocused = selectIsMessageFocused(global, message); const { direction: focusDirection, noHighlight: noFocusHighlight } = (isFocused && global.focusedMessage) || {}; const chat = selectChat(global, message.chatId); const isChat = chat && (isChatChannel(chat) || userId === message.chatId); const senderUser = !isChat && userId ? selectUser(global, userId) : undefined; const senderChat = isChat ? chat : undefined; const premiumGiftSticker = global.premiumGifts?.stickers?.[0]; return { usersById, senderUser, senderChat, targetChatId, targetUserIds, targetMessage, isFocused, premiumGiftSticker, ...(isFocused && { focusDirection, noFocusHighlight }), }; }, )(ActionMessage));