Composer Embedded Message: Support multi senders title in forward (#5898)

This commit is contained in:
Alexander Zinchuk 2025-05-14 19:02:25 +03:00
parent a8ab4942d0
commit 5c2e5dfcb4
5 changed files with 75 additions and 19 deletions

View File

@ -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";

View File

@ -233,4 +233,10 @@
color: var(--accent-color);
}
}
&.is-input-forward {
.message-title {
font-weight: var(--font-weight-normal);
}
}
}

View File

@ -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<OwnProps> = ({
sender,
senderChat,
forwardSender,
composerForwardSenders,
title,
customText,
isProtected,
@ -115,12 +118,21 @@ const EmbeddedMessage: FC<OwnProps> = ({
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<OwnProps> = ({
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 (
<span>
@ -174,7 +186,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
return renderText(title);
}
if (!senderTitle) {
if (!senderTitle && !forwardSendersTitle) {
return NBSP;
}
@ -195,7 +207,14 @@ const EmbeddedMessage: FC<OwnProps> = ({
<span className="embedded-sender-wrapper">
{checkShouldRenderSenderTitle() && (
<span className="embedded-sender">
{renderText(isReplyToQuote ? lang('ReplyToQuote', senderTitle) : senderTitle)}
{!composerForwardSenders && senderTitle
&& renderText(isReplyToQuote ? oldLang('ReplyToQuote', senderTitle) : senderTitle)}
{forwardSendersTitle && renderText(lang('ComposerTitleForwardFrom', {
users: forwardSendersTitle,
}, {
withNodes: true,
withMarkdown: true,
}))}
</span>
)}
{icon && <Icon name={icon} className="embedded-chat-icon" />}
@ -232,6 +251,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
isQuote && 'is-quote',
mediaThumbnail && 'with-thumb',
'no-selection',
composerForwardSenders && 'is-input-forward',
)}
dir={lang.isRtl ? 'rtl' : undefined}
onClick={handleClick}

View File

@ -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<OwnProps & StateProps> = ({
chatId,
currentUserId,
isSenderChannel,
forwardMessageIds,
fromChatId,
}) => {
const {
resetDraftReplyInfo,
@ -120,10 +124,27 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
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<OwnProps>(
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<OwnProps>(
senderChat,
currentUserId: global.currentUserId,
isSenderChannel,
forwardMessageIds,
fromChatId,
};
},
)(ComposerEmbeddedMessage));

View File

@ -2411,6 +2411,9 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
'user': V;
'stars': V;
};
'ComposerTitleForwardFrom': {
'users': V;
};
}
export interface LangPairPlural {