336 lines
11 KiB
TypeScript
336 lines
11 KiB
TypeScript
import type { FC } from '../../../lib/teact/teact';
|
|
import type React from '../../../lib/teact/teact';
|
|
import { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
|
import { getActions, withGlobal } from '../../../global';
|
|
|
|
import type { ApiMessage, ApiMessageWebPage, ApiTypeStory, ApiWebPage, ApiWebPageFull } from '../../../api/types';
|
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
|
import { AudioOrigin, type ThemeKey, type WebPageMediaSize } from '../../../types';
|
|
|
|
import { getPhotoFullDimensions } from '../../../global/helpers';
|
|
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
|
|
import buildClassName from '../../../util/buildClassName';
|
|
import { tryParseDeepLink } from '../../../util/deepLinkParser';
|
|
import trimText from '../../../util/trimText';
|
|
import renderText from '../../common/helpers/renderText';
|
|
import { getWebpageButtonLangKey } from './helpers/webpageType';
|
|
|
|
import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener';
|
|
import useEnsureStory from '../../../hooks/useEnsureStory';
|
|
import useLang from '../../../hooks/useLang';
|
|
import useLastCallback from '../../../hooks/useLastCallback';
|
|
|
|
import Audio from '../../common/Audio';
|
|
import Document from '../../common/Document';
|
|
import EmojiIconBackground from '../../common/embedded/EmojiIconBackground';
|
|
import PeerColorWrapper from '../../common/PeerColorWrapper';
|
|
import SafeLink from '../../common/SafeLink';
|
|
import StickerView from '../../common/StickerView';
|
|
import Button from '../../ui/Button';
|
|
import BaseStory from './BaseStory';
|
|
import Photo from './Photo';
|
|
import Video from './Video';
|
|
import WebPageUniqueGift from './WebPageUniqueGift';
|
|
|
|
import './WebPage.scss';
|
|
|
|
const MAX_TEXT_LENGTH = 170; // symbols
|
|
const WEBPAGE_STORY_TYPE = 'telegram_story';
|
|
const WEBPAGE_GIFT_TYPE = 'telegram_nft';
|
|
const STICKER_SIZE = 80;
|
|
const EMOJI_SIZE = 38;
|
|
|
|
type OwnProps = {
|
|
messageWebPage: ApiMessageWebPage;
|
|
webPage: ApiWebPage;
|
|
message?: ApiMessage;
|
|
noAvatars?: boolean;
|
|
canAutoLoad?: boolean;
|
|
canAutoPlay?: boolean;
|
|
asForwarded?: boolean;
|
|
isDownloading?: boolean;
|
|
isProtected?: boolean;
|
|
isConnected?: boolean;
|
|
backgroundEmojiId?: string;
|
|
theme: ThemeKey;
|
|
story?: ApiTypeStory;
|
|
shouldWarnAboutFiles?: boolean;
|
|
autoLoadFileMaxSizeMb?: number;
|
|
lastPlaybackTimestamp?: number;
|
|
observeIntersectionForLoading?: ObserveFn;
|
|
observeIntersectionForPlaying?: ObserveFn;
|
|
onAudioPlay?: NoneToVoidFunction;
|
|
onMediaClick?: NoneToVoidFunction;
|
|
onDocumentClick?: NoneToVoidFunction;
|
|
onCancelMediaTransfer?: NoneToVoidFunction;
|
|
onContainerClick?: ((e: React.MouseEvent) => void);
|
|
};
|
|
type StateProps = {
|
|
canPlayAnimatedEmojis: boolean;
|
|
};
|
|
|
|
const WebPage: FC<OwnProps & StateProps> = ({
|
|
messageWebPage,
|
|
webPage,
|
|
message,
|
|
noAvatars,
|
|
canAutoLoad,
|
|
canAutoPlay,
|
|
asForwarded,
|
|
isDownloading = false,
|
|
isProtected,
|
|
isConnected,
|
|
story,
|
|
theme,
|
|
backgroundEmojiId,
|
|
shouldWarnAboutFiles,
|
|
autoLoadFileMaxSizeMb,
|
|
lastPlaybackTimestamp,
|
|
observeIntersectionForLoading,
|
|
observeIntersectionForPlaying,
|
|
onMediaClick,
|
|
onDocumentClick,
|
|
onContainerClick,
|
|
onAudioPlay,
|
|
onCancelMediaTransfer,
|
|
}) => {
|
|
const { openUrl, openTelegramLink } = getActions();
|
|
const stickersRef = useRef<HTMLDivElement>();
|
|
|
|
const lang = useLang();
|
|
|
|
const handleMediaClick = useLastCallback(() => {
|
|
onMediaClick!();
|
|
});
|
|
|
|
const handleContainerClick = useLastCallback((e: React.MouseEvent) => {
|
|
onContainerClick?.(e);
|
|
});
|
|
|
|
const fullWebPage = webPage?.webpageType === 'full' ? webPage : undefined;
|
|
|
|
const { story: storyData, stickers } = fullWebPage || {};
|
|
|
|
useEnsureStory(storyData?.peerId, storyData?.id, story);
|
|
|
|
const hasCustomColor = stickers?.isWithTextColor || stickers?.documents?.[0]?.shouldUseTextColor;
|
|
const customColor = useDynamicColorListener(stickersRef, undefined, !hasCustomColor);
|
|
|
|
const linkTimestamp = useMemo(() => {
|
|
const parsedLink = webPage?.url && tryParseDeepLink(webPage?.url);
|
|
if (!parsedLink || !('timestamp' in parsedLink)) return undefined;
|
|
return parsedLink.timestamp;
|
|
}, [webPage?.url]);
|
|
|
|
if (webPage?.webpageType !== 'full') return undefined;
|
|
|
|
const handleOpenTelegramLink = useLastCallback(() => {
|
|
openTelegramLink({
|
|
url: webPage.url,
|
|
});
|
|
});
|
|
|
|
const {
|
|
siteName,
|
|
url,
|
|
displayUrl,
|
|
title,
|
|
description,
|
|
photo,
|
|
video,
|
|
audio,
|
|
type,
|
|
document,
|
|
} = webPage;
|
|
const { mediaSize } = messageWebPage;
|
|
const isStory = type === WEBPAGE_STORY_TYPE;
|
|
const isGift = type === WEBPAGE_GIFT_TYPE;
|
|
const isExpiredStory = story && 'isDeleted' in story;
|
|
|
|
const resultType = stickers?.isEmoji ? 'telegram_emojiset' : type;
|
|
const quickButtonLangKey = !isExpiredStory ? getWebpageButtonLangKey(resultType) : undefined;
|
|
const quickButtonTitle = quickButtonLangKey && lang(quickButtonLangKey);
|
|
|
|
const truncatedDescription = trimText(description, MAX_TEXT_LENGTH);
|
|
const isArticle = Boolean(truncatedDescription || title || siteName);
|
|
let isSquarePhoto = Boolean(stickers);
|
|
if (isArticle && webPage?.photo && !webPage.video && !webPage.document) {
|
|
isSquarePhoto = getIsSmallPhoto(webPage, mediaSize);
|
|
}
|
|
const isMediaInteractive = (photo || video) && onMediaClick && !isSquarePhoto;
|
|
|
|
const className = buildClassName(
|
|
'WebPage',
|
|
isSquarePhoto && 'with-square-photo',
|
|
!photo && !video && 'without-media',
|
|
video && 'with-video',
|
|
!isArticle && 'no-article',
|
|
document && 'with-document',
|
|
quickButtonTitle && 'with-quick-button',
|
|
isGift && 'with-gift',
|
|
);
|
|
|
|
function renderQuickButton(caption: string) {
|
|
return (
|
|
<Button
|
|
className="WebPage--quick-button"
|
|
size="tiny"
|
|
color="translucent"
|
|
isRectangular
|
|
noForcedUpperCase
|
|
onClick={handleOpenTelegramLink}
|
|
>
|
|
{caption}
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<PeerColorWrapper
|
|
className={className}
|
|
data-initial={(siteName || displayUrl)[0]}
|
|
dir={lang.isRtl ? 'rtl' : 'auto'}
|
|
onClick={handleContainerClick}
|
|
>
|
|
<div className={buildClassName(
|
|
'WebPage--content',
|
|
isStory && 'is-story',
|
|
isGift && 'is-gift',
|
|
)}
|
|
>
|
|
{backgroundEmojiId && (
|
|
<EmojiIconBackground
|
|
emojiDocumentId={backgroundEmojiId}
|
|
className="WebPage--background-icons"
|
|
/>
|
|
)}
|
|
{isStory && (
|
|
<BaseStory story={story} isProtected={isProtected} isConnected={isConnected} isPreview />
|
|
)}
|
|
{isGift && (
|
|
<WebPageUniqueGift
|
|
gift={webPage.gift!}
|
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
onClick={handleOpenTelegramLink}
|
|
/>
|
|
)}
|
|
{isArticle && (
|
|
<div
|
|
className={buildClassName('WebPage-text', 'WebPage-text_interactive')}
|
|
onClick={() => openUrl({ url, shouldSkipModal: messageWebPage.isSafe })}
|
|
>
|
|
<SafeLink className="site-name" url={url} text={siteName || displayUrl} />
|
|
{title && (
|
|
<p className="site-title">{renderText(title)}</p>
|
|
)}
|
|
{truncatedDescription && !isGift && (
|
|
<p className="site-description">{renderText(truncatedDescription, ['emoji', 'br'])}</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
{photo && !isGift && !video && !document && (
|
|
<Photo
|
|
photo={photo}
|
|
isOwn={message?.isOutgoing}
|
|
isInWebPage
|
|
observeIntersection={observeIntersectionForLoading}
|
|
noAvatars={noAvatars}
|
|
canAutoLoad={canAutoLoad}
|
|
size={isSquarePhoto ? 'pictogram' : 'inline'}
|
|
asForwarded={asForwarded}
|
|
nonInteractive={!isMediaInteractive}
|
|
isDownloading={isDownloading}
|
|
isProtected={isProtected}
|
|
theme={theme}
|
|
onClick={isMediaInteractive ? handleMediaClick : undefined}
|
|
onCancelUpload={onCancelMediaTransfer}
|
|
/>
|
|
)}
|
|
{video && (
|
|
<Video
|
|
video={video}
|
|
isOwn={message?.isOutgoing}
|
|
isInWebPage
|
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
noAvatars={noAvatars}
|
|
canAutoLoad={canAutoLoad}
|
|
canAutoPlay={canAutoPlay}
|
|
asForwarded={asForwarded}
|
|
isDownloading={isDownloading}
|
|
isProtected={isProtected}
|
|
lastPlaybackTimestamp={lastPlaybackTimestamp || linkTimestamp}
|
|
onClick={isMediaInteractive ? handleMediaClick : undefined}
|
|
onCancelUpload={onCancelMediaTransfer}
|
|
/>
|
|
)}
|
|
{audio && (
|
|
<Audio
|
|
theme={theme}
|
|
message={message!}
|
|
origin={AudioOrigin.Inline}
|
|
noAvatars={noAvatars}
|
|
isDownloading={isDownloading}
|
|
onPlay={onAudioPlay}
|
|
onCancelUpload={onCancelMediaTransfer}
|
|
/>
|
|
)}
|
|
{document && (
|
|
<Document
|
|
document={document}
|
|
message={message}
|
|
observeIntersection={observeIntersectionForLoading}
|
|
autoLoadFileMaxSizeMb={autoLoadFileMaxSizeMb}
|
|
onMediaClick={onDocumentClick}
|
|
onCancelUpload={onCancelMediaTransfer}
|
|
isDownloading={isDownloading}
|
|
shouldWarnAboutFiles={shouldWarnAboutFiles}
|
|
/>
|
|
)}
|
|
{stickers && (
|
|
<div
|
|
ref={stickersRef}
|
|
className={buildClassName(
|
|
'media-inner', 'square-image', stickers.isEmoji && 'WebPage--emoji-grid', 'WebPage--stickers',
|
|
)}
|
|
>
|
|
{stickers.documents.map((sticker) => (
|
|
<div key={sticker.id} className="WebPage--sticker">
|
|
<StickerView
|
|
containerRef={stickersRef}
|
|
sticker={sticker}
|
|
shouldLoop
|
|
size={stickers.isEmoji ? EMOJI_SIZE : STICKER_SIZE}
|
|
customColor={customColor}
|
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{quickButtonTitle && renderQuickButton(quickButtonTitle)}
|
|
</PeerColorWrapper>
|
|
);
|
|
};
|
|
|
|
function getIsSmallPhoto(webPage: ApiWebPageFull, mediaSize?: WebPageMediaSize) {
|
|
if (!webPage?.photo) return false;
|
|
if (mediaSize === 'small') return true;
|
|
if (mediaSize === 'large') return false;
|
|
|
|
const { width, height } = getPhotoFullDimensions(webPage.photo) || {};
|
|
if (!width || !height) return false;
|
|
|
|
return width === height && !webPage.hasLargeMedia;
|
|
}
|
|
|
|
export default memo(withGlobal<OwnProps>(
|
|
(global): Complete<StateProps> => {
|
|
return {
|
|
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
|
};
|
|
},
|
|
)(WebPage));
|