Composer Embedded Message: Fix closing animation (#6634)

This commit is contained in:
Alexander Zinchuk 2026-01-20 12:01:22 +01:00
parent c2eb1abd3c
commit a662e58e78
2 changed files with 124 additions and 69 deletions

View File

@ -31,6 +31,7 @@ import { unique } from '../../../util/iteratees';
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import useFrozenProps from '../../../hooks/useFrozenProps';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
@ -81,33 +82,21 @@ type OwnProps = {
const CLOSE_DURATION = 350; const CLOSE_DURATION = 350;
const ComposerEmbeddedMessage = ({ const ComposerEmbeddedMessage = (props: OwnProps & StateProps) => {
replyInfo, const {
suggestedPostInfo, shouldAnimate,
editingId, isReplyToDiscussion,
message, isInChangingRecipientMode,
sender, forwardMessageIds,
shouldAnimate, fromChatId,
forwardedMessagesCount, replyInfo,
noAuthors, editingId,
noCaptions, suggestedPostInfo,
forwardsHaveCaptions, shouldForceShowEditing,
shouldForceShowEditing, message,
isCurrentUserPremium, forwardedMessagesCount,
isContextMenuDisabled, } = props;
isReplyToDiscussion,
isInChangingRecipientMode,
shouldPreventComposerAnimation,
senderChat,
chatId,
currentUserId,
isSenderChannel,
forwardMessageIds,
fromChatId,
isMediaNsfw,
theme,
onClear,
}: OwnProps & StateProps) => {
const { const {
resetDraftReplyInfo, resetDraftReplyInfo,
resetDraftSuggestedPostInfo, resetDraftSuggestedPostInfo,
@ -127,19 +116,16 @@ const ComposerEmbeddedMessage = ({
const lang = useLang(); const lang = useLang();
const isReplyToTopicStart = message?.content.action?.type === 'topicCreate'; const isReplyToTopicStart = message?.content.action?.type === 'topicCreate';
const isShowingReply = replyInfo && !shouldForceShowEditing;
const isReplyWithQuote = Boolean(replyInfo?.quoteText);
const isShowingSuggestedPost = Boolean(suggestedPostInfo) && !shouldForceShowEditing; const isShowingSuggestedPost = Boolean(suggestedPostInfo) && !shouldForceShowEditing;
const isForwarding = Boolean(forwardedMessagesCount); const isForwarding = Boolean(forwardedMessagesCount);
const selectSenderFromForwardedMessage = useLastCallback((forwardedMessage: ApiMessage) => { const selectSenderFromForwardedMessage = useLastCallback((forwardedMessage: ApiMessage) => {
const global = getGlobal(); const global = getGlobal();
sender = selectForwardedSender(global, forwardedMessage); let localSender = selectForwardedSender(global, forwardedMessage);
if (!sender) { if (!localSender) {
sender = selectSender(global, forwardedMessage); localSender = selectSender(global, forwardedMessage);
} }
return sender; return localSender;
}); });
const forwardSenders = useMemo(() => { const forwardSenders = useMemo(() => {
@ -159,7 +145,7 @@ const ComposerEmbeddedMessage = ({
})(); })();
const { const {
shouldRender, transitionClassNames, shouldRender, transitionClassNames, isClosing,
} = useShowTransitionDeprecated( } = useShowTransitionDeprecated(
isShown && !isReplyToTopicStart && !isReplyToDiscussion, isShown && !isReplyToTopicStart && !isReplyToDiscussion,
undefined, undefined,
@ -169,6 +155,35 @@ const ComposerEmbeddedMessage = ({
CLOSE_DURATION, CLOSE_DURATION,
!shouldAnimate, !shouldAnimate,
); );
const {
chatId,
currentUserId,
theme,
onClear,
isCurrentUserPremium,
isContextMenuDisabled,
shouldPreventComposerAnimation,
sender,
senderChat,
isMediaNsfw,
noAuthors,
noCaptions,
forwardsHaveCaptions,
forwardedMessagesCount: frozenForwardedMessagesCount,
message: frozenMessage,
shouldForceShowEditing: frozenShouldForceShowEditing,
suggestedPostInfo: frozenSuggestedPostInfo,
replyInfo: frozenReplyInfo,
editingId: frozenEditingId,
isSenderChannel,
} = useFrozenProps(props, isClosing);
const isForwardingRendering = Boolean(frozenForwardedMessagesCount);
const isShowingReplyRendering = Boolean(frozenReplyInfo) && !frozenShouldForceShowEditing;
const isReplyWithQuoteRendering = Boolean(frozenReplyInfo?.quoteText);
const isShowingSuggestedPostRendering = Boolean(frozenSuggestedPostInfo) && !frozenShouldForceShowEditing;
useEffect(() => { useEffect(() => {
if (shouldPreventComposerAnimation) { if (shouldPreventComposerAnimation) {
setShouldPreventComposerAnimation({ shouldPreventComposerAnimation: false }); setShouldPreventComposerAnimation({ shouldPreventComposerAnimation: false });
@ -176,14 +191,14 @@ const ComposerEmbeddedMessage = ({
}); });
const clearEmbedded = useLastCallback(() => { const clearEmbedded = useLastCallback(() => {
if (editingId) { if (frozenEditingId) {
setEditingId({ messageId: undefined }); setEditingId({ messageId: undefined });
} else if (forwardedMessagesCount) { } else if (frozenForwardedMessagesCount) {
exitForwardMode(); exitForwardMode();
} else if (isShowingSuggestedPost) { } else if (isShowingSuggestedPostRendering) {
resetDraftSuggestedPostInfo(); resetDraftSuggestedPostInfo();
resetDraftReplyInfo(); resetDraftReplyInfo();
} else if (replyInfo && !shouldForceShowEditing) { } else if (frozenReplyInfo && !frozenShouldForceShowEditing) {
resetDraftReplyInfo(); resetDraftReplyInfo();
} }
onClear?.(); onClear?.();
@ -197,10 +212,10 @@ const ComposerEmbeddedMessage = ({
} = useContextMenuHandlers(ref); } = useContextMenuHandlers(ref);
const focusMessageFromDraft = () => { const focusMessageFromDraft = () => {
focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true }); focusMessage({ chatId: frozenMessage!.chatId, messageId: frozenMessage!.id, noForumTopicPanel: true });
}; };
const handleMessageClick = useLastCallback((e: React.MouseEvent): void => { const handleMessageClick = useLastCallback((e: React.MouseEvent): void => {
if (suggestedPostInfo) { if (frozenSuggestedPostInfo) {
openSuggestMessageModal({ chatId }); openSuggestMessageModal({ chatId });
return; return;
} }
@ -231,6 +246,13 @@ const ComposerEmbeddedMessage = ({
}); });
const handleDoNotReplyClick = useLastCallback(buildAutoCloseMenuItemHandler(clearEmbedded)); const handleDoNotReplyClick = useLastCallback(buildAutoCloseMenuItemHandler(clearEmbedded));
const handleIconKeyDown = useLastCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleContextMenu(e as unknown as React.MouseEvent);
}
});
const getTriggerElement = useLastCallback(() => ref.current); const getTriggerElement = useLastCallback(() => ref.current);
const getRootElement = useLastCallback(() => ref.current!); const getRootElement = useLastCallback(() => ref.current!);
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.forward-context-menu .bubble')); const getMenuElement = useLastCallback(() => ref.current!.querySelector('.forward-context-menu .bubble'));
@ -243,47 +265,47 @@ const ComposerEmbeddedMessage = ({
}, [handleContextMenuClose, handleContextMenuHide, shouldRender]); }, [handleContextMenuClose, handleContextMenuHide, shouldRender]);
const className = buildClassName('ComposerEmbeddedMessage', transitionClassNames); const className = buildClassName('ComposerEmbeddedMessage', transitionClassNames);
const renderingSender = useCurrentOrPrev(sender, true);
const { className: peerColorClass, style: peerColorStyle } = usePeerColor({ const { className: peerColorClass, style: peerColorStyle } = usePeerColor({
peer: renderingSender, peer: sender,
theme, theme,
}); });
const innerClassName = buildClassName('ComposerEmbeddedMessage_inner', peerColorClass); const innerClassName = buildClassName('ComposerEmbeddedMessage_inner', peerColorClass);
const leftIcon = useMemo(() => { const leftIcon = useMemo(() => {
if (editingId) { if (frozenEditingId) {
return 'edit'; return 'edit';
} }
if (isShowingSuggestedPost) { if (isShowingSuggestedPostRendering) {
return 'cash-circle'; return 'cash-circle';
} }
if (isForwarding) { if (isForwardingRendering) {
return 'forward'; return 'forward';
} }
if (isShowingReply) { if (isShowingReplyRendering) {
return 'reply'; return 'reply';
} }
return undefined; return undefined;
}, [editingId, isForwarding, isShowingReply, isShowingSuggestedPost]); }, [frozenEditingId, isForwardingRendering, isShowingReplyRendering, isShowingSuggestedPostRendering]);
const customText = forwardedMessagesCount && forwardedMessagesCount > 1 const customText = frozenForwardedMessagesCount && frozenForwardedMessagesCount > 1
? oldLang('ForwardedMessageCount', forwardedMessagesCount) ? oldLang('ForwardedMessageCount', frozenForwardedMessagesCount)
: undefined; : undefined;
const strippedMessage = useMemo(() => { const strippedMessage = useMemo(() => {
if (!message || !isForwarding || !message.content.text || !noAuthors || isCurrentUserPremium) return message; if (!frozenMessage || !isForwardingRendering || !frozenMessage.content.text
|| !noAuthors || isCurrentUserPremium) return frozenMessage;
const strippedText = stripCustomEmoji(message.content.text); const strippedText = stripCustomEmoji(frozenMessage.content.text);
return { return {
...message, ...frozenMessage,
content: { content: {
...message.content, ...frozenMessage.content,
text: strippedText, text: strippedText,
}, },
}; };
}, [isCurrentUserPremium, isForwarding, message, noAuthors]); }, [isCurrentUserPremium, isForwardingRendering, frozenMessage, noAuthors]);
const renderingLeftIcon = useCurrentOrPrev(leftIcon, true); const renderingLeftIcon = useCurrentOrPrev(leftIcon, true);
@ -291,22 +313,29 @@ const ComposerEmbeddedMessage = ({
return undefined; return undefined;
} }
const canReplyInSenderChat = sender && !isSenderChannel && chatId !== sender.id && sender.id !== currentUserId; const canReplyInSenderChat = sender && !isSenderChannel
&& chatId !== sender.id && sender.id !== currentUserId;
return ( return (
<div className={className} ref={ref} onContextMenu={handleContextMenu}> <div className={className} ref={ref} onContextMenu={handleContextMenu}>
<div className={innerClassName} style={peerColorStyle}> <div className={innerClassName} style={peerColorStyle}>
<div className="embedded-left-icon" onClick={handleContextMenu}> <div
className="embedded-left-icon"
role="button"
tabIndex={0}
onClick={handleContextMenu}
onKeyDown={handleIconKeyDown}
>
{renderingLeftIcon && <Icon name={renderingLeftIcon} />} {renderingLeftIcon && <Icon name={renderingLeftIcon} />}
{Boolean(replyInfo?.quoteText) && ( {Boolean(frozenReplyInfo?.quoteText) && (
<Icon name="quote" className="quote-reply" /> <Icon name="quote" className="quote-reply" />
)} )}
</div> </div>
<EmbeddedMessage <EmbeddedMessage
isOpen={isShown} isOpen={isShown}
className="inside-input" className="inside-input"
replyInfo={replyInfo} replyInfo={frozenReplyInfo}
suggestedPostInfo={suggestedPostInfo} suggestedPostInfo={frozenSuggestedPostInfo}
isMediaNsfw={isMediaNsfw} isMediaNsfw={isMediaNsfw}
isInComposer isInComposer
message={strippedMessage} message={strippedMessage}
@ -314,7 +343,7 @@ const ComposerEmbeddedMessage = ({
composerForwardSenders={forwardSenders} composerForwardSenders={forwardSenders}
customText={customText} customText={customText}
noCaptions={noCaptions} noCaptions={noCaptions}
title={(editingId && !isShowingReply) ? oldLang('EditMessage') title={(frozenEditingId && !isShowingReplyRendering) ? oldLang('EditMessage')
: noAuthors ? oldLang('HiddenSendersNameDescription') : undefined} : noAuthors ? oldLang('HiddenSendersNameDescription') : undefined}
onClick={handleMessageClick} onClick={handleMessageClick}
senderChat={senderChat} senderChat={senderChat}
@ -328,7 +357,7 @@ const ComposerEmbeddedMessage = ({
onClick={handleClearClick} onClick={handleClearClick}
iconName="close" iconName="close"
/> />
{(isShowingReply || isForwarding) && !isContextMenuDisabled && ( {(isShowingReplyRendering || isForwardingRendering) && !isContextMenuDisabled && (
<Menu <Menu
isOpen={isContextMenuOpen} isOpen={isContextMenuOpen}
anchor={contextMenuAnchor} anchor={contextMenuAnchor}
@ -339,7 +368,7 @@ const ComposerEmbeddedMessage = ({
onClose={handleContextMenuClose} onClose={handleContextMenuClose}
onCloseAnimationEnd={handleContextMenuHide} onCloseAnimationEnd={handleContextMenuHide}
> >
{isForwarding && ( {isForwardingRendering && (
<> <>
<MenuItem <MenuItem
icon={!noAuthors ? 'message-succeeded' : undefined} icon={!noAuthors ? 'message-succeeded' : undefined}
@ -349,7 +378,7 @@ const ComposerEmbeddedMessage = ({
noAuthors: false, noAuthors: false,
})} })}
> >
{oldLang(forwardedMessagesCount > 1 ? 'ShowSenderNames' : 'ShowSendersName')} {oldLang(frozenForwardedMessagesCount > 1 ? 'ShowSenderNames' : 'ShowSendersName')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon={noAuthors ? 'message-succeeded' : undefined} icon={noAuthors ? 'message-succeeded' : undefined}
@ -359,7 +388,7 @@ const ComposerEmbeddedMessage = ({
noAuthors: true, noAuthors: true,
})} })}
> >
{oldLang(forwardedMessagesCount > 1 ? 'HideSenderNames' : 'HideSendersName')} {oldLang(frozenForwardedMessagesCount > 1 ? 'HideSenderNames' : 'HideSendersName')}
</MenuItem> </MenuItem>
{forwardsHaveCaptions && ( {forwardsHaveCaptions && (
<> <>
@ -372,7 +401,8 @@ const ComposerEmbeddedMessage = ({
noCaptions: false, noCaptions: false,
})} })}
> >
{oldLang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.ShowCaption' : 'ShowCaption')} {oldLang(frozenForwardedMessagesCount > 1
? 'Conversation.ForwardOptions.ShowCaption' : 'ShowCaption')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon={noCaptions ? 'message-succeeded' : undefined} icon={noCaptions ? 'message-succeeded' : undefined}
@ -382,7 +412,8 @@ const ComposerEmbeddedMessage = ({
noCaptions: true, noCaptions: true,
})} })}
> >
{oldLang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.HideCaption' : 'HideCaption')} {oldLang(frozenForwardedMessagesCount > 1
? 'Conversation.ForwardOptions.HideCaption' : 'HideCaption')}
</MenuItem> </MenuItem>
</> </>
)} )}
@ -392,7 +423,7 @@ const ComposerEmbeddedMessage = ({
</MenuItem> </MenuItem>
</> </>
)} )}
{isShowingReply && ( {isShowingReplyRendering && (
<> <>
<MenuItem <MenuItem
icon="show-message" icon="show-message"
@ -400,7 +431,7 @@ const ComposerEmbeddedMessage = ({
> >
{oldLang('Message.Context.Goto')} {oldLang('Message.Context.Goto')}
</MenuItem> </MenuItem>
{isReplyWithQuote && ( {isReplyWithQuoteRendering && (
<MenuItem <MenuItem
icon="remove-quote" icon="remove-quote"
onClick={handleRemoveQuoteClick} onClick={handleRemoveQuoteClick}

View File

@ -0,0 +1,24 @@
import { useRef } from '../lib/teact/teact';
export default function useFrozenProps<T extends Record<string, unknown>>(
props: T,
shouldFreeze: boolean,
alwaysFreshKeys?: readonly (keyof T)[],
): T {
const frozenRef = useRef<T>(props);
if (!shouldFreeze) {
frozenRef.current = props;
} else if (alwaysFreshKeys?.length) {
const updates: Partial<T> = {};
for (const key of alwaysFreshKeys) {
updates[key] = props[key];
}
frozenRef.current = {
...frozenRef.current,
...updates,
};
}
return frozenRef.current;
}