TelegramPWA/src/components/common/embedded/EmbeddedMessage.tsx
2023-11-12 12:49:20 +04:00

247 lines
7.3 KiB
TypeScript

import type { FC } from '../../../lib/teact/teact';
import React, { useMemo, useRef } from '../../../lib/teact/teact';
import type {
ApiChat,
ApiMessage, ApiPeer, ApiReplyInfo,
} from '../../../api/types';
import type { ChatTranslatedMessages } from '../../../global/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { IconName } from '../../../types/icons';
import {
getMessageIsSpoiler,
getMessageMediaHash,
getMessageRoundVideo,
getSenderTitle,
isActionMessage,
isChatChannel,
isChatGroup,
isMessageTranslatable,
} from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName';
import { getPictogramDimensions } from '../helpers/mediaDimensions';
import { getPeerColorClass } from '../helpers/peerColor';
import renderText from '../helpers/renderText';
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 useThumbnail from '../../../hooks/useThumbnail';
import useMessageTranslation from '../../middle/message/hooks/useMessageTranslation';
import ActionMessage from '../../middle/ActionMessage';
import Icon from '../Icon';
import MediaSpoiler from '../MediaSpoiler';
import MessageSummary from '../MessageSummary';
import EmojiIconBackground from './EmojiIconBackground';
import './EmbeddedMessage.scss';
type OwnProps = {
className?: string;
replyInfo?: ApiReplyInfo;
message?: ApiMessage;
sender?: ApiPeer;
senderChat?: ApiChat;
forwardSender?: ApiPeer;
title?: string;
customText?: string;
noUserColors?: boolean;
isProtected?: boolean;
chatTranslations?: ChatTranslatedMessages;
requestedChatTranslationLanguage?: string;
observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn;
onClick: NoneToVoidFunction;
};
const NBSP = '\u00A0';
const EmbeddedMessage: FC<OwnProps> = ({
className,
message,
replyInfo,
sender,
senderChat,
forwardSender,
title,
customText,
isProtected,
noUserColors,
chatTranslations,
requestedChatTranslationLanguage,
observeIntersectionForLoading,
observeIntersectionForPlaying,
onClick,
}) => {
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
const isIntersecting = useIsIntersecting(ref, observeIntersectionForLoading);
const wrappedMedia = useMemo(() => {
const replyMedia = replyInfo?.type === 'message' && replyInfo.replyMedia;
if (!replyMedia) return undefined;
return {
content: replyMedia,
};
}, [replyInfo]);
const mediaBlobUrl = useMedia(message && getMessageMediaHash(message, 'pictogram'), !isIntersecting);
const mediaThumbnail = useThumbnail(message || wrappedMedia);
const isRoundVideo = Boolean(message && getMessageRoundVideo(message));
const isSpoiler = Boolean(message && getMessageIsSpoiler(message));
const isQuote = Boolean(replyInfo?.type === 'message' && replyInfo.isQuote);
const replyForwardInfo = replyInfo?.type === 'message' ? replyInfo.replyFrom : undefined;
const shouldTranslate = message && isMessageTranslatable(message);
const { translatedText } = useMessageTranslation(
chatTranslations, message?.chatId, shouldTranslate ? message?.id : undefined, requestedChatTranslationLanguage,
);
const lang = useLang();
const senderTitle = sender ? getSenderTitle(lang, sender)
: (replyForwardInfo?.hiddenUserName || message?.forwardInfo?.hiddenUserName);
const senderChatTitle = senderChat ? getSenderTitle(lang, senderChat) : message?.forwardInfo?.hiddenUserName;
const forwardSenderTitle = forwardSender ? getSenderTitle(lang, forwardSender)
: message?.forwardInfo?.hiddenUserName;
const areSendersSame = sender?.id === forwardSender?.id;
const { handleClick, handleMouseDown } = useFastClick(onClick);
function renderTextContent() {
if (replyInfo?.type === 'message' && replyInfo.quoteText) {
return renderTextWithEntities({
text: replyInfo.quoteText.text,
entities: replyInfo.quoteText.entities,
});
}
if (!message) {
return customText || NBSP;
}
if (isActionMessage(message)) {
return (
<ActionMessage
message={message}
isEmbedded
observeIntersectionForLoading={observeIntersectionForLoading}
observeIntersectionForPlaying={observeIntersectionForPlaying}
/>
);
}
return (
<MessageSummary
lang={lang}
message={message}
noEmoji={Boolean(mediaThumbnail)}
translatedText={translatedText}
observeIntersectionForLoading={observeIntersectionForLoading}
observeIntersectionForPlaying={observeIntersectionForPlaying}
/>
);
}
function renderSender() {
if (title) {
return renderText(title);
}
if (!senderTitle) {
return NBSP;
}
let icon: IconName | undefined;
if (senderChat) {
if (isChatChannel(senderChat)) {
icon = 'channel-filled';
}
if (isChatGroup(senderChat)) {
icon = 'group-filled';
}
}
const isChatSender = senderChat?.id === sender?.id;
return (
<>
{!isChatSender && <span className="embedded-sender">{renderText(senderTitle)}</span>}
{icon && <Icon name={icon} className="embedded-chat-icon" />}
{icon && senderChatTitle && renderText(senderChatTitle)}
</>
);
}
return (
<div
ref={ref}
className={buildClassName(
'EmbeddedMessage',
className,
getPeerColorClass(sender, noUserColors, true),
isQuote && 'is-quote',
mediaThumbnail && 'with-thumb',
)}
dir={lang.isRtl ? 'rtl' : undefined}
onClick={handleClick}
onMouseDown={handleMouseDown}
>
{mediaThumbnail && renderPictogram(mediaThumbnail, mediaBlobUrl, isRoundVideo, isProtected, isSpoiler)}
{sender?.backgroundEmojiId && (
<EmojiIconBackground emojiDocumentId={sender.backgroundEmojiId} className="EmbeddedMessage--background-icons" />
)}
<div className="message-text">
<p className="embedded-text-wrapper">
{renderTextContent()}
</p>
<div className="message-title">
{renderSender()}
{forwardSenderTitle && !areSendersSame && (
<>
<Icon name={forwardSender ? 'share-filled' : 'forward'} className="embedded-origin-icon" />
{renderText(forwardSenderTitle)}
</>
)}
</div>
</div>
</div>
);
};
function renderPictogram(
thumbDataUri: string,
blobUrl?: string,
isRoundVideo?: boolean,
isProtected?: boolean,
isSpoiler?: boolean,
) {
const { width, height } = getPictogramDimensions();
const srcUrl = blobUrl || thumbDataUri;
return (
<div className={buildClassName('embedded-thumb', isRoundVideo && 'round')}>
{!isSpoiler && (
<img
src={srcUrl}
width={width}
height={height}
alt=""
className="pictogram"
draggable={false}
/>
)}
<MediaSpoiler thumbDataUri={srcUrl} isVisible={Boolean(isSpoiler)} width={width} height={height} />
{isProtected && <span className="protector" />}
</div>
);
}
export default EmbeddedMessage;