diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index cc6413416..6ca5ec8df 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -662,6 +662,10 @@ function buildReplyInfo(inputInfo: ApiInputReplyInfo, isForum?: boolean): ApiRep export function buildUploadingMedia( attachment: ApiAttachment, ): MediaContent { + if (attachment.gif) { + return { video: attachment.gif }; + } + const { filename: fileName, blobUrl, diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts index e32b2af5e..7e5483126 100644 --- a/src/api/gramjs/gramjsBuilders/index.ts +++ b/src/api/gramjs/gramjsBuilders/index.ts @@ -197,14 +197,14 @@ export function buildInputDocument(media: ApiSticker | ApiVideo) { ])); } -export function buildInputMediaDocument(media: ApiSticker | ApiVideo) { +export function buildInputMediaDocument(media: ApiSticker | ApiVideo, spoiler?: true) { const inputDocument = buildInputDocument(media); if (!inputDocument) { return undefined; } - return new GramJs.InputMediaDocument({ id: inputDocument }); + return new GramJs.InputMediaDocument({ id: inputDocument, spoiler }); } export function buildInputPoll(pollParams: ApiNewPoll, randomId: bigint) { diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index 27e53efe6..8006b69f0 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -438,6 +438,9 @@ export function sendApiMessage( } } + if (!media && attachment?.gif) { + media = buildInputMediaDocument(attachment.gif, attachment.shouldSendAsSpoiler); + } if (!media && attachment) { try { media = await uploadMedia(localMessage, attachment, onProgress!); @@ -607,27 +610,33 @@ function sendGroupedMedia( const prevMediaQueue = mediaQueue; mediaQueue = (async () => { - let media; - try { - media = await uploadMedia(localMessage, attachment, onProgress!); - } catch (err) { - if (DEBUG) { - // eslint-disable-next-line no-console - console.warn(err); + let inputMedia: GramJs.TypeInputMedia | undefined; + + if (attachment.gif) { + inputMedia = buildInputMediaDocument(attachment.gif, attachment.shouldSendAsSpoiler); + } else { + let media; + try { + media = await uploadMedia(localMessage, attachment, onProgress!); + } catch (err) { + if (DEBUG) { + // eslint-disable-next-line no-console + console.warn(err); + } + + groupedUploads[groupedId].counter--; + + await prevMediaQueue; + + return; } - groupedUploads[groupedId].counter--; - - await prevMediaQueue; - - return; + inputMedia = await fetchInputMedia( + buildInputPeer(chat.id, chat.accessHash), + media, + ); } - const inputMedia = await fetchInputMedia( - buildInputPeer(chat.id, chat.accessHash), - media, - ); - await prevMediaQueue; if (!inputMedia) { diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 037340d4f..7c9a752f6 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -1,7 +1,7 @@ import type { CallbackAction } from '../../global/types'; import type { IconName } from '../../types/icons'; import type { LangFnParameters, RegularLangFnParameters } from '../../util/localization'; -import type { ApiDocument, ApiFormattedText, ApiMessageEntity, ApiPhoto, ApiReaction } from './messages'; +import type { ApiDocument, ApiFormattedText, ApiMessageEntity, ApiPhoto, ApiReaction, ApiVideo } from './messages'; import type { ApiPremiumSection } from './payments'; import type { ApiBotVerification } from './peers'; import type { ApiStarsSubscriptionPricing } from './stars'; @@ -44,7 +44,7 @@ export interface ApiOnProgress { } export interface ApiAttachment { - blob: Blob; + blob?: Blob; blobUrl: string; compressedBlobUrl?: string; filename: string; @@ -72,6 +72,8 @@ export interface ApiAttachment { uniqueId?: string; ttlSeconds?: number; shouldSendInHighQuality?: boolean; + + gif?: ApiVideo; } export interface ApiWallpaper { diff --git a/src/assets/font-icons/add-caption.svg b/src/assets/font-icons/add-caption.svg new file mode 100644 index 000000000..5dfcb4480 --- /dev/null +++ b/src/assets/font-icons/add-caption.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index ae945568b..865740426 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -2666,6 +2666,8 @@ "AttachmentSendAudio_other" = "Send {count} Audios"; "AttachmentSendFile_one" = "Send File"; "AttachmentSendFile_other" = "Send {count} Files"; +"AttachmentSendGif" = "Send GIF"; +"AttachmentReplaceGif" = "Replace GIF"; "AttachmentDragAddItems" = "Add Items"; "AttachmentCaptionPlaceholder" = "Add a caption..."; "MessageSummaryTitle" = "AI Summary"; @@ -2729,3 +2731,4 @@ "GiftPreviewToggleRegularModels" = "View Primary Models >"; "AriaGiftPreviewPlay" = "Play random previews"; "AriaGiftPreviewStop" = "Pause random previews"; +"MenuAddCaption" = "Add Caption"; diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index 8210e867a..6f3907a3c 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -132,7 +132,10 @@ import { getServerTime } from '../../util/serverTime'; import windowSize from '../../util/windowSize'; import { DEFAULT_MAX_MESSAGE_LENGTH } from '../../limits'; import applyIosAutoCapitalizationFix from '../middle/composer/helpers/applyIosAutoCapitalizationFix'; -import buildAttachment, { prepareAttachmentsToSend } from '../middle/composer/helpers/buildAttachment'; +import buildAttachment, { + buildGifAttachment, + prepareAttachmentsToSend, +} from '../middle/composer/helpers/buildAttachment'; import { buildCustomEmojiHtml } from '../middle/composer/helpers/customEmoji'; import { isSelectionInsideInput } from '../middle/composer/helpers/selection'; import renderText from './helpers/renderText'; @@ -1021,6 +1024,8 @@ const Composer = ({ theme, }); + const hasGifFromPicker = attachments.some((a) => a.gif); + useClipboardPaste( isForCurrentMessageList || isInStoryViewer, insertFormattedTextAndUpdateCursor, @@ -1030,6 +1035,7 @@ const Composer = ({ !isCurrentUserPremium && !isChatWithSelf, showCustomEmojiPremiumNotification, !attachments.length, + hasGifFromPicker, ); const handleEmbeddedClear = useLastCallback(() => { @@ -1474,6 +1480,11 @@ const Composer = ({ clearDraft({ chatId, threadId, isLocalOnly: true }); }); + const handleGifAddCaption = useLastCallback((gif: ApiVideo) => { + handleSetAttachments([buildGifAttachment(gif)]); + closeSymbolMenu(); + }); + const handleStickerSelect = useLastCallback(( sticker: ApiSticker, isSilent?: boolean, @@ -2234,6 +2245,7 @@ const Composer = ({ canSendGifs={canSendGifs} isMessageComposer={isInMessageList} onGifSelect={handleGifSelect} + onGifAddCaption={handleGifAddCaption} onStickerSelect={handleStickerSelect} onCustomEmojiSelect={handleCustomEmojiSelect} onRemoveSymbol={removeSymbol} diff --git a/src/components/common/GifButton.tsx b/src/components/common/GifButton.tsx index 3c2018a11..36e38b9fb 100644 --- a/src/components/common/GifButton.tsx +++ b/src/components/common/GifButton.tsx @@ -16,6 +16,7 @@ import useBuffering from '../../hooks/useBuffering'; import useCanvasBlur from '../../hooks/useCanvasBlur'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import { useIsIntersecting } from '../../hooks/useIntersectionObserver'; +import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import useMedia from '../../hooks/useMedia'; import useOldLang from '../../hooks/useOldLang'; @@ -33,9 +34,10 @@ type OwnProps = { observeIntersection: ObserveFn; isDisabled?: boolean; className?: string; + isSavedMessages?: boolean; onClick?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void; onUnsaveClick?: (gif: ApiVideo) => void; - isSavedMessages?: boolean; + onAddCaption?: (gif: ApiVideo) => void; }; const GifButton: FC = ({ @@ -43,13 +45,15 @@ const GifButton: FC = ({ isDisabled, className, observeIntersection, + isSavedMessages, onClick, onUnsaveClick, - isSavedMessages, + onAddCaption, }) => { const ref = useRef(); - const lang = useOldLang(); + const oldLang = useOldLang(); + const lang = useLang(); const isIntersecting = useIsIntersecting(ref, observeIntersection); const loadAndPlay = isIntersecting && !isDisabled; @@ -76,6 +80,7 @@ const GifButton: FC = ({ const getTriggerElement = useLastCallback(() => ref.current); const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar')); const getMenuElement = useLastCallback(() => ref.current!.querySelector('.gif-context-menu .bubble')); + const getLayout = useLastCallback(() => ({ shouldAvoidNegativePosition: true })); const handleClick = useLastCallback(() => { if (isContextMenuOpen || !onClick) return; @@ -109,6 +114,13 @@ const GifButton: FC = ({ }, undefined, true); }); + const handleAddCaption = useLastCallback(() => { + onAddCaption?.({ + ...gif, + blobUrl: videoData, + }); + }); + const handleMouseDown = useLastCallback((e: React.MouseEvent) => { preventMessageInputBlurWithBubbling(e); handleBeforeContextMenu(e); @@ -182,17 +194,21 @@ const GifButton: FC = ({ getTriggerElement={getTriggerElement} getRootElement={getRootElement} getMenuElement={getMenuElement} + getLayout={getLayout} className="gif-context-menu" autoClose onClose={handleContextMenuClose} onCloseAnimationEnd={handleContextMenuHide} > - {!isSavedMessages && {lang('SendWithoutSound')}} + {!isSavedMessages && {oldLang('SendWithoutSound')}} - {lang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')} + {oldLang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')} + {onAddCaption && ( + {lang('MenuAddCaption')} + )} {onUnsaveClick && ( - {lang('Delete')} + {oldLang('Delete')} )} )} diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index f3dd18081..3310df42d 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -9,6 +9,7 @@ import type { Signal } from '../../../util/signals'; import { BASE_EMOJI_KEYWORD_LANG, EDITABLE_INPUT_MODAL_ID, + GIF_MIME_TYPE, SUPPORTED_AUDIO_CONTENT_TYPES, SUPPORTED_PHOTO_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES, @@ -154,6 +155,7 @@ const AttachmentModal = ({ const svgRef = useRef(); const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings, resetMessageMediaEditorRequest, + updateShouldSaveAttachmentsCompression, } = getActions(); const lang = useLang(); @@ -170,6 +172,7 @@ const AttachmentModal = ({ const isInAlbum = editingMessage && editingMessage?.groupedId; const isEditingMessageFile = isEditing && attachments?.length && getAttachmentMediaType(attachments[0]); const notEditingFile = isEditingMessageFile !== 'file'; + const hasGifFromPicker = renderingAttachments?.some((a) => a.gif); const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag(); const [editingAttachmentIndex, setEditingAttachmentIndex] = useState(undefined); @@ -185,7 +188,7 @@ const AttachmentModal = ({ const shouldSendCompressed = attachmentSettings.shouldCompress; const isSendingCompressed = Boolean( - (shouldSendCompressed || shouldForceCompression || isInAlbum) && !shouldForceAsFile, + (shouldSendCompressed || shouldForceCompression || isInAlbum || hasGifFromPicker) && !shouldForceAsFile, ); const [shouldSendGrouped, setShouldSendGrouped] = useState(attachmentSettings.shouldSendGrouped); const isInvertedMedia = attachmentSettings.isInvertedMedia; @@ -215,6 +218,13 @@ const AttachmentModal = ({ } }, [closeSymbolMenu, isOpen]); + useEffect(() => { + if (hasGifFromPicker) { + updateShouldSaveAttachmentsCompression({ shouldSave: false }); + setShouldSendGrouped(false); + } + }, [hasGifFromPicker, updateShouldSaveAttachmentsCompression]); + const [hasMedia, hasOnlyMedia] = useMemo(() => { const onlyMedia = Boolean(renderingAttachments?.every((a) => a.quick || a.audio)); if (onlyMedia) return [true, true]; @@ -509,13 +519,25 @@ const AttachmentModal = ({ const isQuickGallery = isSendingCompressed && hasOnlyMedia; - const [areAllPhotos, areAllVideos, areAllAudios, hasAnyPhoto] = useMemo(() => { - if (!isQuickGallery || !renderingAttachments) return [false, false, false]; - const everyPhoto = renderingAttachments.every((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)); - const everyVideo = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType)); - const everyAudio = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType)); - const anyPhoto = renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)); - return [everyPhoto, everyVideo, everyAudio, anyPhoto]; + const { + areAllPhotos, areAllVideos, areAllAudios, areAllGifs, hasAnyPhoto, + } = useMemo(() => { + if (!isQuickGallery || !renderingAttachments) { + return { + areAllPhotos: false, + areAllVideos: false, + areAllAudios: false, + areAllGifs: false, + hasAnyPhoto: false, + }; + } + return { + areAllPhotos: renderingAttachments.every((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)), + areAllVideos: renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType)), + areAllAudios: renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType)), + areAllGifs: renderingAttachments.every((a) => a.gif || a.mimeType === GIF_MIME_TYPE), + hasAnyPhoto: renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)), + }; }, [renderingAttachments, isQuickGallery]); const hasAnySpoilerable = useMemo(() => { @@ -553,7 +575,10 @@ const AttachmentModal = ({ let title = ''; const attachmentsLength = renderingAttachments.length; - if (areAllPhotos) { + + if (areAllGifs) { + title = lang(isEditing ? 'AttachmentReplaceGif' : 'AttachmentSendGif'); + } else if (areAllPhotos) { title = lang( `Attachment${isEditing ? 'Replace' : 'Send'}Photo`, { count: attachmentsLength }, @@ -602,7 +627,7 @@ const AttachmentModal = ({ trigger={MoreMenuButton} positionX="right" > - {Boolean(!editingMessage) && ( + {Boolean(!editingMessage) && !hasGifFromPicker && ( {lang('Add')} )} {hasMedia && ( @@ -621,7 +646,7 @@ const AttachmentModal = ({ )) } { - !shouldForceAsFile && !shouldForceCompression && (isSendingCompressed ? ( + !shouldForceAsFile && !shouldForceCompression && !hasGifFromPicker && (isSendingCompressed ? ( {lang(isMultiple ? 'AttachmentMenuSendAllAsFiles' : 'AttachmentMenuSendAsFiles')} diff --git a/src/components/middle/composer/GifPicker.scss b/src/components/middle/composer/GifPicker.scss index 886e74cf3..d89d6588c 100644 --- a/src/components/middle/composer/GifPicker.scss +++ b/src/components/middle/composer/GifPicker.scss @@ -4,6 +4,7 @@ overflow-y: auto; + width: calc(100% - 0.375rem); height: calc(100% - 0.1875rem); margin: 0 0.1875rem; padding-bottom: 0.1875rem; diff --git a/src/components/middle/composer/GifPicker.tsx b/src/components/middle/composer/GifPicker.tsx index 13001e968..9191a6996 100644 --- a/src/components/middle/composer/GifPicker.tsx +++ b/src/components/middle/composer/GifPicker.tsx @@ -24,6 +24,7 @@ type OwnProps = { loadAndPlay: boolean; canSendGifs?: boolean; onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void; + onGifAddCaption?: (gif: ApiVideo) => void; }; type StateProps = { @@ -40,6 +41,7 @@ const GifPicker: FC = ({ savedGifs, isSavedMessages, onGifSelect, + onGifAddCaption, }) => { const { loadSavedGifs, saveGif } = getActions(); @@ -80,9 +82,10 @@ const GifPicker: FC = ({ gif={gif} observeIntersection={observeIntersection} isDisabled={!loadAndPlay} + isSavedMessages={isSavedMessages} onClick={canSendGifs ? onGifSelect : undefined} onUnsaveClick={handleUnsaveClick} - isSavedMessages={isSavedMessages} + onAddCaption={canSendGifs ? onGifAddCaption : undefined} /> )) ) : canRenderContents && savedGifs ? ( diff --git a/src/components/middle/composer/SymbolMenu.tsx b/src/components/middle/composer/SymbolMenu.tsx index e5500a0e3..ed3c3f156 100644 --- a/src/components/middle/composer/SymbolMenu.tsx +++ b/src/components/middle/composer/SymbolMenu.tsx @@ -56,6 +56,7 @@ export type OwnProps = { canUpdateStickerSetsOrder?: boolean, ) => void; onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void; + onGifAddCaption?: (gif: ApiVideo) => void; onRemoveSymbol: () => void; onSearchOpen: (type: 'stickers' | 'gifs') => void; addRecentEmoji: GlobalActions['addRecentEmoji']; @@ -91,6 +92,7 @@ const SymbolMenu: FC = ({ onCustomEmojiSelect, onStickerSelect, onGifSelect, + onGifAddCaption, onRemoveSymbol, onSearchOpen, addRecentEmoji, @@ -239,6 +241,7 @@ const SymbolMenu: FC = ({ loadAndPlay={canSendGifs ? isOpen && (isActive || isFrom) : false} canSendGifs={canSendGifs} onGifSelect={onGifSelect} + onGifAddCaption={onGifAddCaption} /> ); } diff --git a/src/components/middle/composer/SymbolMenuButton.tsx b/src/components/middle/composer/SymbolMenuButton.tsx index 3a2c27d2b..3b212bbcb 100644 --- a/src/components/middle/composer/SymbolMenuButton.tsx +++ b/src/components/middle/composer/SymbolMenuButton.tsx @@ -41,6 +41,7 @@ type OwnProps = { canUpdateStickerSetsOrder?: boolean, ) => void; onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void; + onGifAddCaption?: (gif: ApiVideo) => void; onRemoveSymbol: VoidFunction; onEmojiSelect: (emoji: string) => void; closeBotCommandMenu?: VoidFunction; @@ -73,6 +74,7 @@ const SymbolMenuButton: FC = ({ onCustomEmojiSelect, onStickerSelect, onGifSelect, + onGifAddCaption, onRemoveSymbol, onEmojiSelect, closeBotCommandMenu, @@ -181,6 +183,7 @@ const SymbolMenuButton: FC = ({ onStickerSelect={onStickerSelect} onCustomEmojiSelect={onCustomEmojiSelect} onGifSelect={onGifSelect} + onGifAddCaption={onGifAddCaption} onRemoveSymbol={onRemoveSymbol} onSearchOpen={handleSearchOpen} addRecentEmoji={addRecentEmoji} diff --git a/src/components/middle/composer/helpers/buildAttachment.ts b/src/components/middle/composer/helpers/buildAttachment.ts index 5b8348606..11b14af32 100644 --- a/src/components/middle/composer/helpers/buildAttachment.ts +++ b/src/components/middle/composer/helpers/buildAttachment.ts @@ -1,4 +1,4 @@ -import type { ApiAttachment } from '../../../../api/types'; +import type { ApiAttachment, ApiVideo } from '../../../../api/types'; import { GIF_MIME_TYPE, @@ -132,3 +132,26 @@ function validateAspectRatio(width: number, height: number) { const maxAspectRatio = Math.max(width, height) / Math.min(width, height); return maxAspectRatio <= MAX_ASPECT_RATIO; } + +export function buildGifAttachment(gif: ApiVideo): ApiAttachment { + const { + blobUrl, + thumbnail, + fileName, + mimeType, + size, + width, + height, + duration, + } = gif; + + return { + gif, + blobUrl: blobUrl || '', + previewBlobUrl: thumbnail?.dataUri, + filename: fileName, + mimeType, + size, + quick: width && height ? { width, height, duration } : undefined, + } satisfies ApiAttachment; +} diff --git a/src/components/middle/composer/hooks/useAttachmentModal.ts b/src/components/middle/composer/hooks/useAttachmentModal.ts index ad835fc6b..afd01fa75 100644 --- a/src/components/middle/composer/hooks/useAttachmentModal.ts +++ b/src/components/middle/composer/hooks/useAttachmentModal.ts @@ -3,6 +3,7 @@ import { getActions } from '../../../../global'; import type { ApiAttachment, ApiMessage } from '../../../../api/types'; +import { GIF_MIME_TYPE } from '../../../../config'; import { canReplaceMessageMedia, getAttachmentMediaType } from '../../../../global/helpers'; import { MEMO_EMPTY_ARRAY } from '../../../../util/memo'; import buildAttachment from '../helpers/buildAttachment'; @@ -86,6 +87,11 @@ export default function useAttachmentModal({ const handleAppendFiles = useLastCallback(async (files: File[], isSpoiler?: boolean) => { if (editedMessage) { + if (editedMessage.groupedId && files[0].type === GIF_MIME_TYPE) { + showNotification({ message: lang('MediaReplaceInvalidError', undefined, { pluralValue: 1 }) }); + return; + } + const newAttachment = await buildAttachment(files[0].name, files[0]); const canReplace = editedMessage && canReplaceMessageMedia(editedMessage, newAttachment); @@ -109,6 +115,11 @@ export default function useAttachmentModal({ const handleFileSelect = useLastCallback(async (files: File[]) => { if (editedMessage) { + if (editedMessage.groupedId && files[0].type === GIF_MIME_TYPE) { + showNotification({ message: lang('MediaReplaceInvalidError', undefined, { pluralValue: 1 }) }); + return; + } + const newAttachment = await buildAttachment(files[0].name, files[0]); const canReplace = editedMessage && canReplaceMessageMedia(editedMessage, newAttachment); @@ -129,8 +140,10 @@ export default function useAttachmentModal({ }); const handleUpdateAttachmentsQuality = useLastCallback(async () => { - const newAttachments = await Promise.all(attachments.map((attachment) => - buildAttachment(attachment.filename, attachment.blob, { shouldSendInHighQuality }))); + const newAttachments = await Promise.all(attachments.map(async (attachment) => { + if (!attachment.blob) return attachment; + return buildAttachment(attachment.filename, attachment.blob, { shouldSendInHighQuality }); + })); handleSetAttachments(newAttachments); }); diff --git a/src/components/middle/composer/hooks/useClipboardPaste.ts b/src/components/middle/composer/hooks/useClipboardPaste.ts index 169c1ab75..8779269f9 100644 --- a/src/components/middle/composer/hooks/useClipboardPaste.ts +++ b/src/components/middle/composer/hooks/useClipboardPaste.ts @@ -32,6 +32,7 @@ const useClipboardPaste = ( shouldStripCustomEmoji?: boolean, onCustomEmojiStripped?: VoidFunction, shouldUpdateAttachmentCompression?: boolean, + shouldSkipFilePaste?: boolean, ) => { const { showNotification, @@ -100,7 +101,7 @@ const useClipboardPaste = ( } const hasText = textToPaste && textToPaste.text; - let shouldSetAttachments = files?.length && !isWordDocument; + let shouldSetAttachments = files?.length && !isWordDocument && !shouldSkipFilePaste; const newAttachments = files ? await Promise.all(files.map((file) => buildAttachment(file.name, file))) : []; const canReplace = (editedMessage && newAttachments?.length @@ -154,7 +155,7 @@ const useClipboardPaste = ( }; }, [ insertTextAndUpdateCursor, editedMessage, setAttachments, isActive, shouldStripCustomEmoji, - onCustomEmojiStripped, setNextText, lang, shouldUpdateAttachmentCompression, + onCustomEmojiStripped, setNextText, lang, shouldUpdateAttachmentCompression, shouldSkipFilePaste, ]); }; diff --git a/src/components/middle/message/Video.tsx b/src/components/middle/message/Video.tsx index f8984e6ca..a97cce38d 100644 --- a/src/components/middle/message/Video.tsx +++ b/src/components/middle/message/Video.tsx @@ -299,14 +299,16 @@ const Video = ({ style={forcedWidth ? `width: ${forcedWidth}px` : undefined} /> )} - + {previewBlobUrl && ( + + )} {hasThumb && !isPreviewPreloaded && ( )} diff --git a/src/styles/icons.css b/src/styles/icons.css index 38f2e1352..6fab061ab 100644 --- a/src/styles/icons.css +++ b/src/styles/icons.css @@ -3,8 +3,8 @@ font-weight: normal; font-style: normal; font-display: block; - src: url("./icons.woff2?d1a9cacb64e401206f928a39a587a2bd") format("woff2"), -url("./icons.woff?d1a9cacb64e401206f928a39a587a2bd") format("woff"); + src: url("./icons.woff2?c37e5ad179a86df12e2f9214af6a855a") format("woff2"), +url("./icons.woff?c37e5ad179a86df12e2f9214af6a855a") format("woff"); } .icon-char::before { @@ -900,87 +900,90 @@ url("./icons.woff?d1a9cacb64e401206f928a39a587a2bd") format("woff"); .icon-add-filled::before { content: "\f225"; } -.icon-active-sessions::before { +.icon-add-caption::before { content: "\f226"; } -.icon-folder-tabs-user::before { +.icon-active-sessions::before { content: "\f227"; } -.icon-folder-tabs-star::before { +.icon-rating-icons-negative::before { content: "\f228"; } -.icon-folder-tabs-group::before { +.icon-rating-icons-level90::before { content: "\f229"; } -.icon-folder-tabs-folder::before { +.icon-rating-icons-level9::before { content: "\f22a"; } -.icon-folder-tabs-chats::before { +.icon-rating-icons-level80::before { content: "\f22b"; } -.icon-folder-tabs-chat::before { +.icon-rating-icons-level8::before { content: "\f22c"; } -.icon-folder-tabs-channel::before { +.icon-rating-icons-level70::before { content: "\f22d"; } -.icon-folder-tabs-bot::before { +.icon-rating-icons-level7::before { content: "\f22e"; } -.icon-rating-icons-negative::before { +.icon-rating-icons-level60::before { content: "\f22f"; } -.icon-rating-icons-level90::before { +.icon-rating-icons-level6::before { content: "\f230"; } -.icon-rating-icons-level9::before { +.icon-rating-icons-level50::before { content: "\f231"; } -.icon-rating-icons-level80::before { +.icon-rating-icons-level5::before { content: "\f232"; } -.icon-rating-icons-level8::before { +.icon-rating-icons-level40::before { content: "\f233"; } -.icon-rating-icons-level70::before { +.icon-rating-icons-level4::before { content: "\f234"; } -.icon-rating-icons-level7::before { +.icon-rating-icons-level30::before { content: "\f235"; } -.icon-rating-icons-level60::before { +.icon-rating-icons-level3::before { content: "\f236"; } -.icon-rating-icons-level6::before { +.icon-rating-icons-level20::before { content: "\f237"; } -.icon-rating-icons-level50::before { +.icon-rating-icons-level2::before { content: "\f238"; } -.icon-rating-icons-level5::before { +.icon-rating-icons-level10::before { content: "\f239"; } -.icon-rating-icons-level40::before { +.icon-rating-icons-level1::before { content: "\f23a"; } -.icon-rating-icons-level4::before { +.icon-folder-tabs-user::before { content: "\f23b"; } -.icon-rating-icons-level30::before { +.icon-folder-tabs-star::before { content: "\f23c"; } -.icon-rating-icons-level3::before { +.icon-folder-tabs-group::before { content: "\f23d"; } -.icon-rating-icons-level20::before { +.icon-folder-tabs-folder::before { content: "\f23e"; } -.icon-rating-icons-level2::before { +.icon-folder-tabs-chats::before { content: "\f23f"; } -.icon-rating-icons-level10::before { +.icon-folder-tabs-chat::before { content: "\f240"; } -.icon-rating-icons-level1::before { +.icon-folder-tabs-channel::before { content: "\f241"; } +.icon-folder-tabs-bot::before { + content: "\f242"; +} diff --git a/src/styles/icons.scss b/src/styles/icons.scss index de98a69b0..08cdede72 100644 --- a/src/styles/icons.scss +++ b/src/styles/icons.scss @@ -309,32 +309,33 @@ $icons-map: ( "add-user-filled": "\f223", "add-one-badge": "\f224", "add-filled": "\f225", - "active-sessions": "\f226", - "folder-tabs-user": "\f227", - "folder-tabs-star": "\f228", - "folder-tabs-group": "\f229", - "folder-tabs-folder": "\f22a", - "folder-tabs-chats": "\f22b", - "folder-tabs-chat": "\f22c", - "folder-tabs-channel": "\f22d", - "folder-tabs-bot": "\f22e", - "rating-icons-negative": "\f22f", - "rating-icons-level90": "\f230", - "rating-icons-level9": "\f231", - "rating-icons-level80": "\f232", - "rating-icons-level8": "\f233", - "rating-icons-level70": "\f234", - "rating-icons-level7": "\f235", - "rating-icons-level60": "\f236", - "rating-icons-level6": "\f237", - "rating-icons-level50": "\f238", - "rating-icons-level5": "\f239", - "rating-icons-level40": "\f23a", - "rating-icons-level4": "\f23b", - "rating-icons-level30": "\f23c", - "rating-icons-level3": "\f23d", - "rating-icons-level20": "\f23e", - "rating-icons-level2": "\f23f", - "rating-icons-level10": "\f240", - "rating-icons-level1": "\f241", + "add-caption": "\f226", + "active-sessions": "\f227", + "rating-icons-negative": "\f228", + "rating-icons-level90": "\f229", + "rating-icons-level9": "\f22a", + "rating-icons-level80": "\f22b", + "rating-icons-level8": "\f22c", + "rating-icons-level70": "\f22d", + "rating-icons-level7": "\f22e", + "rating-icons-level60": "\f22f", + "rating-icons-level6": "\f230", + "rating-icons-level50": "\f231", + "rating-icons-level5": "\f232", + "rating-icons-level40": "\f233", + "rating-icons-level4": "\f234", + "rating-icons-level30": "\f235", + "rating-icons-level3": "\f236", + "rating-icons-level20": "\f237", + "rating-icons-level2": "\f238", + "rating-icons-level10": "\f239", + "rating-icons-level1": "\f23a", + "folder-tabs-user": "\f23b", + "folder-tabs-star": "\f23c", + "folder-tabs-group": "\f23d", + "folder-tabs-folder": "\f23e", + "folder-tabs-chats": "\f23f", + "folder-tabs-chat": "\f240", + "folder-tabs-channel": "\f241", + "folder-tabs-bot": "\f242", ); diff --git a/src/styles/icons.woff b/src/styles/icons.woff index 7534a641d..b98a8b95c 100644 Binary files a/src/styles/icons.woff and b/src/styles/icons.woff differ diff --git a/src/styles/icons.woff2 b/src/styles/icons.woff2 index 1f50c0b46..ed35c81ed 100644 Binary files a/src/styles/icons.woff2 and b/src/styles/icons.woff2 differ diff --git a/src/types/icons/font.ts b/src/types/icons/font.ts index fd6833b73..b431979bc 100644 --- a/src/types/icons/font.ts +++ b/src/types/icons/font.ts @@ -292,15 +292,8 @@ export type FontIconName = | 'add-user-filled' | 'add-one-badge' | 'add-filled' + | 'add-caption' | 'active-sessions' - | 'folder-tabs-user' - | 'folder-tabs-star' - | 'folder-tabs-group' - | 'folder-tabs-folder' - | 'folder-tabs-chats' - | 'folder-tabs-chat' - | 'folder-tabs-channel' - | 'folder-tabs-bot' | 'rating-icons-negative' | 'rating-icons-level90' | 'rating-icons-level9' @@ -319,4 +312,12 @@ export type FontIconName = | 'rating-icons-level20' | 'rating-icons-level2' | 'rating-icons-level10' - | 'rating-icons-level1'; + | 'rating-icons-level1' + | 'folder-tabs-user' + | 'folder-tabs-star' + | 'folder-tabs-group' + | 'folder-tabs-folder' + | 'folder-tabs-chats' + | 'folder-tabs-chat' + | 'folder-tabs-channel' + | 'folder-tabs-bot'; diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 4f880dde8..6bad2cca3 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1958,6 +1958,8 @@ export interface LangPair { 'AttachmentMenuUngroupAllMedia': undefined; 'AttachmentMenuEnableSpoiler': undefined; 'AttachmentMenuDisableSpoiler': undefined; + 'AttachmentSendGif': undefined; + 'AttachmentReplaceGif': undefined; 'AttachmentDragAddItems': undefined; 'AttachmentCaptionPlaceholder': undefined; 'MessageSummaryTitle': undefined; @@ -2002,6 +2004,7 @@ export interface LangPair { 'GiftPreviewToggleRegularModels': undefined; 'AriaGiftPreviewPlay': undefined; 'AriaGiftPreviewStop': undefined; + 'MenuAddCaption': undefined; } export interface LangPairWithVariables {