Add buttons for t.me previews (#3068)

This commit is contained in:
Alexander Zinchuk 2023-04-25 17:24:56 +04:00
parent 7e53d0da57
commit 1e36462ed7
6 changed files with 156 additions and 91 deletions

View File

@ -1133,33 +1133,6 @@ function buildAction(
function buildReplyButtons(message: UniversalMessage, shouldSkipBuyButton?: boolean): ApiReplyKeyboard | undefined { function buildReplyButtons(message: UniversalMessage, shouldSkipBuyButton?: boolean): ApiReplyKeyboard | undefined {
const { replyMarkup, media } = message; const { replyMarkup, media } = message;
// TODO Move to the proper button inside preview
if (!replyMarkup) {
if (media instanceof GramJs.MessageMediaWebPage && media.webpage instanceof GramJs.WebPage) {
if (media.webpage.type === 'telegram_message') {
return {
inlineButtons: [[{
type: 'url',
text: 'Show Message',
url: media.webpage.url,
}]],
};
}
if (media.webpage.type === 'telegram_botapp') {
return {
inlineButtons: [[{
type: 'url',
text: 'Open App',
url: media.webpage.url,
}]],
};
}
}
return undefined;
}
// TODO
if (!(replyMarkup instanceof GramJs.ReplyKeyboardMarkup || replyMarkup instanceof GramJs.ReplyInlineMarkup)) { if (!(replyMarkup instanceof GramJs.ReplyKeyboardMarkup || replyMarkup instanceof GramJs.ReplyInlineMarkup)) {
return undefined; return undefined;
} }

View File

@ -1,22 +1,33 @@
.WebPage { .WebPage {
margin-top: 0.25rem; margin-top: 0.25rem;
margin-bottom: 0.125rem; margin-bottom: 0.125rem;
padding-left: 0.625rem;
font-size: calc(var(--message-text-size, 1rem) - 0.125rem); font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
line-height: 1.125rem; line-height: 1.125rem;
max-width: 29rem; max-width: 29rem;
position: relative;
&::before { .WebPage--content {
content: ""; padding-left: 0.625rem;
display: block; position: relative;
position: absolute;
top: 0; &::before {
bottom: 0; content: "";
left: 0; display: block;
width: 0.125rem; position: absolute;
background: var(--accent-color); top: 0;
border-radius: 0.125rem; bottom: 0;
left: 0;
width: 0.125rem;
background: var(--accent-color);
border-radius: 0.125rem;
}
}
&--quick-button {
margin-top: 0.375rem;
.theme-dark .Message.own &:hover {
color: var(--background-color);
}
} }
&-text { &-text {
@ -55,13 +66,14 @@
margin-bottom: 1rem !important; margin-bottom: 1rem !important;
} }
.message-content:not(.has-reactions) &.no-article:last-child { .message-content:not(.has-reactions) &.no-article:last-child,
.message-content:not(.has-reactions) &.with-quick-button,
.message-content:not(.has-reactions) &.with-square-photo {
margin-bottom: 1rem !important; margin-bottom: 1rem !important;
} }
&.with-square-photo { &.with-square-photo .WebPage--content {
display: flex; display: flex;
margin-bottom: 1rem;
.WebPage-text { .WebPage-text {
order: 1; order: 1;
@ -92,7 +104,7 @@
} }
} }
&:not(.with-square-photo) { &:not(.with-square-photo):not(.with-quick-button) {
.site-name, .site-name,
.site-title, .site-title,
.site-description { .site-description {

View File

@ -1,20 +1,25 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback } from '../../../lib/teact/teact'; import React, { memo, useCallback } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiMessage } from '../../../api/types'; import type { ApiMessage } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { ISettings } from '../../../types'; import type { ISettings } from '../../../types';
import { getMessageWebPage } from '../../../global/helpers'; import { getMessageWebPage } from '../../../global/helpers';
import { calculateMediaDimensions } from './helpers/mediaDimensions'; import { calculateMediaDimensions } from './helpers/mediaDimensions';
import { getWebpageButtonText } from './helpers/webpageType';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import trimText from '../../../util/trimText'; import trimText from '../../../util/trimText';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import useLang from '../../../hooks/useLang';
import SafeLink from '../../common/SafeLink'; import SafeLink from '../../common/SafeLink';
import Photo from './Photo'; import Photo from './Photo';
import Video from './Video'; import Video from './Video';
import Button from '../../ui/Button';
import './WebPage.scss'; import './WebPage.scss';
@ -51,13 +56,23 @@ const WebPage: FC<OwnProps> = ({
onMediaClick, onMediaClick,
onCancelMediaTransfer, onCancelMediaTransfer,
}) => { }) => {
const { openTelegramLink } = getActions();
const webPage = getMessageWebPage(message); const webPage = getMessageWebPage(message);
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const lang = useLang();
const handleMediaClick = useCallback(() => { const handleMediaClick = useCallback(() => {
onMediaClick!(); onMediaClick!();
}, [onMediaClick]); }, [onMediaClick]);
const handleQuickButtonClick = useCallback(() => {
if (!webPage) return;
openTelegramLink({
url: webPage.url,
});
}, [webPage]);
if (!webPage) { if (!webPage) {
return undefined; return undefined;
} }
@ -70,7 +85,9 @@ const WebPage: FC<OwnProps> = ({
description, description,
photo, photo,
video, video,
type,
} = webPage; } = webPage;
const quickButtonLangKey = !inPreview ? getWebpageButtonText(type) : undefined;
const truncatedDescription = trimText(description, MAX_TEXT_LENGTH); const truncatedDescription = trimText(description, MAX_TEXT_LENGTH);
const isArticle = Boolean(truncatedDescription || title || siteName); const isArticle = Boolean(truncatedDescription || title || siteName);
let isSquarePhoto = false; let isSquarePhoto = false;
@ -87,56 +104,73 @@ const WebPage: FC<OwnProps> = ({
!photo && !video && !inPreview && 'without-media', !photo && !video && !inPreview && 'without-media',
video && 'with-video', video && 'with-video',
!isArticle && 'no-article', !isArticle && 'no-article',
quickButtonLangKey && 'with-quick-button',
); );
function renderQuickButton(langKey: string) {
return (
<Button
className="WebPage--quick-button"
size="tiny"
color="translucent-bordered"
onClick={handleQuickButtonClick}
>
{lang(langKey)}
</Button>
);
}
return ( return (
<div <div
className={className} className={className}
data-initial={(siteName || displayUrl)[0]} data-initial={(siteName || displayUrl)[0]}
dir="auto" dir="auto"
> >
{photo && !video && ( <div className="WebPage--content">
<Photo {photo && !video && (
message={message} <Photo
observeIntersection={observeIntersection} message={message}
noAvatars={noAvatars} observeIntersection={observeIntersection}
canAutoLoad={canAutoLoad} noAvatars={noAvatars}
size={isSquarePhoto ? 'pictogram' : 'inline'} canAutoLoad={canAutoLoad}
asForwarded={asForwarded} size={isSquarePhoto ? 'pictogram' : 'inline'}
nonInteractive={!isMediaInteractive} asForwarded={asForwarded}
isDownloading={isDownloading} nonInteractive={!isMediaInteractive}
isProtected={isProtected} isDownloading={isDownloading}
theme={theme} isProtected={isProtected}
onClick={isMediaInteractive ? handleMediaClick : undefined} theme={theme}
onCancelUpload={onCancelMediaTransfer} onClick={isMediaInteractive ? handleMediaClick : undefined}
/> onCancelUpload={onCancelMediaTransfer}
)} />
{isArticle && ( )}
<div className="WebPage-text"> {isArticle && (
<SafeLink className="site-name" url={url} text={siteName || displayUrl} /> <div className="WebPage-text">
{!inPreview && title && ( <SafeLink className="site-name" url={url} text={siteName || displayUrl} />
<p className="site-title">{renderText(title)}</p> {!inPreview && title && (
)} <p className="site-title">{renderText(title)}</p>
{truncatedDescription && ( )}
<p className="site-description">{renderText(truncatedDescription, ['emoji', 'br'])}</p> {truncatedDescription && (
)} <p className="site-description">{renderText(truncatedDescription, ['emoji', 'br'])}</p>
</div> )}
)} </div>
{!inPreview && video && ( )}
<Video {!inPreview && video && (
message={message} <Video
observeIntersectionForLoading={observeIntersection!} message={message}
noAvatars={noAvatars} observeIntersectionForLoading={observeIntersection!}
canAutoLoad={canAutoLoad} noAvatars={noAvatars}
canAutoPlay={canAutoPlay} canAutoLoad={canAutoLoad}
lastSyncTime={lastSyncTime} canAutoPlay={canAutoPlay}
asForwarded={asForwarded} lastSyncTime={lastSyncTime}
isDownloading={isDownloading} asForwarded={asForwarded}
isProtected={isProtected} isDownloading={isDownloading}
onClick={isMediaInteractive ? handleMediaClick : undefined} isProtected={isProtected}
onCancelUpload={onCancelMediaTransfer} onClick={isMediaInteractive ? handleMediaClick : undefined}
/> onCancelUpload={onCancelMediaTransfer}
)} />
)}
</div>
{quickButtonLangKey && renderQuickButton(quickButtonLangKey)}
</div> </div>
); );
}; };

View File

@ -0,0 +1,30 @@
// https://github.com/telegramdesktop/tdesktop/blob/3da787791f6d227f69b32bf4003bc6071d05e2ac/Telegram/SourceFiles/history/view/history_view_view_button.cpp#L51
export function getWebpageButtonText(type?: string) {
switch (type) {
case 'telegram_channel_request':
case 'telegram_megagroup_request':
case 'telegram_chat_request':
return 'lng_view_button_request_join';
case 'telegram_message':
return 'lng_view_button_message';
case 'telegram_bot':
return 'lng_view_button_bot';
case 'telegram_voicechat':
return 'lng_view_button_voice_chat';
case 'telegram_livestream':
return 'lng_view_button_voice_chat_channel';
case 'telegram_megagroup':
case 'telegram_chat':
return 'lng_view_button_group';
case 'telegram_channel':
return 'lng_view_button_channel';
case 'telegram_user':
return 'lng_view_button_user';
case 'telegram_botapp':
return 'lng_view_button_bot_app';
case 'telegram_chatlist':
return 'ViewChatList';
default:
return undefined;
}
}

View File

@ -175,11 +175,10 @@
} }
&.translucent { &.translucent {
--ripple-color: var(--color-interactive-element-hover);
background-color: transparent; background-color: transparent;
color: var(--color-text-secondary); color: var(--color-text-secondary);
--ripple-color: var(--color-interactive-element-hover);
@include active-styles() { @include active-styles() {
background-color: var(--color-interactive-element-hover); background-color: var(--color-interactive-element-hover);
} }
@ -194,9 +193,9 @@
} }
&.translucent-white { &.translucent-white {
--ripple-color: rgba(255, 255, 255, 0.08);
background-color: transparent; background-color: transparent;
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
--ripple-color: rgba(255, 255, 255, 0.08);
@include active-styles() { @include active-styles() {
background-color: rgba(255, 255, 255, 0.08); background-color: rgba(255, 255, 255, 0.08);
@ -209,9 +208,9 @@
} }
&.translucent-black { &.translucent-black {
--ripple-color: rgba(0, 0, 0, 0.08);
background-color: transparent; background-color: transparent;
color: rgba(0, 0, 0, 0.8); color: rgba(0, 0, 0, 0.8);
--ripple-color: rgba(0, 0, 0, 0.08);
@include active-styles() { @include active-styles() {
background-color: rgba(0, 0, 0, 0.08); background-color: rgba(0, 0, 0, 0.08);
@ -222,6 +221,22 @@
} }
} }
&.translucent-bordered {
--ripple-color: rgba(0, 0, 0, 0.08);
background-color: transparent;
color: var(--accent-color);
border: 1px solid var(--accent-color);
@include active-styles() {
background-color: var(--accent-color);
color: var(--color-white);
}
@include no-ripple-styles() {
background-color: var(--active-color);
}
}
&.dark { &.dark {
background-color: rgba(0, 0, 0, 0.75); background-color: rgba(0, 0, 0, 0.75);
color: white; color: white;

View File

@ -18,7 +18,8 @@ export type OwnProps = {
children: React.ReactNode; children: React.ReactNode;
size?: 'default' | 'smaller' | 'tiny'; size?: 'default' | 'smaller' | 'tiny';
color?: ( color?: (
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black' | 'dark' 'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
| 'translucent-bordered' | 'dark'
); );
backgroundImage?: string; backgroundImage?: string;
id?: string; id?: string;