diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 30544203..295bc4b8 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -15,7 +15,8 @@ import { ApiUser, ApiChat, ApiSticker, - ApiThreadInfo, ApiAvailableReaction, + ApiThreadInfo, + ApiAvailableReaction, } from '../../../api/types'; import { AudioOrigin, FocusDirection, IAlbum, ISettings, @@ -147,10 +148,10 @@ type StateProps = { isThreadTop?: boolean; shouldHideReply?: boolean; replyMessage?: ApiMessage; - reactionsMessage?: ApiMessage; replyMessageSender?: ApiUser | ApiChat; outgoingStatus?: ApiMessageOutgoingStatus; uploadProgress?: number; + isInDocumentGroup: boolean; isProtected?: boolean; isFocused?: boolean; focusDirection?: FocusDirection; @@ -180,12 +181,23 @@ type StateProps = { shouldLoopStickers?: boolean; autoLoadFileMaxSizeMb: number; threadInfo?: ApiThreadInfo; + reactionMessage?: ApiMessage; + availableReactions?: ApiAvailableReaction[]; defaultReaction?: string; activeReaction?: ActiveReaction; activeEmojiInteraction?: ActiveEmojiInteraction; - availableReactions?: ApiAvailableReaction[]; }; +type MetaPosition = + 'in-text' + | 'standalone' + | 'none'; +type ReactionsPosition = + 'inside' + | 'outside' + | 'in-meta' + | 'none'; + const NBSP = '\u00A0'; // eslint-disable-next-line max-len const APPENDIX_OWN = { __html: '' }; @@ -205,7 +217,6 @@ const Message: FC = ({ withAvatar, withSenderName, areReactionsInMeta, - reactionsMessage, noComments, appearanceOrder, isFirstInGroup, @@ -224,6 +235,7 @@ const Message: FC = ({ replyMessageSender, outgoingStatus, uploadProgress, + isInDocumentGroup, isProtected, isFocused, focusDirection, @@ -245,10 +257,11 @@ const Message: FC = ({ isSelected, isGroupSelected, threadId, + reactionMessage, + availableReactions, defaultReaction, activeReaction, activeEmojiInteraction, - availableReactions, messageListType, isPinnedList, isDownloading, @@ -298,7 +311,9 @@ const Message: FC = ({ }, [appearanceOrder, markShown, noAppearanceAnimation]); const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false); - const { id: messageId, chatId } = message; + const { + id: messageId, chatId, forwardInfo, viaBotId, + } = message; const isLocal = isMessageLocal(message); const isOwn = isOwnMessage(message); @@ -306,7 +321,7 @@ const Message: FC = ({ const hasReply = isReplyMessage(message) && !shouldHideReply; const hasThread = Boolean(threadInfo) && messageListType === 'thread'; const customShape = getMessageCustomShape(message); - const { forwardInfo, viaBotId } = message; + const hasAnimatedEmoji = localSticker || animatedEmoji; const asForwarded = ( forwardInfo && (!isChatWithSelf || isScheduled) @@ -314,24 +329,13 @@ const Message: FC = ({ && !forwardInfo.isLinkedChannelPost && !customShape ); - const isInDocumentGroup = Boolean(message.groupedId) && !message.isInAlbum; const isAlbum = Boolean(album) && album!.messages.length > 1; - const { - text, photo, video, audio, voice, document, sticker, contact, poll, webPage, invoice, - } = getMessageContent(message); - const hasReactionButtons = !areReactionsInMeta && reactionsMessage?.reactions - && !areReactionsEmpty(reactionsMessage.reactions); - const textParts = renderMessageText(message, highlight, isEmojiOnlyMessage(customShape)); + const isInDocumentGroupNotFirst = isInDocumentGroup && !isFirstInDocumentGroup; + const isInDocumentGroupNotLast = isInDocumentGroup && !isLastInDocumentGroup; const isContextMenuShown = contextMenuPosition !== undefined; - const signature = ( - (isChannel && message.adminTitle) || (!asForwarded && forwardInfo?.adminTitle) || undefined - ); - const metaSafeAuthorWidth = useMemo(() => { - return signature ? calculateAuthorWidth(signature) : undefined; - }, [signature]); const canShowActionButton = ( !(isContextMenuShown || isInSelectMode || isForwarding) - && (!isInDocumentGroup || isLastInDocumentGroup) + && !isInDocumentGroupNotLast ); const canForward = isChannel && !isScheduled; const canFocus = Boolean(isPinnedList @@ -339,11 +343,9 @@ const Message: FC = ({ && (forwardInfo.isChannelPost || (isChatWithSelf && !isOwn) || isRepliesChat) && forwardInfo.fromMessageId )); - const avatarPeer = forwardInfo && (isChatWithSelf || isRepliesChat || !sender) ? originSender : sender; - const senderPeer = forwardInfo ? originSender : sender; - const hasAnimatedEmoji = localSticker || animatedEmoji; - const areReactionsOutside = hasReactionButtons - && (asForwarded || customShape || ((photo || video || hasAnimatedEmoji) && !textParts)); + + const withCommentButton = threadInfo && !isInDocumentGroupNotLast && messageListType === 'thread' && !noComments; + const withQuickReactionButton = !IS_TOUCH_ENV && defaultReaction && !isInDocumentGroupNotLast; const selectMessage = useCallback((e?: React.MouseEvent, groupedId?: string) => { toggleMessageSelection({ @@ -354,6 +356,9 @@ const Message: FC = ({ }); }, [toggleMessageSelection, messageId, isAlbum, album]); + const avatarPeer = forwardInfo && (isChatWithSelf || isRepliesChat || !sender) ? originSender : sender; + const senderPeer = forwardInfo ? originSender : sender; + const { handleMouseDown, handleClick, @@ -380,7 +385,7 @@ const Message: FC = ({ isContextMenuShown, contentRef, isOwn, - isInDocumentGroup && !isLastInDocumentGroup, + isInDocumentGroupNotLast, ); const { @@ -453,11 +458,35 @@ const Message: FC = ({ hasComments: threadInfo && threadInfo?.messagesCount > 0, hasActionButton: canForward || canFocus, }); - const withCommentButton = ( - threadInfo && (!isInDocumentGroup || isLastInDocumentGroup) && messageListType === 'thread' && !noComments - ); + const withAppendix = contentClassName.includes('has-appendix'); - const withQuickReaction = !IS_TOUCH_ENV && defaultReaction && (!isInDocumentGroup || isLastInDocumentGroup); + const textParts = renderMessageText(message, highlight, isEmojiOnlyMessage(customShape)); + + const { + text, photo, video, audio, voice, document, sticker, contact, poll, webPage, invoice, + } = getMessageContent(message); + + let metaPosition!: MetaPosition; + if (isInDocumentGroupNotLast) { + metaPosition = 'none'; + } else if (textParts && !hasAnimatedEmoji && !webPage) { + metaPosition = 'in-text'; + } else { + metaPosition = 'standalone'; + } + + let reactionsPosition!: ReactionsPosition; + if (areReactionsInMeta) { + reactionsPosition = 'in-meta'; + } else if (reactionMessage?.reactions && !areReactionsEmpty(reactionMessage.reactions)) { + if (asForwarded || customShape || ((photo || video || hasAnimatedEmoji) && !textParts)) { + reactionsPosition = 'outside'; + } else { + reactionsPosition = 'inside'; + } + } else { + reactionsPosition = 'none'; + } useEnsureMessage( isRepliesChat && message.replyToChatId ? message.replyToChatId : chatId, @@ -504,6 +533,11 @@ const Message: FC = ({ style = `width: ${calculatedWidth + extraPadding}px`; } + const signature = (isChannel && message.adminTitle) || (!asForwarded && forwardInfo?.adminTitle) || undefined; + const metaSafeAuthorWidth = useMemo(() => { + return signature ? calculateAuthorWidth(signature) : undefined; + }, [signature]); + function renderAvatar() { const isAvatarPeerUser = avatarPeer && isUserId(avatarPeer.id); const avatarUser = (avatarPeer && isAvatarPeerUser) ? avatarPeer as ApiUser : undefined; @@ -522,16 +556,29 @@ const Message: FC = ({ ); } - function renderMeta(withReactionOffset = false) { - return ( + function renderReactionsAndMeta() { + const meta = ( + ); + + if (reactionsPosition !== 'inside') { + return meta; + } + + return ( + ); @@ -545,10 +592,9 @@ const Message: FC = ({ noMediaCorners && 'no-media-corners', ); const hasCustomAppendix = isLastInGroup && !textParts && !asForwarded && !hasThread; - const shouldInlineMeta = !webPage && !hasAnimatedEmoji && textParts; const textContentClass = buildClassName( 'text-content', - shouldInlineMeta && 'with-meta', + metaPosition === 'in-text' && 'with-meta', outgoingStatus && 'with-outgoing-icon', ); @@ -692,19 +738,7 @@ const Message: FC = ({ {!hasAnimatedEmoji && textParts && (

{textParts} - {shouldInlineMeta && ( - <> - {hasReactionButtons && !areReactionsOutside ? ( - - ) - : renderMeta()} - - )} + {metaPosition === 'in-text' && renderReactionsAndMeta()}

)} @@ -731,7 +765,7 @@ const Message: FC = ({ function renderSenderName() { const shouldRender = !(customShape && !viaBotId) && ( (withSenderName && !photo && !video) || asForwarded || viaBotId || forceSenderName - ) && (!isInDocumentGroup || isFirstInDocumentGroup) && !(hasReply && customShape); + ) && !isInDocumentGroupNotFirst && !(hasReply && customShape); if (!shouldRender) { return undefined; @@ -794,9 +828,9 @@ const Message: FC = ({ onClick={handleClick} onContextMenu={handleContextMenu} onDoubleClick={handleDoubleClick} - onMouseEnter={isInDocumentGroup && !isLastInDocumentGroup ? handleDocumentGroupMouseEnter : undefined} - onMouseMove={withQuickReaction ? handleMouseMove : undefined} - onMouseLeave={(withQuickReaction || (isInDocumentGroup && !isLastInDocumentGroup)) ? handleMouseLeave : undefined} + onMouseEnter={isInDocumentGroupNotLast ? handleDocumentGroupMouseEnter : undefined} + onMouseMove={withQuickReactionButton ? handleMouseMove : undefined} + onMouseLeave={(withQuickReactionButton || isInDocumentGroupNotLast) ? handleMouseLeave : undefined} >
= ({ style={style} dir="auto" > - {asForwarded && (!isInDocumentGroup || isFirstInDocumentGroup) && ( + {asForwarded && !isInDocumentGroupNotFirst && (
{lang('ForwardedMessage')}
)} {renderContent()} - {(!isInDocumentGroup || isLastInDocumentGroup) && !(!webPage && !hasAnimatedEmoji && textParts) && ( - <> - {hasReactionButtons && !areReactionsOutside && (hasAnimatedEmoji || !textParts || webPage) ? ( - - ) : renderMeta()} - - )} + {!isInDocumentGroupNotLast && metaPosition === 'standalone' && renderReactionsAndMeta()} {canShowActionButton && canForward ? (