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 {
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)) {
return undefined;
}

View File

@ -1,22 +1,33 @@
.WebPage {
margin-top: 0.25rem;
margin-bottom: 0.125rem;
padding-left: 0.625rem;
font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
line-height: 1.125rem;
max-width: 29rem;
position: relative;
&::before {
content: "";
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 0.125rem;
background: var(--accent-color);
border-radius: 0.125rem;
.WebPage--content {
padding-left: 0.625rem;
position: relative;
&::before {
content: "";
display: block;
position: absolute;
top: 0;
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 {
@ -55,13 +66,14 @@
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;
}
&.with-square-photo {
&.with-square-photo .WebPage--content {
display: flex;
margin-bottom: 1rem;
.WebPage-text {
order: 1;
@ -92,7 +104,7 @@
}
}
&:not(.with-square-photo) {
&:not(.with-square-photo):not(.with-quick-button) {
.site-name,
.site-title,
.site-description {

View File

@ -1,20 +1,25 @@
import type { FC } 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 { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { ISettings } from '../../../types';
import { getMessageWebPage } from '../../../global/helpers';
import { calculateMediaDimensions } from './helpers/mediaDimensions';
import { getWebpageButtonText } from './helpers/webpageType';
import renderText from '../../common/helpers/renderText';
import trimText from '../../../util/trimText';
import buildClassName from '../../../util/buildClassName';
import useAppLayout from '../../../hooks/useAppLayout';
import useLang from '../../../hooks/useLang';
import SafeLink from '../../common/SafeLink';
import Photo from './Photo';
import Video from './Video';
import Button from '../../ui/Button';
import './WebPage.scss';
@ -51,13 +56,23 @@ const WebPage: FC<OwnProps> = ({
onMediaClick,
onCancelMediaTransfer,
}) => {
const { openTelegramLink } = getActions();
const webPage = getMessageWebPage(message);
const { isMobile } = useAppLayout();
const lang = useLang();
const handleMediaClick = useCallback(() => {
onMediaClick!();
}, [onMediaClick]);
const handleQuickButtonClick = useCallback(() => {
if (!webPage) return;
openTelegramLink({
url: webPage.url,
});
}, [webPage]);
if (!webPage) {
return undefined;
}
@ -70,7 +85,9 @@ const WebPage: FC<OwnProps> = ({
description,
photo,
video,
type,
} = webPage;
const quickButtonLangKey = !inPreview ? getWebpageButtonText(type) : undefined;
const truncatedDescription = trimText(description, MAX_TEXT_LENGTH);
const isArticle = Boolean(truncatedDescription || title || siteName);
let isSquarePhoto = false;
@ -87,56 +104,73 @@ const WebPage: FC<OwnProps> = ({
!photo && !video && !inPreview && 'without-media',
video && 'with-video',
!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 (
<div
className={className}
data-initial={(siteName || displayUrl)[0]}
dir="auto"
>
{photo && !video && (
<Photo
message={message}
observeIntersection={observeIntersection}
noAvatars={noAvatars}
canAutoLoad={canAutoLoad}
size={isSquarePhoto ? 'pictogram' : 'inline'}
asForwarded={asForwarded}
nonInteractive={!isMediaInteractive}
isDownloading={isDownloading}
isProtected={isProtected}
theme={theme}
onClick={isMediaInteractive ? handleMediaClick : undefined}
onCancelUpload={onCancelMediaTransfer}
/>
)}
{isArticle && (
<div className="WebPage-text">
<SafeLink className="site-name" url={url} text={siteName || displayUrl} />
{!inPreview && title && (
<p className="site-title">{renderText(title)}</p>
)}
{truncatedDescription && (
<p className="site-description">{renderText(truncatedDescription, ['emoji', 'br'])}</p>
)}
</div>
)}
{!inPreview && video && (
<Video
message={message}
observeIntersectionForLoading={observeIntersection!}
noAvatars={noAvatars}
canAutoLoad={canAutoLoad}
canAutoPlay={canAutoPlay}
lastSyncTime={lastSyncTime}
asForwarded={asForwarded}
isDownloading={isDownloading}
isProtected={isProtected}
onClick={isMediaInteractive ? handleMediaClick : undefined}
onCancelUpload={onCancelMediaTransfer}
/>
)}
<div className="WebPage--content">
{photo && !video && (
<Photo
message={message}
observeIntersection={observeIntersection}
noAvatars={noAvatars}
canAutoLoad={canAutoLoad}
size={isSquarePhoto ? 'pictogram' : 'inline'}
asForwarded={asForwarded}
nonInteractive={!isMediaInteractive}
isDownloading={isDownloading}
isProtected={isProtected}
theme={theme}
onClick={isMediaInteractive ? handleMediaClick : undefined}
onCancelUpload={onCancelMediaTransfer}
/>
)}
{isArticle && (
<div className="WebPage-text">
<SafeLink className="site-name" url={url} text={siteName || displayUrl} />
{!inPreview && title && (
<p className="site-title">{renderText(title)}</p>
)}
{truncatedDescription && (
<p className="site-description">{renderText(truncatedDescription, ['emoji', 'br'])}</p>
)}
</div>
)}
{!inPreview && video && (
<Video
message={message}
observeIntersectionForLoading={observeIntersection!}
noAvatars={noAvatars}
canAutoLoad={canAutoLoad}
canAutoPlay={canAutoPlay}
lastSyncTime={lastSyncTime}
asForwarded={asForwarded}
isDownloading={isDownloading}
isProtected={isProtected}
onClick={isMediaInteractive ? handleMediaClick : undefined}
onCancelUpload={onCancelMediaTransfer}
/>
)}
</div>
{quickButtonLangKey && renderQuickButton(quickButtonLangKey)}
</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 {
--ripple-color: var(--color-interactive-element-hover);
background-color: transparent;
color: var(--color-text-secondary);
--ripple-color: var(--color-interactive-element-hover);
@include active-styles() {
background-color: var(--color-interactive-element-hover);
}
@ -194,9 +193,9 @@
}
&.translucent-white {
--ripple-color: rgba(255, 255, 255, 0.08);
background-color: transparent;
color: rgba(255, 255, 255, 0.5);
--ripple-color: rgba(255, 255, 255, 0.08);
@include active-styles() {
background-color: rgba(255, 255, 255, 0.08);
@ -209,9 +208,9 @@
}
&.translucent-black {
--ripple-color: rgba(0, 0, 0, 0.08);
background-color: transparent;
color: rgba(0, 0, 0, 0.8);
--ripple-color: rgba(0, 0, 0, 0.08);
@include active-styles() {
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 {
background-color: rgba(0, 0, 0, 0.75);
color: white;

View File

@ -18,7 +18,8 @@ export type OwnProps = {
children: React.ReactNode;
size?: 'default' | 'smaller' | 'tiny';
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;
id?: string;