GIF: Support sending with caption (#6755)
This commit is contained in:
parent
07d24e2d43
commit
b5fed8276a
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
1
src/assets/font-icons/add-caption.svg
Normal file
1
src/assets/font-icons/add-caption.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="a" viewBox="0 0 32 32"><path d="M24.241 8.014H3.18c-.741 0-1.342-.604-1.342-1.35 0-.745.601-1.349 1.342-1.349H24.24c.74 0 1.342.604 1.342 1.35 0 .745-.601 1.35-1.342 1.35" /><rect width="13.437" height="2.699" x="1.837" y="22.019" rx="1.35" ry="1.35"/><rect width="19.521" height="2.699" x="1.837" y="13.667" rx="1.35" ry="1.35"/><rect width="2.699" height="11.86" x="22.883" y="17.439" rx="1.35" ry="1.35"/><rect width="11.86" height="2.699" x="18.302" y="22.019" rx="1.35" ry="1.35"/></svg>
|
||||
|
After Width: | Height: | Size: 540 B |
@ -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";
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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<OwnProps> = ({
|
||||
@ -43,13 +45,15 @@ const GifButton: FC<OwnProps> = ({
|
||||
isDisabled,
|
||||
className,
|
||||
observeIntersection,
|
||||
isSavedMessages,
|
||||
onClick,
|
||||
onUnsaveClick,
|
||||
isSavedMessages,
|
||||
onAddCaption,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
|
||||
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<OwnProps> = ({
|
||||
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<OwnProps> = ({
|
||||
}, undefined, true);
|
||||
});
|
||||
|
||||
const handleAddCaption = useLastCallback(() => {
|
||||
onAddCaption?.({
|
||||
...gif,
|
||||
blobUrl: videoData,
|
||||
});
|
||||
});
|
||||
|
||||
const handleMouseDown = useLastCallback((e: React.MouseEvent<HTMLElement>) => {
|
||||
preventMessageInputBlurWithBubbling(e);
|
||||
handleBeforeContextMenu(e);
|
||||
@ -182,17 +194,21 @@ const GifButton: FC<OwnProps> = ({
|
||||
getTriggerElement={getTriggerElement}
|
||||
getRootElement={getRootElement}
|
||||
getMenuElement={getMenuElement}
|
||||
getLayout={getLayout}
|
||||
className="gif-context-menu"
|
||||
autoClose
|
||||
onClose={handleContextMenuClose}
|
||||
onCloseAnimationEnd={handleContextMenuHide}
|
||||
>
|
||||
{!isSavedMessages && <MenuItem onClick={handleSendQuiet} icon="mute">{lang('SendWithoutSound')}</MenuItem>}
|
||||
{!isSavedMessages && <MenuItem onClick={handleSendQuiet} icon="mute">{oldLang('SendWithoutSound')}</MenuItem>}
|
||||
<MenuItem onClick={handleSendScheduled} icon="calendar">
|
||||
{lang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')}
|
||||
{oldLang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')}
|
||||
</MenuItem>
|
||||
{onAddCaption && (
|
||||
<MenuItem icon="add-caption" onClick={handleAddCaption}>{lang('MenuAddCaption')}</MenuItem>
|
||||
)}
|
||||
{onUnsaveClick && (
|
||||
<MenuItem destructive icon="delete" onClick={handleContextDelete}>{lang('Delete')}</MenuItem>
|
||||
<MenuItem destructive icon="delete" onClick={handleContextDelete}>{oldLang('Delete')}</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
)}
|
||||
|
||||
@ -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<SVGSVGElement>();
|
||||
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<number | undefined>(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 && (
|
||||
<MenuItem icon="add" onClick={handleDocumentSelect}>{lang('Add')}</MenuItem>
|
||||
)}
|
||||
{hasMedia && (
|
||||
@ -621,7 +646,7 @@ const AttachmentModal = ({
|
||||
))
|
||||
}
|
||||
{
|
||||
!shouldForceAsFile && !shouldForceCompression && (isSendingCompressed ? (
|
||||
!shouldForceAsFile && !shouldForceCompression && !hasGifFromPicker && (isSendingCompressed ? (
|
||||
|
||||
<MenuItem icon="document" onClick={handleToggleShouldCompress}>
|
||||
{lang(isMultiple ? 'AttachmentMenuSendAllAsFiles' : 'AttachmentMenuSendAsFiles')}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<OwnProps & StateProps> = ({
|
||||
savedGifs,
|
||||
isSavedMessages,
|
||||
onGifSelect,
|
||||
onGifAddCaption,
|
||||
}) => {
|
||||
const { loadSavedGifs, saveGif } = getActions();
|
||||
|
||||
@ -80,9 +82,10 @@ const GifPicker: FC<OwnProps & StateProps> = ({
|
||||
gif={gif}
|
||||
observeIntersection={observeIntersection}
|
||||
isDisabled={!loadAndPlay}
|
||||
isSavedMessages={isSavedMessages}
|
||||
onClick={canSendGifs ? onGifSelect : undefined}
|
||||
onUnsaveClick={handleUnsaveClick}
|
||||
isSavedMessages={isSavedMessages}
|
||||
onAddCaption={canSendGifs ? onGifAddCaption : undefined}
|
||||
/>
|
||||
))
|
||||
) : canRenderContents && savedGifs ? (
|
||||
|
||||
@ -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<OwnProps & StateProps> = ({
|
||||
onCustomEmojiSelect,
|
||||
onStickerSelect,
|
||||
onGifSelect,
|
||||
onGifAddCaption,
|
||||
onRemoveSymbol,
|
||||
onSearchOpen,
|
||||
addRecentEmoji,
|
||||
@ -239,6 +241,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
loadAndPlay={canSendGifs ? isOpen && (isActive || isFrom) : false}
|
||||
canSendGifs={canSendGifs}
|
||||
onGifSelect={onGifSelect}
|
||||
onGifAddCaption={onGifAddCaption}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<OwnProps> = ({
|
||||
onCustomEmojiSelect,
|
||||
onStickerSelect,
|
||||
onGifSelect,
|
||||
onGifAddCaption,
|
||||
onRemoveSymbol,
|
||||
onEmojiSelect,
|
||||
closeBotCommandMenu,
|
||||
@ -181,6 +183,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
||||
onStickerSelect={onStickerSelect}
|
||||
onCustomEmojiSelect={onCustomEmojiSelect}
|
||||
onGifSelect={onGifSelect}
|
||||
onGifAddCaption={onGifAddCaption}
|
||||
onRemoveSymbol={onRemoveSymbol}
|
||||
onSearchOpen={handleSearchOpen}
|
||||
addRecentEmoji={addRecentEmoji}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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,
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
@ -299,14 +299,16 @@ const Video = <T,>({
|
||||
style={forcedWidth ? `width: ${forcedWidth}px` : undefined}
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
ref={previewRef}
|
||||
src={previewBlobUrl}
|
||||
className={buildClassName('thumbnail', withBlurredBackground && 'with-blurred-bg')}
|
||||
alt=""
|
||||
style={forcedWidth ? `width: ${forcedWidth}px;` : undefined}
|
||||
draggable={!isProtected}
|
||||
/>
|
||||
{previewBlobUrl && (
|
||||
<img
|
||||
ref={previewRef}
|
||||
src={previewBlobUrl}
|
||||
className={buildClassName('thumbnail', withBlurredBackground && 'with-blurred-bg')}
|
||||
alt=""
|
||||
style={forcedWidth ? `width: ${forcedWidth}px;` : undefined}
|
||||
draggable={!isProtected}
|
||||
/>
|
||||
)}
|
||||
{hasThumb && !isPreviewPreloaded && (
|
||||
<canvas ref={thumbRef} className="thumbnail" />
|
||||
)}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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",
|
||||
);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -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';
|
||||
|
||||
3
src/types/language.d.ts
vendored
3
src/types/language.d.ts
vendored
@ -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<V = LangVariable> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user