759 lines
23 KiB
TypeScript
759 lines
23 KiB
TypeScript
import {
|
|
memo, useEffect, useMemo, useRef,
|
|
useUnmountCleanup,
|
|
} from '../../../lib/teact/teact';
|
|
import { getActions, withGlobal } from '../../../global';
|
|
|
|
import type { ApiMessageAction } from '../../../api/types/messageActions';
|
|
import type {
|
|
FocusDirection,
|
|
ScrollTargetPosition,
|
|
ThreadId,
|
|
} from '../../../types';
|
|
import type { Signal } from '../../../util/signals';
|
|
import {
|
|
type ApiKeyboardButton,
|
|
type ApiMessage,
|
|
type ApiPeer,
|
|
type KeyboardButtonGiftOffer,
|
|
type KeyboardButtonNoForwardsRequest,
|
|
MAIN_THREAD_ID,
|
|
} from '../../../api/types';
|
|
import { MediaViewerOrigin } from '../../../types';
|
|
|
|
import { MESSAGE_APPEARANCE_DELAY } from '../../../config';
|
|
import { getMessageHtmlId } from '../../../global/helpers';
|
|
import { getPeerTitle } from '../../../global/helpers/peers';
|
|
import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
|
import {
|
|
selectActionMessageBg,
|
|
selectChat,
|
|
selectChatMessage,
|
|
selectIsCurrentUserFrozen,
|
|
selectIsCurrentUserPremium,
|
|
selectIsInSelectMode,
|
|
selectIsMessageFocused,
|
|
selectSender,
|
|
selectTabState,
|
|
} from '../../../global/selectors';
|
|
import { selectThreadReadState } from '../../../global/selectors/threads';
|
|
import { IS_TAURI } from '../../../util/browser/globalEnvironment';
|
|
import { IS_ANDROID, IS_FLUID_BACKGROUND_SUPPORTED } from '../../../util/browser/windowEnvironment';
|
|
import buildClassName from '../../../util/buildClassName';
|
|
import { isLocalMessageId } from '../../../util/keys/messageKey';
|
|
import { getServerTime } from '../../../util/serverTime';
|
|
import { isElementInViewport } from '../../../util/visibility/isElementInViewport';
|
|
import { preventMessageInputBlur } from '../helpers/preventMessageInputBlur';
|
|
|
|
import useAppLayout from '../../../hooks/useAppLayout';
|
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
|
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
|
import useFlag from '../../../hooks/useFlag';
|
|
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
|
import useLang from '../../../hooks/useLang';
|
|
import useLastCallback from '../../../hooks/useLastCallback';
|
|
import useShowTransition from '../../../hooks/useShowTransition';
|
|
import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter';
|
|
import useFocusMessageListElement from './hooks/useFocusMessageListElement';
|
|
|
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
|
import ActionMessageText from './ActionMessageText';
|
|
import ChannelPhoto from './actions/ChannelPhoto';
|
|
import Gift from './actions/Gift';
|
|
import GiveawayPrize from './actions/GiveawayPrize';
|
|
import NoForwardsRequest from './actions/NoForwardsRequest';
|
|
import StarGift from './actions/StarGift';
|
|
import StarGiftPurchaseOffer from './actions/StarGiftPurchaseOffer';
|
|
import StarGiftUnique from './actions/StarGiftUnique';
|
|
import SuggestedPhoto from './actions/SuggestedPhoto';
|
|
import SuggestedPostApproval from './actions/SuggestedPostApproval';
|
|
import SuggestedPostBalanceTooLow from './actions/SuggestedPostBalanceTooLow';
|
|
import SuggestedPostRejected from './actions/SuggestedPostRejected';
|
|
import ContextMenuContainer from './ContextMenuContainer';
|
|
import InlineButtons from './InlineButtons';
|
|
import Reactions from './reactions/Reactions';
|
|
import SimilarChannels from './SimilarChannels';
|
|
|
|
import styles from './ActionMessage.module.scss';
|
|
|
|
type OwnProps = {
|
|
message: ApiMessage;
|
|
threadId: ThreadId;
|
|
appearanceOrder: number;
|
|
isJustAdded?: boolean;
|
|
isLastInList?: boolean;
|
|
memoFirstUnreadIdRef?: { current: number | undefined };
|
|
getIsMessageListReady?: Signal<boolean>;
|
|
observeIntersectionForBottom?: ObserveFn;
|
|
observeIntersectionForLoading?: ObserveFn;
|
|
observeIntersectionForPlaying?: ObserveFn;
|
|
onMessageUnmount?: (messageId: number) => void;
|
|
};
|
|
|
|
type StateProps = {
|
|
sender?: ApiPeer;
|
|
currentUserId?: string;
|
|
isInsideTopic?: boolean;
|
|
isFocused?: boolean;
|
|
focusDirection?: FocusDirection;
|
|
noFocusHighlight?: boolean;
|
|
replyMessage?: ApiMessage;
|
|
actionMessageBg?: string;
|
|
isCurrentUserPremium?: boolean;
|
|
isInSelectMode?: boolean;
|
|
hasUnreadReaction?: boolean;
|
|
isResizingContainer?: boolean;
|
|
scrollTargetPosition?: ScrollTargetPosition;
|
|
isAccountFrozen?: boolean;
|
|
noForwardsRequestExpirePeriod: number;
|
|
};
|
|
|
|
const SINGLE_LINE_ACTIONS = new Set<ApiMessageAction['type']>([
|
|
'pinMessage',
|
|
'chatEditPhoto',
|
|
'chatDeletePhoto',
|
|
'todoCompletions',
|
|
'todoAppendTasks',
|
|
'unsupported',
|
|
]);
|
|
const HIDDEN_TEXT_ACTIONS = new Set<ApiMessageAction['type']>(['giftCode', 'prizeStars',
|
|
'suggestProfilePhoto', 'suggestedPostApproval', 'starGiftPurchaseOffer', 'noForwardsRequest']);
|
|
|
|
const ActionMessage = ({
|
|
message,
|
|
threadId,
|
|
sender,
|
|
currentUserId,
|
|
appearanceOrder,
|
|
isJustAdded,
|
|
isLastInList,
|
|
memoFirstUnreadIdRef,
|
|
getIsMessageListReady,
|
|
isInsideTopic,
|
|
isFocused,
|
|
focusDirection,
|
|
noFocusHighlight,
|
|
replyMessage,
|
|
actionMessageBg,
|
|
isCurrentUserPremium,
|
|
isInSelectMode,
|
|
hasUnreadReaction,
|
|
isResizingContainer,
|
|
scrollTargetPosition,
|
|
isAccountFrozen,
|
|
noForwardsRequestExpirePeriod,
|
|
observeIntersectionForBottom,
|
|
observeIntersectionForLoading,
|
|
observeIntersectionForPlaying,
|
|
onMessageUnmount,
|
|
}: OwnProps & StateProps) => {
|
|
const {
|
|
requestConfetti,
|
|
openMediaViewer,
|
|
getReceipt,
|
|
checkGiftCode,
|
|
openPrizeStarsTransactionFromGiveaway,
|
|
openPremiumModal,
|
|
openStarsTransactionFromGift,
|
|
openGiftInfoModalFromMessage,
|
|
toggleChannelRecommendations,
|
|
animateUnreadReaction,
|
|
markMentionsRead,
|
|
focusMessage,
|
|
openGiftOfferAcceptModal,
|
|
declineStarGiftOffer,
|
|
showNotification,
|
|
toggleNoForwards,
|
|
} = getActions();
|
|
|
|
const ref = useRef<HTMLDivElement>();
|
|
|
|
const { id, chatId } = message;
|
|
const action = message.content.action!;
|
|
const isLocal = isLocalMessageId(id);
|
|
|
|
const isTextHidden = HIDDEN_TEXT_ACTIONS.has(action.type);
|
|
const isSingleLine = SINGLE_LINE_ACTIONS.has(action.type);
|
|
const isFluidMultiline = IS_FLUID_BACKGROUND_SUPPORTED && !isSingleLine;
|
|
const isClickableText = action.type === 'suggestedPostSuccess';
|
|
const isNarrowMessage = action.type === 'starGiftPurchaseOfferDeclined';
|
|
|
|
const messageReplyInfo = getMessageReplyInfo(message);
|
|
const { replyToMsgId, replyToPeerId } = messageReplyInfo || {};
|
|
|
|
const withServiceReactions = Boolean(message.areReactionsPossible && message?.reactions?.results?.length);
|
|
|
|
const hasGiftOfferExpired = action.type === 'starGiftPurchaseOffer' && action.expiresAt <= getServerTime();
|
|
const shouldRenderGiftOfferButtons = action.type === 'starGiftPurchaseOffer'
|
|
&& !message.isOutgoing && !action.isAccepted && !action.isDeclined && !hasGiftOfferExpired;
|
|
|
|
const hasNoForwardsRequestExpired = action.type === 'noForwardsRequest'
|
|
&& (message.date + noForwardsRequestExpirePeriod) <= getServerTime();
|
|
const shouldRenderNoForwardsButtons = action.type === 'noForwardsRequest'
|
|
&& !message.isOutgoing && !action.isExpired && !hasNoForwardsRequestExpired;
|
|
|
|
const shouldRenderInlineButtons = shouldRenderGiftOfferButtons || shouldRenderNoForwardsButtons;
|
|
|
|
const shouldSkipRender = isInsideTopic && action.type === 'topicCreate';
|
|
|
|
const lang = useLang();
|
|
const { isTouchScreen } = useAppLayout();
|
|
|
|
const giftOfferInlineButtons: KeyboardButtonGiftOffer[][] = useMemo(() => [
|
|
[
|
|
{
|
|
type: 'giftOffer',
|
|
buttonType: 'reject',
|
|
text: lang('GiftOfferReject'),
|
|
},
|
|
{
|
|
type: 'giftOffer',
|
|
buttonType: 'accept',
|
|
text: lang('GiftOfferAccept'),
|
|
},
|
|
],
|
|
], [lang]);
|
|
|
|
const noForwardsInlineButtons: KeyboardButtonNoForwardsRequest[][] = useMemo(() => [
|
|
[
|
|
{
|
|
type: 'noForwardsRequest',
|
|
buttonType: 'reject',
|
|
text: lang('NoForwardsRequestReject'),
|
|
},
|
|
{
|
|
type: 'noForwardsRequest',
|
|
buttonType: 'accept',
|
|
text: lang('NoForwardsRequestAccept'),
|
|
},
|
|
],
|
|
], [lang]);
|
|
|
|
const [isRejectOfferDialogOpen, openRejectOfferDialog, closeRejectOfferDialog] = useFlag(false);
|
|
|
|
const handleInlineButtonClick = useLastCallback((button: ApiKeyboardButton) => {
|
|
if (button.type === 'giftOffer') {
|
|
if (button.buttonType === 'accept') {
|
|
if (action.type === 'starGiftPurchaseOffer') {
|
|
openGiftOfferAcceptModal({
|
|
peerId: chatId,
|
|
messageId: id,
|
|
gift: action.gift,
|
|
price: action.price,
|
|
});
|
|
}
|
|
} else if (button.buttonType === 'reject') {
|
|
openRejectOfferDialog();
|
|
}
|
|
} else if (button.type === 'noForwardsRequest') {
|
|
if (action.type === 'noForwardsRequest') {
|
|
const isAccept = button.buttonType === 'accept';
|
|
toggleNoForwards({
|
|
userId: chatId,
|
|
isEnabled: isAccept ? action.newValue : action.prevValue,
|
|
requestMsgId: id,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
const handleRejectOfferConfirm = useLastCallback(() => {
|
|
closeRejectOfferDialog();
|
|
declineStarGiftOffer({ messageId: id });
|
|
});
|
|
|
|
const handleRejectOfferClose = useLastCallback(() => {
|
|
closeRejectOfferDialog();
|
|
});
|
|
|
|
useOnIntersect(ref, !shouldSkipRender ? observeIntersectionForBottom : undefined);
|
|
|
|
useEnsureMessage(
|
|
replyToPeerId || chatId,
|
|
replyToMsgId,
|
|
replyMessage,
|
|
id,
|
|
);
|
|
useFocusMessageListElement({
|
|
elementRef: ref,
|
|
isFocused,
|
|
focusDirection,
|
|
noFocusHighlight,
|
|
isResizingContainer,
|
|
isJustAdded,
|
|
scrollTargetPosition,
|
|
});
|
|
|
|
const {
|
|
isContextMenuOpen, contextMenuAnchor,
|
|
handleBeforeContextMenu, handleContextMenu,
|
|
handleContextMenuClose, handleContextMenuHide,
|
|
} = useContextMenuHandlers(
|
|
ref,
|
|
(isTouchScreen && isInSelectMode) || isAccountFrozen,
|
|
!IS_TAURI,
|
|
IS_ANDROID,
|
|
getIsMessageListReady,
|
|
);
|
|
const isContextMenuShown = contextMenuAnchor !== undefined;
|
|
|
|
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
|
preventMessageInputBlur(e);
|
|
handleBeforeContextMenu(e);
|
|
};
|
|
|
|
const noAppearanceAnimation = appearanceOrder <= 0;
|
|
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
|
useEffect(() => {
|
|
if (noAppearanceAnimation) {
|
|
return;
|
|
}
|
|
|
|
// Keep as is for now to avoid breaking appearance order
|
|
setTimeout(markShown, appearanceOrder * MESSAGE_APPEARANCE_DELAY);
|
|
}, [appearanceOrder, noAppearanceAnimation]);
|
|
|
|
const { ref: refWithTransition } = useShowTransition({
|
|
isOpen: isShown,
|
|
noOpenTransition: noAppearanceAnimation,
|
|
noCloseTransition: true,
|
|
className: false,
|
|
ref,
|
|
});
|
|
|
|
useUnmountCleanup(() => {
|
|
onMessageUnmount?.(id);
|
|
});
|
|
|
|
useEffect(() => {
|
|
const bottomMarker = ref.current;
|
|
if (!bottomMarker || !isElementInViewport(bottomMarker)) return;
|
|
|
|
if (hasUnreadReaction) {
|
|
animateUnreadReaction({ chatId, messageIds: [id] });
|
|
}
|
|
|
|
if (message.hasUnreadMention) {
|
|
markMentionsRead({ chatId, messageIds: [id] });
|
|
}
|
|
}, [hasUnreadReaction, chatId, id, animateUnreadReaction, message.hasUnreadMention]);
|
|
|
|
useEffect(() => {
|
|
if (action.type !== 'giftPremium') return;
|
|
if ((memoFirstUnreadIdRef?.current && id >= memoFirstUnreadIdRef.current) || isLocal) {
|
|
requestConfetti({});
|
|
}
|
|
}, [action.type, id, isLocal, memoFirstUnreadIdRef]);
|
|
|
|
const fluidBackgroundStyle = useFluidBackgroundFilter(isFluidMultiline ? actionMessageBg : undefined);
|
|
|
|
const handleKeyDown = useLastCallback((e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
handleClick();
|
|
}
|
|
});
|
|
|
|
const handleClick = useLastCallback(() => {
|
|
switch (action.type) {
|
|
case 'paymentSent':
|
|
case 'paymentRefunded': {
|
|
getReceipt({
|
|
chatId: message.chatId,
|
|
messageId: message.id,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'chatEditPhoto': {
|
|
openMediaViewer({
|
|
chatId: message.chatId,
|
|
messageId: message.id,
|
|
threadId,
|
|
origin: MediaViewerOrigin.ChannelAvatar,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'giftCode': {
|
|
checkGiftCode({ slug: action.slug, message: { chatId: message.chatId, messageId: message.id } });
|
|
break;
|
|
}
|
|
|
|
case 'prizeStars': {
|
|
openPrizeStarsTransactionFromGiveaway({
|
|
chatId: message.chatId,
|
|
messageId: message.id,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'giftPremium': {
|
|
openPremiumModal({
|
|
isGift: true,
|
|
fromUserId: sender?.id,
|
|
toUserId: sender && sender.id === currentUserId ? chatId : currentUserId,
|
|
daysAmount: action.days,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'giftTon':
|
|
case 'giftStars': {
|
|
openStarsTransactionFromGift({
|
|
chatId: message.chatId,
|
|
messageId: message.id,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'starGift': {
|
|
openGiftInfoModalFromMessage({
|
|
chatId: message.chatId,
|
|
messageId: message.id,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'starGiftUnique': {
|
|
if (action.gift.isBurned) {
|
|
showNotification({ message: lang('ActionStarGiftUniqueBurnedError') });
|
|
break;
|
|
}
|
|
openGiftInfoModalFromMessage({
|
|
chatId: message.chatId,
|
|
messageId: message.id,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'starGiftPurchaseOffer': {
|
|
if (shouldRenderGiftOfferButtons) {
|
|
openGiftOfferAcceptModal({
|
|
peerId: chatId,
|
|
messageId: id,
|
|
gift: action.gift,
|
|
price: action.price,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'channelJoined': {
|
|
toggleChannelRecommendations({ chatId });
|
|
break;
|
|
}
|
|
|
|
case 'suggestedPostApproval': {
|
|
const replyInfo = getMessageReplyInfo(message);
|
|
if (replyInfo?.type === 'message' && replyInfo.replyToMsgId) {
|
|
focusMessage({
|
|
chatId: message.chatId,
|
|
threadId,
|
|
messageId: replyInfo.replyToMsgId,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'suggestedPostSuccess': {
|
|
const replyInfo = getMessageReplyInfo(message);
|
|
if (replyInfo?.type === 'message' && replyInfo.replyToMsgId) {
|
|
focusMessage({
|
|
chatId: message.chatId,
|
|
threadId,
|
|
messageId: replyInfo.replyToMsgId,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
const fullContent = useMemo(() => {
|
|
switch (action.type) {
|
|
case 'chatEditPhoto': {
|
|
if (!action.photo) return undefined;
|
|
return (
|
|
<ChannelPhoto
|
|
action={action}
|
|
observeIntersection={observeIntersectionForLoading}
|
|
onClick={handleClick}
|
|
/>
|
|
);
|
|
}
|
|
|
|
case 'suggestProfilePhoto':
|
|
return (
|
|
<SuggestedPhoto
|
|
message={message}
|
|
action={action}
|
|
observeIntersection={observeIntersectionForLoading}
|
|
/>
|
|
);
|
|
|
|
case 'prizeStars':
|
|
case 'giftCode':
|
|
return (
|
|
<GiveawayPrize
|
|
action={action}
|
|
sender={sender}
|
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
onClick={handleClick}
|
|
/>
|
|
);
|
|
|
|
case 'giftPremium':
|
|
case 'giftTon':
|
|
case 'giftStars':
|
|
return (
|
|
<Gift
|
|
action={action}
|
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
onClick={handleClick}
|
|
/>
|
|
);
|
|
|
|
case 'starGift':
|
|
return (
|
|
<StarGift
|
|
action={action}
|
|
message={message}
|
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
onClick={handleClick}
|
|
/>
|
|
);
|
|
|
|
case 'starGiftUnique':
|
|
return (
|
|
<StarGiftUnique
|
|
action={action}
|
|
message={message}
|
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
onClick={handleClick}
|
|
/>
|
|
);
|
|
|
|
case 'starGiftPurchaseOffer':
|
|
return (
|
|
<StarGiftPurchaseOffer
|
|
action={action}
|
|
message={message}
|
|
hasButtons={shouldRenderGiftOfferButtons}
|
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
onClick={shouldRenderGiftOfferButtons ? handleClick : undefined}
|
|
/>
|
|
);
|
|
|
|
case 'channelJoined':
|
|
return (
|
|
<SimilarChannels
|
|
chatId={message.chatId}
|
|
/>
|
|
);
|
|
|
|
case 'noForwardsRequest':
|
|
return (
|
|
<NoForwardsRequest
|
|
message={message}
|
|
/>
|
|
);
|
|
|
|
case 'suggestedPostApproval':
|
|
if (action.isBalanceTooLow) {
|
|
return (
|
|
<SuggestedPostBalanceTooLow
|
|
message={message}
|
|
action={action}
|
|
onClick={handleClick}
|
|
/>
|
|
);
|
|
}
|
|
return action.isRejected ? (
|
|
<SuggestedPostRejected
|
|
message={message}
|
|
action={action}
|
|
onClick={handleClick}
|
|
/>
|
|
) : (
|
|
<SuggestedPostApproval
|
|
message={message}
|
|
action={action}
|
|
onClick={handleClick}
|
|
/>
|
|
);
|
|
|
|
default:
|
|
return undefined;
|
|
}
|
|
}, [
|
|
action, message, observeIntersectionForLoading, sender, observeIntersectionForPlaying, shouldRenderGiftOfferButtons,
|
|
]);
|
|
|
|
if ((isInsideTopic && action.type === 'topicCreate') || action.type === 'phoneCall') {
|
|
return undefined;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={refWithTransition}
|
|
id={getMessageHtmlId(id)}
|
|
className={buildClassName(
|
|
'ActionMessage',
|
|
'message-list-item',
|
|
styles.root,
|
|
isSingleLine && styles.singleLine,
|
|
isFluidMultiline && styles.fluidMultiline,
|
|
fullContent && styles.hasFullContent,
|
|
isFocused && !noFocusHighlight && 'focused',
|
|
isContextMenuShown && 'has-menu-open',
|
|
isLastInList && 'last-in-list',
|
|
)}
|
|
data-message-id={message.id}
|
|
data-is-pinned={message.isPinned || undefined}
|
|
data-has-unread-mention={message.hasUnreadMention || undefined}
|
|
data-has-unread-reaction={hasUnreadReaction || undefined}
|
|
onMouseDown={handleMouseDown}
|
|
onContextMenu={handleContextMenu}
|
|
>
|
|
{!isTextHidden && (
|
|
<>
|
|
{isFluidMultiline && (
|
|
<div className={buildClassName(
|
|
styles.inlineWrapper,
|
|
isClickableText && styles.hoverable,
|
|
isNarrowMessage && styles.narrowWrapper,
|
|
)}
|
|
>
|
|
<span className={styles.fluidBackground} style={fluidBackgroundStyle}>
|
|
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
|
</span>
|
|
</div>
|
|
)}
|
|
<div className={buildClassName(
|
|
styles.inlineWrapper,
|
|
isClickableText && styles.hoverable,
|
|
isNarrowMessage && styles.narrowWrapper,
|
|
)}
|
|
>
|
|
<span
|
|
className={styles.textContent}
|
|
role="button"
|
|
tabIndex={0}
|
|
onClick={handleClick}
|
|
onKeyDown={handleKeyDown}
|
|
>
|
|
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
|
</span>
|
|
</div>
|
|
</>
|
|
)}
|
|
{(fullContent || shouldRenderInlineButtons) && (
|
|
<div className={styles.contentWrapper}>
|
|
{fullContent}
|
|
{shouldRenderGiftOfferButtons && (
|
|
<InlineButtons
|
|
className={styles.inlineButtons}
|
|
inlineButtons={giftOfferInlineButtons}
|
|
onClick={handleInlineButtonClick}
|
|
/>
|
|
)}
|
|
{shouldRenderNoForwardsButtons && (
|
|
<InlineButtons
|
|
className={styles.inlineButtons}
|
|
inlineButtons={noForwardsInlineButtons}
|
|
onClick={handleInlineButtonClick}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
{contextMenuAnchor && (
|
|
<ContextMenuContainer
|
|
isOpen={isContextMenuOpen}
|
|
anchor={contextMenuAnchor}
|
|
message={message}
|
|
messageListType="thread"
|
|
className={styles.contextContainer}
|
|
onClose={handleContextMenuClose}
|
|
onCloseAnimationEnd={handleContextMenuHide}
|
|
/>
|
|
)}
|
|
{withServiceReactions && (
|
|
<Reactions
|
|
isOutside
|
|
message={message}
|
|
threadId={threadId}
|
|
observeIntersection={observeIntersectionForPlaying}
|
|
isCurrentUserPremium={isCurrentUserPremium}
|
|
isAccountFrozen={isAccountFrozen}
|
|
/>
|
|
)}
|
|
<ConfirmDialog
|
|
isOpen={isRejectOfferDialogOpen}
|
|
title={lang('GiftOfferRejectTitle')}
|
|
textParts={lang(
|
|
'GiftOfferRejectText',
|
|
{ user: sender ? getPeerTitle(lang, sender) : lang('ActionFallbackSomeone') },
|
|
{ withNodes: true, withMarkdown: true },
|
|
)}
|
|
confirmLabel={lang('GiftOfferReject')}
|
|
confirmIsDestructive
|
|
confirmHandler={handleRejectOfferConfirm}
|
|
onClose={handleRejectOfferClose}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default memo(withGlobal<OwnProps>(
|
|
(global, { message, threadId }): Complete<StateProps> => {
|
|
const tabState = selectTabState(global);
|
|
|
|
const chat = selectChat(global, message.chatId);
|
|
|
|
const sender = selectSender(global, message);
|
|
|
|
const isInsideTopic = chat?.isForum && threadId !== MAIN_THREAD_ID;
|
|
|
|
const { replyToMsgId, replyToPeerId } = getMessageReplyInfo(message) || {};
|
|
const replyMessage = replyToMsgId
|
|
? selectChatMessage(global, replyToPeerId || message.chatId, replyToMsgId) : undefined;
|
|
|
|
const isFocused = threadId ? selectIsMessageFocused(global, message, threadId) : false;
|
|
const {
|
|
direction: focusDirection,
|
|
noHighlight: noFocusHighlight,
|
|
isResizingContainer, scrollTargetPosition,
|
|
} = (isFocused && tabState.focusedMessage) || {};
|
|
|
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
|
|
|
const readState = selectThreadReadState(global, message.chatId, threadId);
|
|
const hasUnreadReaction = readState?.unreadReactions?.includes(message.id);
|
|
const isAccountFrozen = selectIsCurrentUserFrozen(global);
|
|
|
|
return {
|
|
sender,
|
|
currentUserId: global.currentUserId,
|
|
isCurrentUserPremium,
|
|
isFocused,
|
|
focusDirection,
|
|
noFocusHighlight,
|
|
isInsideTopic,
|
|
replyMessage,
|
|
isInSelectMode: selectIsInSelectMode(global),
|
|
actionMessageBg: selectActionMessageBg(global),
|
|
hasUnreadReaction,
|
|
isResizingContainer,
|
|
scrollTargetPosition,
|
|
isAccountFrozen,
|
|
noForwardsRequestExpirePeriod: global.appConfig.noForwardsRequestExpirePeriod,
|
|
};
|
|
},
|
|
)(ActionMessage));
|