diff --git a/src/api/gramjs/apiBuilders/messageContent.ts b/src/api/gramjs/apiBuilders/messageContent.ts index 2c8963df5..d37ed580e 100644 --- a/src/api/gramjs/apiBuilders/messageContent.ts +++ b/src/api/gramjs/apiBuilders/messageContent.ts @@ -761,6 +761,8 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef }; } + const mediaSize = media.forceSmallMedia ? 'small' : media.forceLargeMedia ? 'large' : undefined; + return { mediaType: 'webpage', id: Number(id), @@ -772,6 +774,7 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef 'title', 'description', 'duration', + 'hasLargeMedia', ]), photo: photo instanceof GramJs.Photo ? buildApiPhoto(photo) : undefined, document: !video && !audio && document ? buildApiDocument(document) : undefined, @@ -779,6 +782,7 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef audio, story, stickers, + mediaSize, }; } diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index 3ee7b86e6..16c12dbd7 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -1,6 +1,7 @@ import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; +import type { WebPageMediaSize } from '../../../global/types'; import type { ThreadId } from '../../../types'; import type { ApiAttachment, @@ -264,6 +265,8 @@ export function sendMessage( wasDrafted, isInvertedMedia, effectId, + webPageMediaSize, + webPageUrl, }: { chat: ApiChat; lastMessageId?: number; @@ -285,6 +288,8 @@ export function sendMessage( wasDrafted?: boolean; isInvertedMedia?: true; effectId?: string; + webPageMediaSize?: WebPageMediaSize; + webPageUrl?: string; }, onProgress?: ApiOnProgress, ) { @@ -366,6 +371,12 @@ export function sendMessage( media = buildInputPoll(poll, randomId); } else if (story) { media = buildInputStory(story); + } else if (webPageUrl && webPageMediaSize) { + media = new GramJs.InputMediaWebPage({ + url: webPageUrl, + forceLargeMedia: webPageMediaSize === 'large' ? true : undefined, + forceSmallMedia: webPageMediaSize === 'small' ? true : undefined, + }); } else if (contact) { media = new GramJs.InputMediaContact({ phoneNumber: contact.phoneNumber, diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index 8c9f39651..3ee850cae 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -1,3 +1,4 @@ +import type { WebPageMediaSize } from '../../global/types'; import type { ThreadId } from '../../types'; import type { ApiWebDocument } from './bots'; import type { ApiGroupCall, PhoneCallAction } from './calls'; @@ -476,6 +477,8 @@ export interface ApiWebPage { video?: ApiVideo; story?: ApiWebPageStoryData; stickers?: ApiWebPageStickerData; + mediaSize?: WebPageMediaSize; + hasLargeMedia?: boolean; } export type ApiReplyInfo = ApiMessageReplyInfo | ApiStoryReplyInfo; diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index 7a50d7b85..dcca9a3d0 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -1063,6 +1063,8 @@ const Composer: FC = ({ shouldUpdateStickerSetOrder, isInvertedMedia, effectId, + webPageMediaSize: attachmentSettings.webPageMediaSize, + webPageUrl: hasWebPagePreview ? webPagePreview!.url : undefined, }); } diff --git a/src/components/middle/composer/WebPagePreview.tsx b/src/components/middle/composer/WebPagePreview.tsx index b6f245ce7..72ca5fe62 100644 --- a/src/components/middle/composer/WebPagePreview.tsx +++ b/src/components/middle/composer/WebPagePreview.tsx @@ -5,7 +5,7 @@ import { getActions, withGlobal } from '../../../global'; import type { ApiFormattedText, ApiMessage, ApiMessageEntityTextUrl, ApiWebPage, } from '../../../api/types'; -import type { GlobalState } from '../../../global/types'; +import type { GlobalState, WebPageMediaSize } from '../../../global/types'; import type { ISettings, ThreadId } from '../../../types'; import type { Signal } from '../../../util/signals'; import { ApiMessageEntityTypes } from '../../../api/types'; @@ -76,6 +76,7 @@ const WebPagePreview: FC = ({ const ref = useRef(null); const isInvertedMedia = attachmentSettings.isInvertedMedia; + const isSmallerMedia = attachmentSettings.webPageMediaSize === 'small'; const detectLinkDebounced = useDebouncedResolver(() => { const formattedText = parseHtmlAsFormattedText(getHtml()); @@ -112,6 +113,8 @@ const WebPagePreview: FC = ({ }, [isDisabled, getHtml, noWebPage, webPagePreview]); const { shouldRender, transitionClassNames } = useShowTransitionDeprecated(isShown); + const hasMediaSizeOptions = webPagePreview?.hasLargeMedia; + const renderingWebPage = useCurrentOrPrev(webPagePreview, true); const handleClearWebpagePreview = useLastCallback(() => { @@ -144,6 +147,10 @@ const WebPagePreview: FC = ({ updateAttachmentSettings({ isInvertedMedia: value }); } + function updateIsLargerMedia(value?: WebPageMediaSize) { + updateAttachmentSettings({ webPageMediaSize: value }); + } + if (!shouldRender || !renderingWebPage) { return undefined; } @@ -183,6 +190,19 @@ const WebPagePreview: FC = ({ ) } + {hasMediaSizeOptions && ( + isSmallerMedia ? ( + // eslint-disable-next-line react/jsx-no-bind + updateIsLargerMedia('large')}> + {lang('ChatInput.EditLink.LargerMedia')} + + ) : ( + // eslint-disable-next-line react/jsx-no-bind + updateIsLargerMedia('small')}> + {lang(('ChatInput.EditLink.SmallerMedia'))} + + ) + )} ({ 'media-inner', !isUploading && !nonInteractive && 'interactive', isSmall && 'small-image', - width === height && 'square-image', + (width === height || size === 'pictogram') && 'square-image', height < MIN_MEDIA_HEIGHT && 'fix-min-height', className, ); diff --git a/src/components/middle/message/WebPage.scss b/src/components/middle/message/WebPage.scss index 028e44d48..203980782 100644 --- a/src/components/middle/message/WebPage.scss +++ b/src/components/middle/message/WebPage.scss @@ -157,6 +157,7 @@ margin-bottom: 0 !important; img { + object-fit: cover; width: 100%; height: 100%; } diff --git a/src/components/middle/message/WebPage.tsx b/src/components/middle/message/WebPage.tsx index 8709150c4..b8cd22a11 100644 --- a/src/components/middle/message/WebPage.tsx +++ b/src/components/middle/message/WebPage.tsx @@ -129,6 +129,7 @@ const WebPage: FC = ({ audio, type, document, + mediaSize, } = webPage; const isStory = type === WEBPAGE_STORY_TYPE; const isExpiredStory = story && 'isDeleted' in story; @@ -145,7 +146,7 @@ const WebPage: FC = ({ noAvatars, isMobile, }); - isSquarePhoto = width === height; + isSquarePhoto = (width === height || mediaSize === 'small') && mediaSize !== 'large'; } const isMediaInteractive = (photo || video) && onMediaClick && !isSquarePhoto; diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts index 1a796ddf8..f52400cb3 100644 --- a/src/global/actions/api/messages.ts +++ b/src/global/actions/api/messages.ts @@ -19,7 +19,7 @@ import type { import type { MessageKey } from '../../../util/keys/messageKey'; import type { RequiredGlobalActions } from '../../index'; import type { - ActionReturnType, ApiDraft, GlobalState, TabArgs, + ActionReturnType, ApiDraft, GlobalState, TabArgs, WebPageMediaSize, } from '../../types'; import { MAIN_THREAD_ID, MESSAGE_DELETED } from '../../../api/types'; import { LoadMoreDirection, type ThreadId } from '../../../types'; @@ -1455,6 +1455,7 @@ async function sendMessage(global: T, params: { lastMessageId?: number; isInvertedMedia?: true; effectId?: string; + webPageMediaSize?: WebPageMediaSize; }) { let currentMessageKey: MessageKey | undefined; const progressCallback = params.attachment ? (progress: number, messageKey: MessageKey) => { diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts index 77488a241..8901e9b72 100644 --- a/src/global/actions/ui/misc.ts +++ b/src/global/actions/ui/misc.ts @@ -491,7 +491,7 @@ addActionHandler('requestConfetti', (global, actions, payload): ActionReturnType addActionHandler('updateAttachmentSettings', (global, actions, payload): ActionReturnType => { const { - shouldCompress, shouldSendGrouped, isInvertedMedia, + shouldCompress, shouldSendGrouped, isInvertedMedia, webPageMediaSize, } = payload; return { @@ -500,6 +500,7 @@ addActionHandler('updateAttachmentSettings', (global, actions, payload): ActionR shouldCompress: shouldCompress ?? global.attachmentSettings.shouldCompress, shouldSendGrouped: shouldSendGrouped ?? global.attachmentSettings.shouldSendGrouped, isInvertedMedia, + webPageMediaSize, }, }; }); diff --git a/src/global/initialState.ts b/src/global/initialState.ts index 9a5d5e05a..0df97f5ce 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -142,6 +142,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { shouldCompress: true, shouldSendGrouped: true, isInvertedMedia: undefined, + webPageMediaSize: undefined, }, scheduledMessages: { diff --git a/src/global/types.ts b/src/global/types.ts index d9e084751..364bc4e82 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -280,6 +280,8 @@ type ConfettiParams = OptionalCombine<{ height?: number; }>; +export type WebPageMediaSize = 'large' | 'small'; + export type TabState = { id: number; isBlurred?: boolean; @@ -920,6 +922,7 @@ export type GlobalState = { shouldCompress: boolean; shouldSendGrouped: boolean; isInvertedMedia?: true; + webPageMediaSize?: WebPageMediaSize; }; attachMenu: { @@ -1632,6 +1635,8 @@ export interface ActionPayloads { isReaction?: true; // Reaction to the story are sent in the form of a message isInvertedMedia?: true; effectId?: string; + webPageMediaSize?: WebPageMediaSize; + webPageUrl?: string; } & WithTabId; sendInviteMessages: { chatId: string; @@ -3189,6 +3194,7 @@ export interface ActionPayloads { shouldCompress?: boolean; shouldSendGrouped?: boolean; isInvertedMedia?: true; + webPageMediaSize?: WebPageMediaSize; }; saveEffectInDraft: {