import type { FC } from '../../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useRef, } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; import type { ApiAvailableReaction, ApiChat, ApiChatReactions, ApiMessage, ApiPeer, ApiReaction, ApiSponsoredMessage, ApiStickerSet, ApiThreadInfo, ApiUser, } from '../../../api/types'; import type { IAnchorPosition } from '../../../types'; import { getUserFullName, isUserId } from '../../../global/helpers'; import buildClassName from '../../../util/buildClassName'; import { disableScrolling, enableScrolling } from '../../../util/scrollLock'; import { REM } from '../../common/helpers/mediaDimensions'; import renderText from '../../common/helpers/renderText'; import { getMessageCopyOptions } from './helpers/copyOptions'; import useAppLayout from '../../../hooks/useAppLayout'; import useFlag from '../../../hooks/useFlag'; import useLastCallback from '../../../hooks/useLastCallback'; import useMenuPosition from '../../../hooks/useMenuPosition'; import useOldLang from '../../../hooks/useOldLang'; import AvatarList from '../../common/AvatarList'; import Menu from '../../ui/Menu'; import MenuItem from '../../ui/MenuItem'; import MenuSeparator from '../../ui/MenuSeparator'; import Skeleton from '../../ui/placeholder/Skeleton'; import ReactionSelector from './reactions/ReactionSelector'; import ReadTimeMenuItem from './ReadTimeMenuItem'; import './MessageContextMenu.scss'; type OwnProps = { isReactionPickerOpen?: boolean; availableReactions?: ApiAvailableReaction[]; topReactions?: ApiReaction[]; defaultTagReactions?: ApiReaction[]; isOpen: boolean; anchor: IAnchorPosition; targetHref?: string; message: ApiMessage | ApiSponsoredMessage; canSendNow?: boolean; enabledReactions?: ApiChatReactions; reactionsLimit?: number; canReschedule?: boolean; canReply?: boolean; canQuote?: boolean; repliesThreadInfo?: ApiThreadInfo; canPin?: boolean; canUnpin?: boolean; canDelete?: boolean; canReport?: boolean; canShowReactionsCount?: boolean; canShowReactionList?: boolean; canBuyPremium?: boolean; canEdit?: boolean; canForward?: boolean; canFaveSticker?: boolean; canUnfaveSticker?: boolean; canCopy?: boolean; canCopyLink?: boolean; canSelect?: boolean; canTranslate?: boolean; canShowOriginal?: boolean; canSelectLanguage?: boolean; isPrivate?: boolean; isCurrentUserPremium?: boolean; canDownload?: boolean; canSaveGif?: boolean; canRevote?: boolean; canClosePoll?: boolean; isDownloading?: boolean; canShowSeenBy?: boolean; seenByRecentPeers?: ApiPeer[]; noReplies?: boolean; hasCustomEmoji?: boolean; customEmojiSets?: ApiStickerSet[]; canPlayAnimatedEmojis?: boolean; noTransition?: boolean; isInSavedMessages?: boolean; shouldRenderShowWhen?: boolean; canLoadReadDate?: boolean; onReply?: NoneToVoidFunction; onOpenThread?: VoidFunction; onEdit?: NoneToVoidFunction; onPin?: NoneToVoidFunction; onUnpin?: NoneToVoidFunction; onForward?: NoneToVoidFunction; onDelete?: NoneToVoidFunction; onReport?: NoneToVoidFunction; onFaveSticker?: NoneToVoidFunction; onUnfaveSticker?: NoneToVoidFunction; onSelect?: NoneToVoidFunction; onSend?: NoneToVoidFunction; onReschedule?: NoneToVoidFunction; onClose: NoneToVoidFunction; onCloseAnimationEnd?: NoneToVoidFunction; onCopyLink?: NoneToVoidFunction; onCopyMessages?: (messageIds: number[]) => void; onCopyNumber?: NoneToVoidFunction; onDownload?: NoneToVoidFunction; onSaveGif?: NoneToVoidFunction; onCancelVote?: NoneToVoidFunction; onClosePoll?: NoneToVoidFunction; onShowSeenBy?: NoneToVoidFunction; onShowReactors?: NoneToVoidFunction; onAboutAdsClick?: NoneToVoidFunction; onSponsoredHide?: NoneToVoidFunction; onSponsorInfo?: NoneToVoidFunction; onSponsoredReport?: NoneToVoidFunction; onTranslate?: NoneToVoidFunction; onShowOriginal?: NoneToVoidFunction; onSelectLanguage?: NoneToVoidFunction; onToggleReaction?: (reaction: ApiReaction) => void; onReactionPickerOpen?: (position: IAnchorPosition) => void; }; const SCROLLBAR_WIDTH = 10; const REACTION_SELECTOR_WIDTH_REM = 19.25; const ANIMATION_DURATION = 200; const MessageContextMenu: FC = ({ isReactionPickerOpen, availableReactions, topReactions, defaultTagReactions, isOpen, message, isPrivate, isCurrentUserPremium, enabledReactions, reactionsLimit, anchor, targetHref, canSendNow, canReschedule, canBuyPremium, canReply, canQuote, canEdit, noReplies, canPin, canUnpin, canDelete, canReport, canForward, canFaveSticker, canUnfaveSticker, canCopy, canCopyLink, canSelect, canDownload, canSaveGif, canRevote, canClosePoll, canTranslate, canShowOriginal, canSelectLanguage, isDownloading, repliesThreadInfo, canShowSeenBy, canShowReactionsCount, canShowReactionList, seenByRecentPeers, hasCustomEmoji, customEmojiSets, canPlayAnimatedEmojis, noTransition, isInSavedMessages, shouldRenderShowWhen, canLoadReadDate, onReply, onOpenThread, onEdit, onPin, onUnpin, onForward, onDelete, onReport, onFaveSticker, onUnfaveSticker, onSelect, onSend, onReschedule, onClose, onCloseAnimationEnd, onCopyLink, onCopyNumber, onDownload, onSaveGif, onCancelVote, onClosePoll, onShowSeenBy, onShowReactors, onToggleReaction, onCopyMessages, onAboutAdsClick, onSponsoredHide, onSponsorInfo, onSponsoredReport, onReactionPickerOpen, onTranslate, onShowOriginal, onSelectLanguage, }) => { const { showNotification, openStickerSet, openCustomEmojiSets, loadStickers, } = getActions(); // eslint-disable-next-line no-null/no-null const menuRef = useRef(null); // eslint-disable-next-line no-null/no-null const scrollableRef = useRef(null); const lang = useOldLang(); const noReactions = !isPrivate && !enabledReactions; const withReactions = canShowReactionList && !noReactions; const isSponsoredMessage = !('id' in message); const messageId = !isSponsoredMessage ? message.id : ''; const seenByDates = !isSponsoredMessage ? message.seenByDates : undefined; const [areItemsHidden, hideItems] = useFlag(); const [isReady, markIsReady, unmarkIsReady] = useFlag(); const { isMobile, isDesktop } = useAppLayout(); const seenByDatesCount = useMemo(() => (seenByDates ? Object.keys(seenByDates).length : 0), [seenByDates]); const handleAfterCopy = useLastCallback(() => { showNotification({ message: lang('Share.Link.Copied'), }); onClose(); }); useEffect(() => { if (isOpen && areItemsHidden && !isReactionPickerOpen) { onClose(); } }, [onClose, isOpen, isReactionPickerOpen, areItemsHidden]); useEffect(() => { if (customEmojiSets?.length) { customEmojiSets.map((customEmojiSet) => { return loadStickers({ stickerSetInfo: { id: customEmojiSet.id, accessHash: customEmojiSet.accessHash, }, }); }); } }, [customEmojiSets, openCustomEmojiSets]); const handleOpenCustomEmojiSets = useLastCallback(() => { if (!customEmojiSets) return; if (customEmojiSets.length === 1) { openStickerSet({ stickerSetInfo: { shortName: customEmojiSets[0].shortName, }, }); } else { openCustomEmojiSets({ setIds: customEmojiSets.map((set) => set.id), }); } onClose(); }); const copyOptions = isSponsoredMessage ? [] : getMessageCopyOptions( message, targetHref, canCopy, handleAfterCopy, canCopyLink ? onCopyLink : undefined, onCopyMessages, onCopyNumber, ); const getTriggerElement = useLastCallback(() => { return isSponsoredMessage ? document.querySelector('.Transition_slide-active > .MessageList .SponsoredMessage') : document.querySelector(`.Transition_slide-active > .MessageList div[data-message-id="${messageId}"]`); }); const getRootElement = useLastCallback(() => document.querySelector('.Transition_slide-active > .MessageList')); const getMenuElement = useLastCallback(() => document.querySelector('.MessageContextMenu .bubble')); const getLayout = useLastCallback(() => { const extraHeightAudioPlayer = (isMobile && (document.querySelector('.AudioPlayer-content'))?.offsetHeight) || 0; const pinnedElement = document.querySelector('.HeaderPinnedMessageWrapper'); const extraHeightPinned = (((isMobile && !extraHeightAudioPlayer) || (!isMobile && pinnedElement?.classList.contains('full-width'))) && pinnedElement?.offsetHeight) || 0; return { extraPaddingX: SCROLLBAR_WIDTH, extraTopPadding: (document.querySelector('.MiddleHeader')!).offsetHeight, extraMarginTop: extraHeightPinned + extraHeightAudioPlayer, shouldAvoidNegativePosition: !isDesktop, menuElMinWidth: withReactions && isMobile ? REACTION_SELECTOR_WIDTH_REM * REM : undefined, }; }); useEffect(() => { if (!isOpen) { unmarkIsReady(); return; } setTimeout(() => { markIsReady(); }, ANIMATION_DURATION); }, [isOpen, markIsReady, unmarkIsReady]); const { positionX, positionY, transformOriginX, transformOriginY, style, menuStyle, withScroll, } = useMenuPosition(anchor, getTriggerElement, getRootElement, getMenuElement, getLayout); useEffect(() => { disableScrolling(withScroll ? scrollableRef.current : undefined, '.ReactionPicker'); return enableScrolling; }, [withScroll]); const handleOpenMessageReactionPicker = useLastCallback((position: IAnchorPosition) => { onReactionPickerOpen!(position); hideItems(); }); return ( {withReactions && ( )}
{canSendNow && {lang('MessageScheduleSend')}} {canReschedule && ( {lang('MessageScheduleEditTime')} )} {canReply && ( {lang(canQuote ? 'lng_context_quote_and_reply' : 'Reply')} )} {!noReplies && Boolean(repliesThreadInfo?.messagesCount) && ( {lang('Conversation.ContextViewReplies', repliesThreadInfo!.messagesCount, 'i')} )} {canEdit && {lang('Edit')}} {canFaveSticker && ( {lang('AddToFavorites')} )} {canUnfaveSticker && ( {lang('Stickers.RemoveFromFavorites')} )} {canTranslate && {lang('TranslateMessage')}} {canShowOriginal && {lang('ShowOriginalButton')}} {canSelectLanguage && ( {lang('lng_settings_change_lang')} )} {copyOptions.map((option) => ( {lang(option.label)} ))} {canPin && {lang('DialogPin')}} {canUnpin && {lang('DialogUnpin')}} {canSaveGif && {lang('lng_context_save_gif')}} {canRevote && {lang('lng_polls_retract')}} {canClosePoll && {lang('lng_polls_stop')}} {canDownload && ( {isDownloading ? lang('lng_context_cancel_download') : lang('lng_media_download')} )} {canForward && {lang('Forward')}} {canSelect && {lang('Common.Select')}} {canReport && {lang('lng_context_report_msg')}} {canDelete && {lang('Delete')}} {hasCustomEmoji && ( <> {!customEmojiSets && ( <> )} {customEmojiSets && customEmojiSets.length === 1 && ( {renderText(lang('MessageContainsEmojiPack', customEmojiSets[0].title), ['simple_markdown', 'emoji'])} )} {customEmojiSets && customEmojiSets.length > 1 && ( {renderText(lang('MessageContainsEmojiPacks', customEmojiSets.length), ['simple_markdown'])} )} )} {isSponsoredMessage && message.sponsorInfo && ( {lang('SponsoredMessageSponsor')} )} {isSponsoredMessage && ( {lang(message.canReport ? 'AboutRevenueSharingAds' : 'SponsoredMessageInfo')} )} {isSponsoredMessage && message.canReport && ( {lang('ReportAd')} )} {isSponsoredMessage && onSponsoredHide && ( <> {lang('HideAd')} )} {(canShowSeenBy || canShowReactionsCount) && !isSponsoredMessage && ( <> {canShowReactionsCount && message.reactors?.count ? ( canShowSeenBy && seenByDatesCount ? lang( 'Chat.OutgoingContextMixedReactionCount', [message.reactors.count, seenByDatesCount], ) : lang('Chat.ContextReactionCount', message.reactors.count, 'i') ) : ( seenByDatesCount === 1 && seenByRecentPeers ? renderText( isUserId(seenByRecentPeers[0].id) ? getUserFullName(seenByRecentPeers[0] as ApiUser)! : (seenByRecentPeers[0] as ApiChat).title, ) : ( seenByDatesCount ? lang('Conversation.ContextMenuSeen', seenByDatesCount, 'i') : lang('Conversation.ContextMenuNoViews') ) )} )} {!isSponsoredMessage && (canLoadReadDate || shouldRenderShowWhen) && ( )}
); }; export default memo(MessageContextMenu);