From 5c2e5dfcb4b5879118fc353dc350ea8f72503ab5 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 14 May 2025 19:02:25 +0300 Subject: [PATCH] Composer Embedded Message: Support multi senders title in forward (#5898) --- src/assets/localization/fallback.strings | 1 + .../common/embedded/EmbeddedMessage.scss | 6 +++ .../common/embedded/EmbeddedMessage.tsx | 34 ++++++++++--- .../composer/ComposerEmbeddedMessage.tsx | 50 ++++++++++++++----- src/types/language.d.ts | 3 ++ 5 files changed, 75 insertions(+), 19 deletions(-) diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index a647fe85a..24e37abdd 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -1953,4 +1953,5 @@ "ApiMessageActionPaidMessagesRefundedIncoming" = "{user} refunded **{stars}** to you"; "NotificationTitleNotSupportedInFrozenAccount" = "Your account is frozen"; "NotificationMessageNotSupportedInFrozenAccount" = "This action is not available"; +"ComposerTitleForwardFrom" = "From: **{users}**"; "ContextMenuItemMention" = "Mention"; diff --git a/src/components/common/embedded/EmbeddedMessage.scss b/src/components/common/embedded/EmbeddedMessage.scss index 6507b8130..cbda0fd7b 100644 --- a/src/components/common/embedded/EmbeddedMessage.scss +++ b/src/components/common/embedded/EmbeddedMessage.scss @@ -233,4 +233,10 @@ color: var(--accent-color); } } + + &.is-input-forward { + .message-title { + font-weight: var(--font-weight-normal); + } + } } diff --git a/src/components/common/embedded/EmbeddedMessage.tsx b/src/components/common/embedded/EmbeddedMessage.tsx index 3937166b2..a0310b2c3 100644 --- a/src/components/common/embedded/EmbeddedMessage.tsx +++ b/src/components/common/embedded/EmbeddedMessage.tsx @@ -29,6 +29,7 @@ import { renderTextWithEntities } from '../helpers/renderTextWithEntities'; import { useFastClick } from '../../../hooks/useFastClick'; import { useIsIntersecting } from '../../../hooks/useIntersectionObserver'; +import useLang from '../../../hooks/useLang'; import useMedia from '../../../hooks/useMedia'; import useOldLang from '../../../hooks/useOldLang'; import useThumbnail from '../../../hooks/useThumbnail'; @@ -49,6 +50,7 @@ type OwnProps = { sender?: ApiPeer; senderChat?: ApiChat; forwardSender?: ApiPeer; + composerForwardSenders?: ApiPeer[]; title?: string; customText?: string; noUserColors?: boolean; @@ -72,6 +74,7 @@ const EmbeddedMessage: FC = ({ sender, senderChat, forwardSender, + composerForwardSenders, title, customText, isProtected, @@ -115,12 +118,21 @@ const EmbeddedMessage: FC = ({ chatTranslations, message?.chatId, shouldTranslate ? message?.id : undefined, requestedChatTranslationLanguage, ); - const lang = useOldLang(); + const oldLang = useOldLang(); + const lang = useLang(); - const senderTitle = sender ? getPeerTitle(lang, sender) + const senderTitle = sender ? getPeerTitle(oldLang, sender) : (replyForwardInfo?.hiddenUserName || message?.forwardInfo?.hiddenUserName); - const senderChatTitle = senderChat ? getPeerTitle(lang, senderChat) : undefined; - const forwardSenderTitle = forwardSender ? getPeerTitle(lang, forwardSender) + + const forwardSendersTitle = useMemo(() => { + if (!composerForwardSenders) return undefined; + + const peerTitles = composerForwardSenders.map((peer) => getPeerTitle(lang, peer)).filter(Boolean); + return lang.conjunction(peerTitles); + }, [composerForwardSenders, lang]); + + const senderChatTitle = senderChat ? getPeerTitle(oldLang, senderChat) : undefined; + const forwardSenderTitle = forwardSender ? getPeerTitle(oldLang, forwardSender) : message?.forwardInfo?.hiddenUserName; const areSendersSame = sender && sender.id === forwardSender?.id; @@ -154,7 +166,7 @@ const EmbeddedMessage: FC = ({ function renderMediaContentType(media?: MediaContainer) { if (!media || media.content.text) return NBSP; - const description = getMediaContentTypeDescription(lang, media.content, {}); + const description = getMediaContentTypeDescription(oldLang, media.content, {}); if (!description || description === CONTENT_NOT_SUPPORTED) return NBSP; return ( @@ -174,7 +186,7 @@ const EmbeddedMessage: FC = ({ return renderText(title); } - if (!senderTitle) { + if (!senderTitle && !forwardSendersTitle) { return NBSP; } @@ -195,7 +207,14 @@ const EmbeddedMessage: FC = ({ {checkShouldRenderSenderTitle() && ( - {renderText(isReplyToQuote ? lang('ReplyToQuote', senderTitle) : senderTitle)} + {!composerForwardSenders && senderTitle + && renderText(isReplyToQuote ? oldLang('ReplyToQuote', senderTitle) : senderTitle)} + {forwardSendersTitle && renderText(lang('ComposerTitleForwardFrom', { + users: forwardSendersTitle, + }, { + withNodes: true, + withMarkdown: true, + }))} )} {icon && } @@ -232,6 +251,7 @@ const EmbeddedMessage: FC = ({ isQuote && 'is-quote', mediaThumbnail && 'with-thumb', 'no-selection', + composerForwardSenders && 'is-input-forward', )} dir={lang.isRtl ? 'rtl' : undefined} onClick={handleClick} diff --git a/src/components/middle/composer/ComposerEmbeddedMessage.tsx b/src/components/middle/composer/ComposerEmbeddedMessage.tsx index b1c3e8b06..99a076f5f 100644 --- a/src/components/middle/composer/ComposerEmbeddedMessage.tsx +++ b/src/components/middle/composer/ComposerEmbeddedMessage.tsx @@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useRef, } from '../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../global'; +import { getActions, getGlobal, withGlobal } from '../../../global'; import type { ApiChat, ApiInputMessageReplyInfo, ApiMessage, ApiPeer, @@ -21,12 +21,12 @@ import { selectForwardedSender, selectIsChatWithSelf, selectIsCurrentUserPremium, - selectPeer, selectSender, selectTabState, } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; +import { unique } from '../../../util/iteratees'; import { getPeerColorClass } from '../../common/helpers/peerColor'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; @@ -63,6 +63,8 @@ type StateProps = { senderChat?: ApiChat; isSenderChannel?: boolean; currentUserId?: string; + forwardMessageIds?: number[]; + fromChatId?: string; }; type OwnProps = { @@ -96,6 +98,8 @@ const ComposerEmbeddedMessage: FC = ({ chatId, currentUserId, isSenderChannel, + forwardMessageIds, + fromChatId, }) => { const { resetDraftReplyInfo, @@ -120,10 +124,27 @@ const ComposerEmbeddedMessage: FC = ({ const isForwarding = Boolean(forwardedMessagesCount); + const selectSenderFromForwardedMessage = useLastCallback((forwardedMessage: ApiMessage) => { + const global = getGlobal(); + sender = selectForwardedSender(global, forwardedMessage); + if (!sender) { + sender = selectSender(global, forwardedMessage); + } + return sender; + }); + + const forwardSenders = useMemo(() => { + if (!isForwarding) return undefined; + const forwardedMessages = forwardMessageIds?.map((id) => selectChatMessage(getGlobal(), fromChatId!, id)) + .filter(Boolean); + const senders = forwardedMessages?.map((m) => selectSenderFromForwardedMessage(m)).filter(Boolean); + return senders ? unique(senders) : undefined; + }, [isForwarding, forwardMessageIds, fromChatId]); + const isShown = (() => { if (isInChangingRecipientMode) return false; if (message && (replyInfo || editingId)) return true; - if (sender && isForwarding) return true; + if (forwardSenders && isForwarding) return true; return false; })(); @@ -266,6 +287,7 @@ const ComposerEmbeddedMessage: FC = ({ isInComposer message={strippedMessage} sender={!noAuthors ? sender : undefined} + composerForwardSenders={forwardSenders} customText={customText} title={(editingId && !isShowingReply) ? oldLang('EditMessage') : noAuthors ? oldLang('HiddenSendersNameDescription') : undefined} @@ -417,18 +439,20 @@ export default memo(withGlobal( let sender: ApiPeer | undefined; + const selectSenderFromForwardedMessage = (forwardedMessage: ApiMessage) => { + sender = selectForwardedSender(global, forwardedMessage); + if (!sender) { + sender = selectSender(global, forwardedMessage); + } + return sender; + }; + if (editingId && message) { sender = selectSender(global, message); } else if (isForwarding) { - if (message) { - sender = selectForwardedSender(global, message); - if (!sender) { - sender = selectSender(global, message); - } - } - if (!sender) { - sender = selectPeer(global, fromChatId!); - } + let forwardSenders = forwardedMessages?.map((m) => selectSenderFromForwardedMessage(m)).filter(Boolean); + forwardSenders = forwardSenders ? unique(forwardSenders) : undefined; + sender = forwardSenders?.length === 1 ? forwardSenders?.[0] : undefined; } else if (replyInfo && message && !shouldForceShowEditing) { const { forwardInfo } = message; const isChatWithSelf = selectIsChatWithSelf(global, chatId); @@ -471,6 +495,8 @@ export default memo(withGlobal( senderChat, currentUserId: global.currentUserId, isSenderChannel, + forwardMessageIds, + fromChatId, }; }, )(ComposerEmbeddedMessage)); diff --git a/src/types/language.d.ts b/src/types/language.d.ts index aaac897ea..744289f32 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -2411,6 +2411,9 @@ export interface LangPairWithVariables { 'user': V; 'stars': V; }; + 'ComposerTitleForwardFrom': { + 'users': V; + }; } export interface LangPairPlural {