Attachment Modal: Redesign (#2253)
This commit is contained in:
parent
288ce625a3
commit
a9dd1221fb
@ -1393,62 +1393,66 @@ function buildUploadingMedia(
|
|||||||
previewBlobUrl,
|
previewBlobUrl,
|
||||||
mimeType,
|
mimeType,
|
||||||
size,
|
size,
|
||||||
|
audio,
|
||||||
|
shouldSendAsFile,
|
||||||
} = attachment;
|
} = attachment;
|
||||||
|
|
||||||
if (attachment.quick) {
|
if (!shouldSendAsFile) {
|
||||||
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
|
if (attachment.quick) {
|
||||||
const { width, height } = attachment.quick;
|
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
|
||||||
|
const { width, height } = attachment.quick;
|
||||||
|
return {
|
||||||
|
photo: {
|
||||||
|
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
||||||
|
sizes: [],
|
||||||
|
thumbnail: { width, height, dataUri: '' }, // Used only for dimensions
|
||||||
|
blobUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
||||||
|
const { width, height, duration } = attachment.quick;
|
||||||
|
return {
|
||||||
|
video: {
|
||||||
|
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
||||||
|
mimeType,
|
||||||
|
duration: duration || 0,
|
||||||
|
fileName,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
blobUrl,
|
||||||
|
...(previewBlobUrl && { thumbnail: { width, height, dataUri: previewBlobUrl } }),
|
||||||
|
size,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attachment.voice) {
|
||||||
|
const { duration, waveform } = attachment.voice;
|
||||||
|
const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH);
|
||||||
return {
|
return {
|
||||||
photo: {
|
voice: {
|
||||||
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
||||||
sizes: [],
|
duration,
|
||||||
thumbnail: { width, height, dataUri: '' }, // Used only for dimensions
|
waveform: inputWaveform,
|
||||||
blobUrl,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) {
|
||||||
const { width, height, duration } = attachment.quick;
|
const { duration, performer, title } = audio || {};
|
||||||
return {
|
return {
|
||||||
video: {
|
audio: {
|
||||||
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
||||||
mimeType,
|
mimeType,
|
||||||
duration: duration || 0,
|
|
||||||
fileName,
|
fileName,
|
||||||
width,
|
|
||||||
height,
|
|
||||||
blobUrl,
|
|
||||||
...(previewBlobUrl && { thumbnail: { width, height, dataUri: previewBlobUrl } }),
|
|
||||||
size,
|
size,
|
||||||
|
duration: duration || 0,
|
||||||
|
title,
|
||||||
|
performer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (attachment.voice) {
|
|
||||||
const { duration, waveform } = attachment.voice;
|
|
||||||
const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH);
|
|
||||||
return {
|
|
||||||
voice: {
|
|
||||||
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
|
||||||
duration,
|
|
||||||
waveform: inputWaveform,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) {
|
|
||||||
const { duration, performer, title } = attachment.audio || {};
|
|
||||||
return {
|
|
||||||
audio: {
|
|
||||||
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
|
||||||
mimeType,
|
|
||||||
fileName,
|
|
||||||
size,
|
|
||||||
duration: duration || 0,
|
|
||||||
title,
|
|
||||||
performer,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
document: {
|
document: {
|
||||||
mimeType,
|
mimeType,
|
||||||
|
|||||||
@ -566,7 +566,7 @@ export async function rescheduleMessage({
|
|||||||
|
|
||||||
async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) {
|
async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) {
|
||||||
const {
|
const {
|
||||||
filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl,
|
filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl, shouldSendAsFile,
|
||||||
} = attachment;
|
} = attachment;
|
||||||
|
|
||||||
const patchedOnProgress: ApiOnProgress = (progress) => {
|
const patchedOnProgress: ApiOnProgress = (progress) => {
|
||||||
@ -584,41 +584,43 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment,
|
|||||||
const thumb = thumbFile ? await uploadFile(thumbFile) : undefined;
|
const thumb = thumbFile ? await uploadFile(thumbFile) : undefined;
|
||||||
|
|
||||||
const attributes: GramJs.TypeDocumentAttribute[] = [new GramJs.DocumentAttributeFilename({ fileName: filename })];
|
const attributes: GramJs.TypeDocumentAttribute[] = [new GramJs.DocumentAttributeFilename({ fileName: filename })];
|
||||||
if (quick) {
|
if (!shouldSendAsFile) {
|
||||||
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
|
if (quick) {
|
||||||
return new GramJs.InputMediaUploadedPhoto({ file: inputFile });
|
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
|
||||||
}
|
return new GramJs.InputMediaUploadedPhoto({ file: inputFile });
|
||||||
|
}
|
||||||
|
|
||||||
if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
||||||
const { width, height, duration } = quick;
|
const { width, height, duration } = quick;
|
||||||
if (duration !== undefined) {
|
if (duration !== undefined) {
|
||||||
attributes.push(new GramJs.DocumentAttributeVideo({
|
attributes.push(new GramJs.DocumentAttributeVideo({
|
||||||
duration,
|
duration,
|
||||||
w: width,
|
w: width,
|
||||||
h: height,
|
h: height,
|
||||||
supportsStreaming: true,
|
supportsStreaming: true,
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (audio) {
|
if (audio) {
|
||||||
const { duration, title, performer } = audio;
|
const { duration, title, performer } = audio;
|
||||||
attributes.push(new GramJs.DocumentAttributeAudio({
|
attributes.push(new GramJs.DocumentAttributeAudio({
|
||||||
duration,
|
duration,
|
||||||
title,
|
title,
|
||||||
performer,
|
performer,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (voice) {
|
if (voice) {
|
||||||
const { duration, waveform } = voice;
|
const { duration, waveform } = voice;
|
||||||
const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH);
|
const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH);
|
||||||
attributes.push(new GramJs.DocumentAttributeAudio({
|
attributes.push(new GramJs.DocumentAttributeAudio({
|
||||||
voice: true,
|
voice: true,
|
||||||
duration,
|
duration,
|
||||||
waveform: Buffer.from(inputWaveform),
|
waveform: Buffer.from(inputWaveform),
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GramJs.InputMediaUploadedDocument({
|
return new GramJs.InputMediaUploadedDocument({
|
||||||
@ -626,6 +628,7 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment,
|
|||||||
mimeType,
|
mimeType,
|
||||||
attributes,
|
attributes,
|
||||||
thumb,
|
thumb,
|
||||||
|
forceFile: shouldSendAsFile || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,10 @@ export interface ApiAttachment {
|
|||||||
performer?: string;
|
performer?: string;
|
||||||
};
|
};
|
||||||
previewBlobUrl?: string;
|
previewBlobUrl?: string;
|
||||||
|
|
||||||
|
shouldSendAsFile?: boolean;
|
||||||
|
|
||||||
|
uniqueId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiWallpaper {
|
export interface ApiWallpaper {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -140,7 +140,7 @@ const File: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="file-info">
|
<div className="file-info">
|
||||||
<div className="file-title" dir="auto">{renderText(name)}</div>
|
<div className="file-title" dir="auto" title={name}>{renderText(name)}</div>
|
||||||
<div className="file-subtitle" dir="auto">
|
<div className="file-subtitle" dir="auto">
|
||||||
<span>
|
<span>
|
||||||
{isTransferring && transferProgress ? `${Math.round(transferProgress * 100)}%` : sizeString}
|
{isTransferring && transferProgress ? `${Math.round(transferProgress * 100)}%` : sizeString}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import type { ApiWallpaper } from '../../../api/types';
|
|||||||
|
|
||||||
import { DARK_THEME_PATTERN_COLOR, DEFAULT_PATTERN_COLOR } from '../../../config';
|
import { DARK_THEME_PATTERN_COLOR, DEFAULT_PATTERN_COLOR } from '../../../config';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
|
import { validateFiles } from '../../../util/files';
|
||||||
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
||||||
import { getAverageColor, getPatternColor, rgb2hex } from '../../../util/colors';
|
import { getAverageColor, getPatternColor, rgb2hex } from '../../../util/colors';
|
||||||
import { selectTheme } from '../../../global/selectors';
|
import { selectTheme } from '../../../global/selectors';
|
||||||
@ -68,8 +69,9 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
|||||||
const handleFileSelect = useCallback((e: Event) => {
|
const handleFileSelect = useCallback((e: Event) => {
|
||||||
const { files } = e.target as HTMLInputElement;
|
const { files } = e.target as HTMLInputElement;
|
||||||
|
|
||||||
if (files && files.length > 0) {
|
const validatedFiles = validateFiles(files);
|
||||||
uploadWallpaper(files[0]);
|
if (validatedFiles?.length) {
|
||||||
|
uploadWallpaper(validatedFiles[0]);
|
||||||
}
|
}
|
||||||
}, [uploadWallpaper]);
|
}, [uploadWallpaper]);
|
||||||
|
|
||||||
|
|||||||
@ -188,7 +188,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canDownload = selectCanDownloadSelectedMessages(global);
|
const canDownload = selectCanDownloadSelectedMessages(global);
|
||||||
const { messageIds: selectedMessageIds } = global.selectedMessages || {};
|
const { messageIds: selectedMessageIds } = global.selectedMessages || {};
|
||||||
const hasProtectedMessage = chatId ? selectHasProtectedMessage(global, chatId, selectedMessageIds) : false;
|
const hasProtectedMessage = chatId ? selectHasProtectedMessage(global, chatId, selectedMessageIds) : false;
|
||||||
const canForward = chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false;
|
const canForward = !isSchedule && chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false;
|
||||||
const isForwardModalOpen = global.forwardMessages.isModalShown;
|
const isForwardModalOpen = global.forwardMessages.isModalShown;
|
||||||
const isAnyModalOpen = Boolean(isForwardModalOpen || global.requestedDraft
|
const isAnyModalOpen = Boolean(isForwardModalOpen || global.requestedDraft
|
||||||
|| global.requestedAttachBotInChat || global.requestedAttachBotInstall);
|
|| global.requestedAttachBotInChat || global.requestedAttachBotInstall);
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
IS_SINGLE_COLUMN_LAYOUT,
|
IS_SINGLE_COLUMN_LAYOUT,
|
||||||
IS_TABLET_COLUMN_LAYOUT,
|
IS_TABLET_COLUMN_LAYOUT,
|
||||||
IS_TOUCH_ENV,
|
|
||||||
MASK_IMAGE_DISABLED,
|
MASK_IMAGE_DISABLED,
|
||||||
} from '../../util/environment';
|
} from '../../util/environment';
|
||||||
import { DropAreaState } from './composer/DropArea';
|
import { DropAreaState } from './composer/DropArea';
|
||||||
@ -285,10 +284,6 @@ const MiddleColumn: FC<StateProps> = ({
|
|||||||
}, [shouldLoadFullChat, chatId, isReady, loadFullChat]);
|
}, [shouldLoadFullChat, chatId, isReady, loadFullChat]);
|
||||||
|
|
||||||
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
if (IS_TOUCH_ENV) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { items } = e.dataTransfer || {};
|
const { items } = e.dataTransfer || {};
|
||||||
const shouldDrawQuick = items && items.length > 0 && Array.from(items)
|
const shouldDrawQuick = items && items.length > 0 && Array.from(items)
|
||||||
// Filter unnecessary element for drag and drop images in Firefox (https://github.com/Ajaxy/telegram-tt/issues/49)
|
// Filter unnecessary element for drag and drop images in Firefox (https://github.com/Ajaxy/telegram-tt/issues/49)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import type { ISettings } from '../../../types';
|
|||||||
import { CONTENT_TYPES_WITH_PREVIEW } from '../../../config';
|
import { CONTENT_TYPES_WITH_PREVIEW } from '../../../config';
|
||||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
||||||
|
import { validateFiles } from '../../../util/files';
|
||||||
|
|
||||||
import useMouseInside from '../../../hooks/useMouseInside';
|
import useMouseInside from '../../../hooks/useMouseInside';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -31,7 +32,7 @@ export type OwnProps = {
|
|||||||
isScheduled?: boolean;
|
isScheduled?: boolean;
|
||||||
attachBots: GlobalState['attachMenu']['bots'];
|
attachBots: GlobalState['attachMenu']['bots'];
|
||||||
peerType?: ApiAttachMenuPeerType;
|
peerType?: ApiAttachMenuPeerType;
|
||||||
onFileSelect: (files: File[], isQuick: boolean) => void;
|
onFileSelect: (files: File[], shouldSuggestCompression?: boolean) => void;
|
||||||
onPollCreate: () => void;
|
onPollCreate: () => void;
|
||||||
theme: ISettings['theme'];
|
theme: ISettings['theme'];
|
||||||
};
|
};
|
||||||
@ -67,11 +68,12 @@ const AttachMenu: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isAttachMenuOpen, openAttachMenu, closeAttachMenu]);
|
}, [isAttachMenuOpen, openAttachMenu, closeAttachMenu]);
|
||||||
|
|
||||||
const handleFileSelect = useCallback((e: Event, isQuick: boolean) => {
|
const handleFileSelect = useCallback((e: Event, shouldSuggestCompression?: boolean) => {
|
||||||
const { files } = e.target as HTMLInputElement;
|
const { files } = e.target as HTMLInputElement;
|
||||||
|
const validatedFiles = validateFiles(files);
|
||||||
|
|
||||||
if (files && files.length > 0) {
|
if (validatedFiles?.length) {
|
||||||
onFileSelect(Array.from(files), isQuick);
|
onFileSelect(validatedFiles, shouldSuggestCompression);
|
||||||
}
|
}
|
||||||
}, [onFileSelect]);
|
}, [onFileSelect]);
|
||||||
|
|
||||||
|
|||||||
@ -5,65 +5,49 @@
|
|||||||
max-width: 26.25rem;
|
max-width: 26.25rem;
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
padding-bottom: 1.5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-header-condensed {
|
||||||
|
padding: 0.3125rem 0.5rem !important;
|
||||||
|
border-bottom: 1px solid var(--color-borders);
|
||||||
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
padding: 0.5rem 1.25rem 1.875rem;
|
padding: 0;
|
||||||
max-height: calc(100vh - 3.25rem);
|
max-height: calc(100vh - 3.25rem);
|
||||||
@media (max-width: 600px) {
|
|
||||||
padding-bottom: 0.25rem;
|
overflow-x: auto;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-wrapper {
|
.attachments-wrapper {
|
||||||
max-height: 26rem;
|
max-height: 26rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-bottom: 1.5rem;
|
gap: 0.5rem;
|
||||||
|
margin: 0 0.25rem;
|
||||||
|
padding: 0.5rem 0.25rem;
|
||||||
|
|
||||||
video,
|
@media (max-width: 600px) {
|
||||||
img {
|
max-height: 80vh;
|
||||||
flex: 1;
|
|
||||||
width: calc(50% - 0.15rem);
|
|
||||||
height: 12rem;
|
|
||||||
margin-bottom: 0.3125rem;
|
|
||||||
border-radius: var(--border-radius-default);
|
|
||||||
object-fit: cover;
|
|
||||||
|
|
||||||
&:only-child {
|
|
||||||
height: auto;
|
|
||||||
max-height: 25rem;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
margin-left: 0.3125rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.document-wrapper {
|
|
||||||
max-height: 25rem;
|
|
||||||
overflow: auto;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0.75rem 0 1.75rem;
|
|
||||||
|
|
||||||
.File:not(:last-child) {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-icon {
|
|
||||||
cursor: default !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-caption-wrapper {
|
.attachment-caption-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -0.5rem;
|
||||||
|
top: 0;
|
||||||
|
right: -0.5rem;
|
||||||
|
border-top: 1px solid var(--color-borders);
|
||||||
|
}
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
@ -75,6 +59,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachment-caption {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-checkbox {
|
||||||
|
margin-left: -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.drop-target {
|
.drop-target {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -119,9 +112,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.attachment-caption-wrapper,
|
.attachment-caption-wrapper,
|
||||||
.document-wrapper,
|
.attachments-wrapper,
|
||||||
.media-wrapper,
|
.input-scroller {
|
||||||
.form-control {
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,13 +128,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.CustomSendMenu {
|
.CustomSendMenu {
|
||||||
bottom: auto;
|
bottom: 2.25rem;
|
||||||
|
|
||||||
.is-pointer-env & > .backdrop {
|
.is-pointer-env & > .backdrop {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: -2.25rem;
|
top: -2rem;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
height: 2.75rem;
|
height: 3.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.AttachmentModal--send-wrapper {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AttachmentModal--send {
|
||||||
|
height: 2.5rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,28 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef,
|
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import type { ApiAttachment, ApiChatMember, ApiSticker } from '../../../api/types';
|
import type { ApiAttachment, ApiChatMember, ApiSticker } from '../../../api/types';
|
||||||
|
import type { GlobalState } from '../../../global/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
BASE_EMOJI_KEYWORD_LANG,
|
||||||
EDITABLE_INPUT_MODAL_ID,
|
EDITABLE_INPUT_MODAL_ID,
|
||||||
SUPPORTED_AUDIO_CONTENT_TYPES,
|
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||||
SUPPORTED_IMAGE_CONTENT_TYPES,
|
SUPPORTED_IMAGE_CONTENT_TYPES,
|
||||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { getFileExtension } from '../../common/helpers/documentInfo';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
||||||
import { hasPreview } from '../../../util/files';
|
|
||||||
import { getHtmlTextLength } from './helpers/getHtmlTextLength';
|
import { getHtmlTextLength } from './helpers/getHtmlTextLength';
|
||||||
|
import { selectChat, selectIsChatWithSelf } from '../../../global/selectors';
|
||||||
|
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||||
|
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { validateFiles } from '../../../util/files';
|
||||||
|
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useMentionTooltip from './hooks/useMentionTooltip';
|
import useMentionTooltip from './hooks/useMentionTooltip';
|
||||||
@ -29,12 +35,14 @@ import useCustomEmojiTooltip from './hooks/useCustomEmojiTooltip';
|
|||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import Modal from '../../ui/Modal';
|
import Modal from '../../ui/Modal';
|
||||||
import File from '../../common/File';
|
|
||||||
import MessageInput from './MessageInput';
|
import MessageInput from './MessageInput';
|
||||||
import MentionTooltip from './MentionTooltip';
|
import MentionTooltip from './MentionTooltip';
|
||||||
import EmojiTooltip from './EmojiTooltip.async';
|
import EmojiTooltip from './EmojiTooltip.async';
|
||||||
import CustomSendMenu from './CustomSendMenu.async';
|
import CustomSendMenu from './CustomSendMenu.async';
|
||||||
import CustomEmojiTooltip from './CustomEmojiTooltip.async';
|
import CustomEmojiTooltip from './CustomEmojiTooltip.async';
|
||||||
|
import AttachmentModalItem from './AttachmentModalItem';
|
||||||
|
import DropdownMenu from '../../ui/DropdownMenu';
|
||||||
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
|
||||||
import './AttachmentModal.scss';
|
import './AttachmentModal.scss';
|
||||||
|
|
||||||
@ -45,28 +53,34 @@ export type OwnProps = {
|
|||||||
caption: string;
|
caption: string;
|
||||||
canShowCustomSendMenu?: boolean;
|
canShowCustomSendMenu?: boolean;
|
||||||
isReady?: boolean;
|
isReady?: boolean;
|
||||||
|
shouldSchedule?: boolean;
|
||||||
|
shouldSuggestCompression?: boolean;
|
||||||
|
onCaptionUpdate: (html: string) => void;
|
||||||
|
onSend: (sendCompressed: boolean, sendGrouped: boolean) => void;
|
||||||
|
onFileAppend: (files: File[]) => void;
|
||||||
|
onDelete: (attachmentIndex: number) => void;
|
||||||
|
onClear: () => void;
|
||||||
|
onSendSilent: (sendCompressed: boolean, sendGrouped: boolean) => void;
|
||||||
|
onSendScheduled: (sendCompressed: boolean, sendGrouped: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
groupChatMembers?: ApiChatMember[];
|
groupChatMembers?: ApiChatMember[];
|
||||||
recentEmojis: string[];
|
recentEmojis: string[];
|
||||||
baseEmojiKeywords?: Record<string, string[]>;
|
baseEmojiKeywords?: Record<string, string[]>;
|
||||||
emojiKeywords?: Record<string, string[]>;
|
emojiKeywords?: Record<string, string[]>;
|
||||||
shouldSchedule?: boolean;
|
|
||||||
shouldSuggestCustomEmoji?: boolean;
|
shouldSuggestCustomEmoji?: boolean;
|
||||||
customEmojiForEmoji?: ApiSticker[];
|
customEmojiForEmoji?: ApiSticker[];
|
||||||
captionLimit: number;
|
captionLimit: number;
|
||||||
onCaptionUpdate: (html: string) => void;
|
attachmentSettings: GlobalState['attachmentSettings'];
|
||||||
onSend: () => void;
|
|
||||||
onFileAppend: (files: File[], isQuick: boolean) => void;
|
|
||||||
onClear: () => void;
|
|
||||||
onSendSilent: () => void;
|
|
||||||
onSendScheduled: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DROP_LEAVE_TIMEOUT_MS = 150;
|
const DROP_LEAVE_TIMEOUT_MS = 150;
|
||||||
const CAPTION_SYMBOLS_LEFT_THRESHOLD = 100;
|
const CAPTION_SYMBOLS_LEFT_THRESHOLD = 100;
|
||||||
|
|
||||||
const AttachmentModal: FC<OwnProps> = ({
|
const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
attachments,
|
attachments,
|
||||||
@ -83,24 +97,39 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
shouldSchedule,
|
shouldSchedule,
|
||||||
shouldSuggestCustomEmoji,
|
shouldSuggestCustomEmoji,
|
||||||
customEmojiForEmoji,
|
customEmojiForEmoji,
|
||||||
|
attachmentSettings,
|
||||||
|
shouldSuggestCompression,
|
||||||
onCaptionUpdate,
|
onCaptionUpdate,
|
||||||
onSend,
|
onSend,
|
||||||
onFileAppend,
|
onFileAppend,
|
||||||
|
onDelete,
|
||||||
onClear,
|
onClear,
|
||||||
onSendSilent,
|
onSendSilent,
|
||||||
onSendScheduled,
|
onSendScheduled,
|
||||||
}) => {
|
}) => {
|
||||||
const { addRecentCustomEmoji, addRecentEmoji } = getActions();
|
const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings } = getActions();
|
||||||
|
const lang = useLang();
|
||||||
const captionRef = useStateRef(caption);
|
const captionRef = useStateRef(caption);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const mainButtonRef = useStateRef<HTMLButtonElement | null>(null);
|
const mainButtonRef = useStateRef<HTMLButtonElement | null>(null);
|
||||||
const hideTimeoutRef = useRef<number>();
|
const hideTimeoutRef = useRef<number>();
|
||||||
const prevAttachments = usePrevious(attachments);
|
const prevAttachments = usePrevious(attachments);
|
||||||
const renderingAttachments = attachments.length ? attachments : prevAttachments;
|
const renderingAttachments = attachments.length ? attachments : prevAttachments;
|
||||||
|
|
||||||
|
const [shouldSendCompressed, setShouldSendCompressed] = useState(
|
||||||
|
shouldSuggestCompression ?? attachmentSettings.shouldCompress,
|
||||||
|
);
|
||||||
|
const [shouldSendGrouped, setShouldSendGrouped] = useState(attachmentSettings.shouldSendGrouped);
|
||||||
|
|
||||||
const isOpen = Boolean(attachments.length);
|
const isOpen = Boolean(attachments.length);
|
||||||
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
||||||
const isQuick = Boolean(renderingAttachments && renderingAttachments.every((a) => a.quick));
|
|
||||||
const lang = useLang();
|
const [hasMedia, hasOnlyMedia] = useMemo(() => {
|
||||||
|
const onlyMedia = Boolean(renderingAttachments?.every((a) => a.quick || a.audio));
|
||||||
|
if (onlyMedia) return [true, true];
|
||||||
|
const oneMedia = Boolean(renderingAttachments?.some((a) => a.quick || a.audio));
|
||||||
|
return [oneMedia, false];
|
||||||
|
}, [renderingAttachments]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers,
|
isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers,
|
||||||
@ -142,6 +171,13 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);
|
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setShouldSendCompressed(shouldSuggestCompression ?? attachmentSettings.shouldCompress);
|
||||||
|
setShouldSendGrouped(attachmentSettings.shouldSendGrouped);
|
||||||
|
}
|
||||||
|
}, [attachmentSettings, isOpen, shouldSuggestCompression]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen: isCustomSendMenuOpen,
|
isContextMenuOpen: isCustomSendMenuOpen,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
@ -149,15 +185,32 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
handleContextMenuHide,
|
handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(mainButtonRef, !canShowCustomSendMenu || !isOpen);
|
} = useContextMenuHandlers(mainButtonRef, !canShowCustomSendMenu || !isOpen);
|
||||||
|
|
||||||
const sendAttachments = useCallback(() => {
|
const sendAttachments = useCallback((isSilent?: boolean, shouldSendScheduled?: boolean) => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
if (shouldSchedule) {
|
const send = (shouldSchedule || shouldSendScheduled) ? onSendScheduled
|
||||||
onSendScheduled();
|
: isSilent ? onSendSilent : onSend;
|
||||||
} else {
|
send(shouldSendCompressed, shouldSendGrouped);
|
||||||
onSend();
|
updateAttachmentSettings({
|
||||||
}
|
shouldCompress: shouldSendCompressed,
|
||||||
|
shouldSendGrouped,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [isOpen, onSendScheduled, onSend, shouldSchedule]);
|
}, [
|
||||||
|
isOpen, shouldSchedule, onSendScheduled, onSend, updateAttachmentSettings, shouldSendCompressed, shouldSendGrouped,
|
||||||
|
onSendSilent,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleSendSilent = useCallback(() => {
|
||||||
|
sendAttachments(true);
|
||||||
|
}, [sendAttachments]);
|
||||||
|
|
||||||
|
const handleSendClick = useCallback(() => {
|
||||||
|
sendAttachments();
|
||||||
|
}, [sendAttachments]);
|
||||||
|
|
||||||
|
const handleScheduleClick = useCallback(() => {
|
||||||
|
sendAttachments(false, true);
|
||||||
|
}, [sendAttachments]);
|
||||||
|
|
||||||
const handleDragLeave = (e: React.DragEvent<HTMLElement>) => {
|
const handleDragLeave = (e: React.DragEvent<HTMLElement>) => {
|
||||||
const { relatedTarget: toTarget, target: fromTarget } = e;
|
const { relatedTarget: toTarget, target: fromTarget } = e;
|
||||||
@ -187,11 +240,9 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const files = await getFilesFromDataTransferItems(dataTransfer.items);
|
const files = await getFilesFromDataTransferItems(dataTransfer.items);
|
||||||
if (files?.length) {
|
if (files?.length) {
|
||||||
const newFiles = Array.from(files).filter((file) => !isQuick || hasPreview(file));
|
onFileAppend(files);
|
||||||
|
|
||||||
onFileAppend(newFiles, isQuick);
|
|
||||||
}
|
}
|
||||||
}, [isQuick, onFileAppend, unmarkHovered]);
|
}, [onFileAppend, unmarkHovered]);
|
||||||
|
|
||||||
function handleDragOver(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
function handleDragOver(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -202,18 +253,55 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFileSelect = useCallback((e: Event) => {
|
||||||
|
const { files } = e.target as HTMLInputElement;
|
||||||
|
const validatedFiles = validateFiles(files);
|
||||||
|
|
||||||
|
if (validatedFiles?.length) {
|
||||||
|
onFileAppend(validatedFiles);
|
||||||
|
}
|
||||||
|
}, [onFileAppend]);
|
||||||
|
|
||||||
|
const handleDocumentSelect = useCallback(() => {
|
||||||
|
openSystemFilesDialog('*', (e) => handleFileSelect(e));
|
||||||
|
}, [handleFileSelect]);
|
||||||
|
|
||||||
|
const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
||||||
|
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||||
|
size="smaller"
|
||||||
|
color="translucent"
|
||||||
|
className={isMenuOpen ? 'active' : ''}
|
||||||
|
onClick={onTrigger}
|
||||||
|
ariaLabel="More actions"
|
||||||
|
>
|
||||||
|
<i className="icon-more" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const leftChars = useMemo(() => {
|
const leftChars = useMemo(() => {
|
||||||
const captionLeftBeforeLimit = captionLimit - getHtmlTextLength(caption);
|
const captionLeftBeforeLimit = captionLimit - getHtmlTextLength(caption);
|
||||||
return captionLeftBeforeLimit <= CAPTION_SYMBOLS_LEFT_THRESHOLD ? captionLeftBeforeLimit : undefined;
|
return captionLeftBeforeLimit <= CAPTION_SYMBOLS_LEFT_THRESHOLD ? captionLeftBeforeLimit : undefined;
|
||||||
}, [caption, captionLimit]);
|
}, [caption, captionLimit]);
|
||||||
|
|
||||||
|
const isQuickGallery = shouldSendCompressed && hasOnlyMedia;
|
||||||
|
|
||||||
|
const [areAllPhotos, areAllVideos, areAllAudios] = useMemo(() => {
|
||||||
|
if (!isQuickGallery || !renderingAttachments) return [false, false, false];
|
||||||
|
const everyPhoto = renderingAttachments.every((a) => SUPPORTED_IMAGE_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));
|
||||||
|
return [everyPhoto, everyVideo, everyAudio];
|
||||||
|
}, [renderingAttachments, isQuickGallery]);
|
||||||
|
|
||||||
if (!renderingAttachments) {
|
if (!renderingAttachments) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const areAllPhotos = renderingAttachments.every((a) => SUPPORTED_IMAGE_CONTENT_TYPES.has(a.mimeType));
|
const isMultiple = renderingAttachments.length > 1;
|
||||||
const areAllVideos = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType));
|
|
||||||
const areAllAudios = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType));
|
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
if (areAllPhotos) {
|
if (areAllPhotos) {
|
||||||
@ -237,29 +325,42 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
<i className="icon-close" />
|
<i className="icon-close" />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="modal-title">{title}</div>
|
<div className="modal-title">{title}</div>
|
||||||
<div className="AttachmentModal--send-wrapper">
|
<DropdownMenu
|
||||||
<Button
|
className="attachment-modal-more-menu"
|
||||||
ref={mainButtonRef}
|
trigger={MoreMenuButton}
|
||||||
color="primary"
|
positionX="right"
|
||||||
size="smaller"
|
>
|
||||||
className="modal-action-button"
|
<MenuItem icon="add" onClick={handleDocumentSelect}>{lang('Add')}</MenuItem>
|
||||||
onClick={sendAttachments}
|
{hasMedia && (
|
||||||
onContextMenu={canShowCustomSendMenu ? handleContextMenu : undefined}
|
shouldSendCompressed ? (
|
||||||
>
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
{lang('Send')}
|
<MenuItem icon="document" onClick={() => setShouldSendCompressed(false)}>
|
||||||
</Button>
|
{lang(isMultiple ? 'Attachment.SendAsFiles' : 'Attachment.SendAsFile')}
|
||||||
{canShowCustomSendMenu && (
|
</MenuItem>
|
||||||
<CustomSendMenu
|
) : (
|
||||||
isOpen={isCustomSendMenuOpen}
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
isOpenToBottom
|
<MenuItem icon="photo" onClick={() => setShouldSendCompressed(true)}>
|
||||||
onSendSilent={!isChatWithSelf ? onSendSilent : undefined}
|
{isMultiple ? 'Send All as Media' : 'Send as Media'}
|
||||||
onSendSchedule={onSendScheduled}
|
</MenuItem>
|
||||||
onClose={handleContextMenuClose}
|
)
|
||||||
onCloseAnimationEnd={handleContextMenuHide}
|
|
||||||
isSavedMessages={isChatWithSelf}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
{isMultiple && (
|
||||||
|
shouldSendGrouped ? (
|
||||||
|
<MenuItem
|
||||||
|
icon="grouped-disable"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => setShouldSendGrouped(false)}
|
||||||
|
>
|
||||||
|
Ungroup All Media
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
<MenuItem icon="grouped" onClick={() => setShouldSendGrouped(true)}>
|
||||||
|
Group All Media
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -270,6 +371,7 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
onClose={onClear}
|
onClose={onClear}
|
||||||
header={renderHeader()}
|
header={renderHeader()}
|
||||||
className={`AttachmentModal ${isHovered ? 'hovered' : ''}`}
|
className={`AttachmentModal ${isHovered ? 'hovered' : ''}`}
|
||||||
|
noBackdropClose
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="drop-target"
|
className="drop-target"
|
||||||
@ -277,31 +379,24 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
onDrop={handleFilesDrop}
|
onDrop={handleFilesDrop}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
|
onClick={unmarkHovered}
|
||||||
data-attach-description={lang('Preview.Dragging.AddItems', 10)}
|
data-attach-description={lang('Preview.Dragging.AddItems', 10)}
|
||||||
data-dropzone
|
data-dropzone
|
||||||
>
|
>
|
||||||
{isQuick ? (
|
<div className={buildClassName('attachments-wrapper', 'custom-scroll')}>
|
||||||
<div className="media-wrapper custom-scroll">
|
{renderingAttachments.map((attachment, i) => (
|
||||||
{renderingAttachments.map((attachment) => (
|
<AttachmentModalItem
|
||||||
attachment.mimeType.startsWith('image/')
|
attachment={attachment}
|
||||||
? <img src={attachment.blobUrl} alt="" />
|
className="attachment-modal-item"
|
||||||
: <video src={attachment.blobUrl} autoPlay muted loop disablePictureInPicture />
|
shouldDisplayCompressed={shouldSendCompressed}
|
||||||
))}
|
shouldDisplayGrouped={shouldSendGrouped}
|
||||||
</div>
|
isSingle={renderingAttachments.length === 1}
|
||||||
) : (
|
index={i}
|
||||||
<div className="document-wrapper custom-scroll">
|
key={attachment.uniqueId || i}
|
||||||
{renderingAttachments.map((attachment) => (
|
onDelete={onDelete}
|
||||||
<File
|
/>
|
||||||
name={attachment.filename}
|
))}
|
||||||
extension={getFileExtension(attachment.filename, attachment.mimeType)}
|
</div>
|
||||||
previewData={attachment.previewBlobUrl}
|
|
||||||
size={attachment.size}
|
|
||||||
smaller
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="attachment-caption-wrapper">
|
<div className="attachment-caption-wrapper">
|
||||||
<MentionTooltip
|
<MentionTooltip
|
||||||
isOpen={isMentionTooltipOpen}
|
isOpen={isMentionTooltipOpen}
|
||||||
@ -325,23 +420,73 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
onCustomEmojiSelect={insertCustomEmoji}
|
onCustomEmojiSelect={insertCustomEmoji}
|
||||||
addRecentCustomEmoji={addRecentCustomEmoji}
|
addRecentCustomEmoji={addRecentCustomEmoji}
|
||||||
/>
|
/>
|
||||||
<MessageInput
|
<div className="attachment-caption">
|
||||||
id="caption-input-text"
|
<MessageInput
|
||||||
chatId={chatId}
|
id="caption-input-text"
|
||||||
threadId={threadId}
|
chatId={chatId}
|
||||||
isAttachmentModalInput
|
threadId={threadId}
|
||||||
html={caption}
|
isAttachmentModalInput
|
||||||
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
html={caption}
|
||||||
placeholder={lang('Caption')}
|
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
||||||
onUpdate={onCaptionUpdate}
|
placeholder={lang('AddCaption')}
|
||||||
onSend={sendAttachments}
|
onUpdate={onCaptionUpdate}
|
||||||
canAutoFocus={Boolean(isReady && attachments.length)}
|
onSend={handleSendClick}
|
||||||
captionLimit={leftChars}
|
canAutoFocus={Boolean(isReady && attachments.length)}
|
||||||
/>
|
captionLimit={leftChars}
|
||||||
|
/>
|
||||||
|
<div className="AttachmentModal--send-wrapper">
|
||||||
|
<Button
|
||||||
|
ref={mainButtonRef}
|
||||||
|
className="AttachmentModal--send"
|
||||||
|
onClick={handleSendClick}
|
||||||
|
onContextMenu={canShowCustomSendMenu ? handleContextMenu : undefined}
|
||||||
|
>
|
||||||
|
{lang('Send')}
|
||||||
|
</Button>
|
||||||
|
{canShowCustomSendMenu && (
|
||||||
|
<CustomSendMenu
|
||||||
|
isOpen={isCustomSendMenuOpen}
|
||||||
|
onSendSilent={!isChatWithSelf ? handleSendSilent : undefined}
|
||||||
|
onSendSchedule={handleScheduleClick}
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
isSavedMessages={isChatWithSelf}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(AttachmentModal);
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { chatId }): StateProps => {
|
||||||
|
const {
|
||||||
|
currentUserId,
|
||||||
|
recentEmojis,
|
||||||
|
customEmojis,
|
||||||
|
attachmentSettings,
|
||||||
|
} = global;
|
||||||
|
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
|
const { language, shouldSuggestCustomEmoji } = global.settings.byKey;
|
||||||
|
const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG];
|
||||||
|
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isChatWithSelf,
|
||||||
|
currentUserId,
|
||||||
|
groupChatMembers: chat?.fullInfo?.members,
|
||||||
|
recentEmojis,
|
||||||
|
baseEmojiKeywords: baseEmojiKeywords?.keywords,
|
||||||
|
emojiKeywords: emojiKeywords?.keywords,
|
||||||
|
shouldSuggestCustomEmoji,
|
||||||
|
customEmojiForEmoji: customEmojis.forEmoji.stickers,
|
||||||
|
captionLimit: selectCurrentLimit(global, 'captionLength'),
|
||||||
|
attachmentSettings,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(AttachmentModal));
|
||||||
|
|||||||
@ -0,0 +1,79 @@
|
|||||||
|
.root {
|
||||||
|
flex: 1 calc(50% - 0.5rem);
|
||||||
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 12rem;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--border-radius-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration {
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
position: absolute;
|
||||||
|
left: 0.1875rem;
|
||||||
|
top: 0.1875rem;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0 0.375rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
line-height: 1.125rem;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single .preview {
|
||||||
|
height: auto;
|
||||||
|
max-height: 25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-grouping {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file {
|
||||||
|
margin: 0.5rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
border-radius: var(--border-radius-default);
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item {
|
||||||
|
display: block;
|
||||||
|
padding: 0.3125rem;
|
||||||
|
color: white;
|
||||||
|
border-radius: var(--border-radius-messages-small);
|
||||||
|
|
||||||
|
transition: 0.2s background-color ease-in-out;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-file {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
118
src/components/middle/composer/AttachmentModalItem.tsx
Normal file
118
src/components/middle/composer/AttachmentModalItem.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import type { ApiAttachment } from '../../../api/types';
|
||||||
|
|
||||||
|
import { SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES } from '../../../config';
|
||||||
|
import { getFileExtension } from '../../common/helpers/documentInfo';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { formatMediaDuration } from '../../../util/dateFormat';
|
||||||
|
|
||||||
|
import File from '../../common/File';
|
||||||
|
|
||||||
|
import styles from './AttachmentModalItem.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
attachment: ApiAttachment;
|
||||||
|
className?: string;
|
||||||
|
shouldDisplayCompressed?: boolean;
|
||||||
|
shouldDisplayGrouped?: boolean;
|
||||||
|
isSingle?: boolean;
|
||||||
|
index: number;
|
||||||
|
onDelete?: (index: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AttachmentModalItem: FC<OwnProps> = ({
|
||||||
|
attachment,
|
||||||
|
className,
|
||||||
|
isSingle,
|
||||||
|
shouldDisplayCompressed,
|
||||||
|
shouldDisplayGrouped,
|
||||||
|
index,
|
||||||
|
onDelete,
|
||||||
|
}) => {
|
||||||
|
const displayType = getDisplayType(attachment, shouldDisplayCompressed);
|
||||||
|
|
||||||
|
const content = useMemo(() => {
|
||||||
|
switch (displayType) {
|
||||||
|
case 'image':
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className={styles.preview}
|
||||||
|
src={attachment.blobUrl}
|
||||||
|
alt=""
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'video':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Boolean(attachment.quick?.duration) && (
|
||||||
|
<div className={styles.duration}>{formatMediaDuration(attachment.quick!.duration)}</div>
|
||||||
|
)}
|
||||||
|
<video
|
||||||
|
className={styles.preview}
|
||||||
|
src={attachment.blobUrl}
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
disablePictureInPicture
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<File
|
||||||
|
className={styles.file}
|
||||||
|
name={attachment.filename}
|
||||||
|
extension={getFileExtension(attachment.filename, attachment.mimeType)}
|
||||||
|
previewData={attachment.previewBlobUrl}
|
||||||
|
size={attachment.size}
|
||||||
|
smaller
|
||||||
|
/>
|
||||||
|
{onDelete && (
|
||||||
|
<i
|
||||||
|
className={buildClassName('icon-delete', styles.actionItem, styles.deleteFile)}
|
||||||
|
onClick={() => onDelete(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [attachment, displayType, index, onDelete]);
|
||||||
|
|
||||||
|
const shouldSkipGrouping = displayType === 'file' || !shouldDisplayGrouped;
|
||||||
|
const shouldRenderOverlay = displayType !== 'file' && onDelete;
|
||||||
|
|
||||||
|
const rootClassName = buildClassName(
|
||||||
|
className, styles.root, isSingle && styles.single, shouldSkipGrouping && styles.noGrouping,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={rootClassName}>
|
||||||
|
{shouldRenderOverlay && (
|
||||||
|
<div className={styles.overlay}>
|
||||||
|
{onDelete && (
|
||||||
|
<i className={buildClassName('icon-delete', styles.actionItem)} onClick={() => onDelete(index)} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDisplayType(attachment: ApiAttachment, shouldDisplayCompressed?: boolean) {
|
||||||
|
if (shouldDisplayCompressed && attachment.quick) {
|
||||||
|
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(attachment.mimeType)) {
|
||||||
|
return 'image';
|
||||||
|
}
|
||||||
|
if (SUPPORTED_VIDEO_CONTENT_TYPES.has(attachment.mimeType)) {
|
||||||
|
return 'video';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(AttachmentModalItem);
|
||||||
@ -456,6 +456,7 @@
|
|||||||
#message-input-text,
|
#message-input-text,
|
||||||
#caption-input-text {
|
#caption-input-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
padding: calc((3.25rem - var(--composer-text-size, 1rem) * 1.375) / 2 - var(--border-width, 0) * 2)
|
padding: calc((3.25rem - var(--composer-text-size, 1rem) * 1.375) / 2 - var(--border-width, 0) * 2)
|
||||||
@ -558,8 +559,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#message-input-text {
|
#message-input-text {
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
line-height: 1.3125;
|
line-height: 1.3125;
|
||||||
@ -601,21 +600,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#caption-input-text {
|
#caption-input-text {
|
||||||
border: 1px solid var(--color-borders-input);
|
|
||||||
border-radius: var(--border-radius-default);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
|
|
||||||
.input-scroller {
|
.input-scroller {
|
||||||
min-height: 3.25rem;
|
min-height: 3rem;
|
||||||
max-height: 15rem;
|
max-height: 15rem;
|
||||||
|
|
||||||
margin-right: 0.5rem;
|
margin-right: -5.5625rem;
|
||||||
|
|
||||||
&:has(.form-control:focus) {
|
&:has(.form-control:focus) {
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-scroller-content {
|
||||||
|
margin-right: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.placeholder-text {
|
.placeholder-text {
|
||||||
top: auto;
|
top: auto;
|
||||||
bottom: 0.875rem;
|
bottom: 0.875rem;
|
||||||
|
|||||||
@ -64,7 +64,7 @@ import {
|
|||||||
import { formatMediaDuration, formatVoiceRecordDuration } from '../../../util/dateFormat';
|
import { formatMediaDuration, formatVoiceRecordDuration } from '../../../util/dateFormat';
|
||||||
import focusEditableElement from '../../../util/focusEditableElement';
|
import focusEditableElement from '../../../util/focusEditableElement';
|
||||||
import parseMessageInput from '../../../util/parseMessageInput';
|
import parseMessageInput from '../../../util/parseMessageInput';
|
||||||
import buildAttachment from './helpers/buildAttachment';
|
import buildAttachment, { prepareAttachmentsToSend } from './helpers/buildAttachment';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
import { insertHtmlInSelection } from '../../../util/selection';
|
import { insertHtmlInSelection } from '../../../util/selection';
|
||||||
import deleteLastCharacterOutsideSelection from '../../../util/deleteLastCharacterOutsideSelection';
|
import deleteLastCharacterOutsideSelection from '../../../util/deleteLastCharacterOutsideSelection';
|
||||||
@ -73,7 +73,6 @@ import windowSize from '../../../util/windowSize';
|
|||||||
import { isSelectionInsideInput } from './helpers/selection';
|
import { isSelectionInsideInput } from './helpers/selection';
|
||||||
import applyIosAutoCapitalizationFix from './helpers/applyIosAutoCapitalizationFix';
|
import applyIosAutoCapitalizationFix from './helpers/applyIosAutoCapitalizationFix';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { hasPreview } from '../../../util/files';
|
|
||||||
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||||
import { buildCustomEmojiHtml } from './helpers/customEmoji';
|
import { buildCustomEmojiHtml } from './helpers/customEmoji';
|
||||||
import { processMessageInputForCustomEmoji } from '../../../util/customEmojiManager';
|
import { processMessageInputForCustomEmoji } from '../../../util/customEmojiManager';
|
||||||
@ -98,6 +97,7 @@ import useInlineBotTooltip from './hooks/useInlineBotTooltip';
|
|||||||
import useBotCommandTooltip from './hooks/useBotCommandTooltip';
|
import useBotCommandTooltip from './hooks/useBotCommandTooltip';
|
||||||
import useSchedule from '../../../hooks/useSchedule';
|
import useSchedule from '../../../hooks/useSchedule';
|
||||||
import useCustomEmojiTooltip from './hooks/useCustomEmojiTooltip';
|
import useCustomEmojiTooltip from './hooks/useCustomEmojiTooltip';
|
||||||
|
import useAttachmentModal from './hooks/useAttachmentModal';
|
||||||
|
|
||||||
import DeleteMessageModal from '../../common/DeleteMessageModal.async';
|
import DeleteMessageModal from '../../common/DeleteMessageModal.async';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -279,7 +279,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
loadSendAs,
|
loadSendAs,
|
||||||
resetOpenChatWithDraft,
|
resetOpenChatWithDraft,
|
||||||
callAttachBot,
|
callAttachBot,
|
||||||
openLimitReachedModal,
|
|
||||||
openPremiumModal,
|
openPremiumModal,
|
||||||
addRecentCustomEmoji,
|
addRecentCustomEmoji,
|
||||||
showNotification,
|
showNotification,
|
||||||
@ -344,6 +343,21 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
shouldSuggestCompression,
|
||||||
|
handleAppendFiles,
|
||||||
|
handleFileSelect,
|
||||||
|
onCaptionUpdate,
|
||||||
|
handleClearAttachments,
|
||||||
|
handleDeleteAttachment,
|
||||||
|
handleSetAttachments,
|
||||||
|
} = useAttachmentModal({
|
||||||
|
attachments,
|
||||||
|
setHtml,
|
||||||
|
setAttachments,
|
||||||
|
fileSizeLimit,
|
||||||
|
});
|
||||||
|
|
||||||
const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag();
|
const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag();
|
||||||
const [isBotCommandMenuOpen, openBotCommandMenu, closeBotCommandMenu] = useFlag();
|
const [isBotCommandMenuOpen, openBotCommandMenu, closeBotCommandMenu] = useFlag();
|
||||||
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
|
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
|
||||||
@ -352,19 +366,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
const [isSymbolMenuLoaded, onSymbolMenuLoadingComplete] = useFlag();
|
const [isSymbolMenuLoaded, onSymbolMenuLoadingComplete] = useFlag();
|
||||||
const [isHoverDisabled, disableHover, enableHover] = useFlag();
|
const [isHoverDisabled, disableHover, enableHover] = useFlag();
|
||||||
|
|
||||||
const handleSetAttachments = useCallback(
|
|
||||||
(newValue: ApiAttachment[] | ((current: ApiAttachment[]) => ApiAttachment[])) => {
|
|
||||||
const newAttachments = typeof newValue === 'function' ? newValue(attachments) : newValue;
|
|
||||||
if (newAttachments.some(({ size }) => size > fileSizeLimit)) {
|
|
||||||
openLimitReachedModal({
|
|
||||||
limit: 'uploadMaxFileparts',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setAttachments(newAttachments);
|
|
||||||
}
|
|
||||||
}, [attachments, fileSizeLimit, openLimitReachedModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
startRecordingVoice,
|
startRecordingVoice,
|
||||||
stopRecordingVoice,
|
stopRecordingVoice,
|
||||||
@ -612,20 +613,100 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [editingMessage, handleEditCancel]);
|
}, [editingMessage, handleEditCancel]);
|
||||||
|
|
||||||
const handleFileSelect = useCallback(async (files: File[], isQuick: boolean) => {
|
const validateTextLength = useCallback((text: string, isAttachmentModal?: boolean) => {
|
||||||
handleSetAttachments(await Promise.all(files.map((file) => buildAttachment(file.name, file, isQuick))));
|
const maxLength = isAttachmentModal ? captionLimit : MESSAGE_MAX_LENGTH;
|
||||||
}, [handleSetAttachments]);
|
if (text?.length > maxLength) {
|
||||||
|
const extraLength = text.length - maxLength;
|
||||||
|
showDialog({
|
||||||
|
data: {
|
||||||
|
message: 'MESSAGE_TOO_LONG_PLEASE_REMOVE_CHARACTERS',
|
||||||
|
textParams: {
|
||||||
|
'{EXTRA_CHARS_COUNT}': extraLength,
|
||||||
|
'{PLURAL_S}': extraLength > 1 ? 's' : '',
|
||||||
|
},
|
||||||
|
hasErrorKey: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleAppendFiles = useCallback(async (files: File[], isQuick: boolean) => {
|
return false;
|
||||||
handleSetAttachments([
|
}
|
||||||
...attachments,
|
return true;
|
||||||
...await Promise.all(files.map((file) => buildAttachment(file.name, file, isQuick))),
|
}, [captionLimit, showDialog]);
|
||||||
]);
|
|
||||||
}, [attachments, handleSetAttachments]);
|
|
||||||
|
|
||||||
const handleClearAttachment = useCallback(() => {
|
const checkSlowMode = useCallback(() => {
|
||||||
setAttachments(MEMO_EMPTY_ARRAY);
|
if (slowMode && !isAdmin) {
|
||||||
}, []);
|
// No need to subscribe on updates in `mapStateToProps`
|
||||||
|
const { serverTimeOffset } = getGlobal();
|
||||||
|
|
||||||
|
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||||
|
|
||||||
|
const nowSeconds = getServerTime(serverTimeOffset);
|
||||||
|
const secondsSinceLastMessage = lastMessageSendTimeSeconds.current
|
||||||
|
&& Math.floor(nowSeconds - lastMessageSendTimeSeconds.current);
|
||||||
|
const nextSendDateNotReached = slowMode.nextSendDate && slowMode.nextSendDate > nowSeconds;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(secondsSinceLastMessage && secondsSinceLastMessage < slowMode.seconds)
|
||||||
|
|| nextSendDateNotReached
|
||||||
|
) {
|
||||||
|
const secondsRemaining = nextSendDateNotReached
|
||||||
|
? slowMode.nextSendDate! - nowSeconds
|
||||||
|
: slowMode.seconds - secondsSinceLastMessage!;
|
||||||
|
showDialog({
|
||||||
|
data: {
|
||||||
|
message: lang('SlowModeHint', formatMediaDuration(secondsRemaining)),
|
||||||
|
isSlowMode: true,
|
||||||
|
hasErrorKey: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
messageInput?.blur();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [isAdmin, lang, showDialog, slowMode]);
|
||||||
|
|
||||||
|
const handleSendAttachments = useCallback((
|
||||||
|
sendCompressed: boolean, sendGrouped: boolean, isSilent?: boolean, scheduledAt?: number,
|
||||||
|
) => {
|
||||||
|
if (connectionState !== 'connectionStateReady') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { text, entities } = parseMessageInput(htmlRef.current!);
|
||||||
|
if (!text && !attachments.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!validateTextLength(text, true)) return;
|
||||||
|
if (!checkSlowMode()) return;
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
text,
|
||||||
|
entities,
|
||||||
|
scheduledAt,
|
||||||
|
isSilent,
|
||||||
|
shouldUpdateStickerSetsOrder: true,
|
||||||
|
attachments: prepareAttachmentsToSend(attachments, sendCompressed),
|
||||||
|
shouldGroupMessages: sendGrouped,
|
||||||
|
});
|
||||||
|
|
||||||
|
// No need to subscribe on updates in `mapStateToProps`
|
||||||
|
const { serverTimeOffset } = getGlobal();
|
||||||
|
|
||||||
|
lastMessageSendTimeSeconds.current = getServerTime(serverTimeOffset);
|
||||||
|
|
||||||
|
clearDraft({ chatId, localOnly: true });
|
||||||
|
|
||||||
|
// Wait until message animation starts
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
resetComposer();
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
attachments, chatId, checkSlowMode, clearDraft, htmlRef, resetComposer, sendMessage, validateTextLength,
|
||||||
|
connectionState,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleSend = useCallback(async (isSilent = false, scheduledAt?: number) => {
|
const handleSend = useCallback(async (isSilent = false, scheduledAt?: number) => {
|
||||||
if (connectionState !== 'connectionStateReady') {
|
if (connectionState !== 'connectionStateReady') {
|
||||||
@ -641,7 +722,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
currentAttachments = [await buildAttachment(
|
currentAttachments = [await buildAttachment(
|
||||||
VOICE_RECORDING_FILENAME,
|
VOICE_RECORDING_FILENAME,
|
||||||
blob,
|
blob,
|
||||||
false,
|
|
||||||
{ voice: { duration, waveform } },
|
{ voice: { duration, waveform } },
|
||||||
)];
|
)];
|
||||||
}
|
}
|
||||||
@ -649,64 +729,28 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const { text, entities } = parseMessageInput(htmlRef.current!);
|
const { text, entities } = parseMessageInput(htmlRef.current!);
|
||||||
|
|
||||||
if (!currentAttachments.length && !text && !isForwarding) {
|
if (currentAttachments.length) {
|
||||||
|
handleSendAttachments(false, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text && !isForwarding) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to subscribe on updates in `mapStateToProps`
|
// No need to subscribe on updates in `mapStateToProps`
|
||||||
const { serverTimeOffset } = getGlobal();
|
const { serverTimeOffset } = getGlobal();
|
||||||
|
|
||||||
const maxLength = currentAttachments.length ? captionLimit : MESSAGE_MAX_LENGTH;
|
if (!validateTextLength(text)) return;
|
||||||
if (text?.length > maxLength) {
|
|
||||||
const extraLength = text.length - maxLength;
|
|
||||||
showDialog({
|
|
||||||
data: {
|
|
||||||
message: 'MESSAGE_TOO_LONG_PLEASE_REMOVE_CHARACTERS',
|
|
||||||
textParams: {
|
|
||||||
'{EXTRA_CHARS_COUNT}': extraLength,
|
|
||||||
'{PLURAL_S}': extraLength > 1 ? 's' : '',
|
|
||||||
},
|
|
||||||
hasErrorKey: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||||
|
|
||||||
if (currentAttachments.length || text) {
|
if (text) {
|
||||||
if (slowMode && !isAdmin) {
|
if (!checkSlowMode()) return;
|
||||||
const nowSeconds = getServerTime(serverTimeOffset);
|
|
||||||
const secondsSinceLastMessage = lastMessageSendTimeSeconds.current
|
|
||||||
&& Math.floor(nowSeconds - lastMessageSendTimeSeconds.current);
|
|
||||||
const nextSendDateNotReached = slowMode.nextSendDate && slowMode.nextSendDate > nowSeconds;
|
|
||||||
|
|
||||||
if (
|
|
||||||
(secondsSinceLastMessage && secondsSinceLastMessage < slowMode.seconds)
|
|
||||||
|| nextSendDateNotReached
|
|
||||||
) {
|
|
||||||
const secondsRemaining = nextSendDateNotReached
|
|
||||||
? slowMode.nextSendDate! - nowSeconds
|
|
||||||
: slowMode.seconds - secondsSinceLastMessage!;
|
|
||||||
showDialog({
|
|
||||||
data: {
|
|
||||||
message: lang('SlowModeHint', formatMediaDuration(secondsRemaining)),
|
|
||||||
isSlowMode: true,
|
|
||||||
hasErrorKey: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
messageInput?.blur();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessage({
|
sendMessage({
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
attachments: currentAttachments,
|
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
isSilent,
|
isSilent,
|
||||||
shouldUpdateStickerSetsOrder: true,
|
shouldUpdateStickerSetsOrder: true,
|
||||||
@ -733,8 +777,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
resetComposer();
|
resetComposer();
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
connectionState, attachments, activeVoiceRecording, isForwarding, clearDraft, chatId, captionLimit,
|
connectionState, attachments, activeVoiceRecording, htmlRef, isForwarding, validateTextLength, clearDraft,
|
||||||
resetComposer, stopRecordingVoice, showDialog, slowMode, isAdmin, sendMessage, forwardMessages, lang, htmlRef,
|
chatId, stopRecordingVoice, handleSendAttachments, checkSlowMode, sendMessage, forwardMessages, resetComposer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleClickBotMenu = useCallback(() => {
|
const handleClickBotMenu = useCallback(() => {
|
||||||
@ -778,13 +822,16 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
if (!args || Object.keys(restArgs).length === 0) {
|
if (!args || Object.keys(restArgs).length === 0) {
|
||||||
void handleSend(Boolean(isSilent), scheduledAt);
|
void handleSend(Boolean(isSilent), scheduledAt);
|
||||||
|
} else if (args.sendCompressed !== undefined || args.sendGrouped !== undefined) {
|
||||||
|
const { sendCompressed = false, sendGrouped = false } = args;
|
||||||
|
void handleSendAttachments(sendCompressed, sendGrouped, isSilent, scheduledAt);
|
||||||
} else {
|
} else {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
...args,
|
...args,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [handleSend, sendInlineBotResult, sendMessage]);
|
}, [handleSendAttachments, handleSend, sendInlineBotResult, sendMessage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contentToBeScheduled) {
|
if (contentToBeScheduled) {
|
||||||
@ -807,8 +854,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (requestedDraftFiles?.length) {
|
if (requestedDraftFiles?.length) {
|
||||||
const isQuick = requestedDraftFiles.every((file) => hasPreview(file));
|
handleFileSelect(requestedDraftFiles);
|
||||||
handleFileSelect(requestedDraftFiles, isQuick);
|
|
||||||
resetOpenChatWithDraft();
|
resetOpenChatWithDraft();
|
||||||
}
|
}
|
||||||
}, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]);
|
}, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]);
|
||||||
@ -931,15 +977,18 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [closePollModal, handleMessageSchedule, requestCalendar, sendMessage, shouldSchedule]);
|
}, [closePollModal, handleMessageSchedule, requestCalendar, sendMessage, shouldSchedule]);
|
||||||
|
|
||||||
const handleSendSilent = useCallback(() => {
|
const sendSilent = useCallback((additionalArgs?: ScheduledMessageArgs) => {
|
||||||
if (shouldSchedule) {
|
if (shouldSchedule) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
handleMessageSchedule({ isSilent: true }, scheduledAt);
|
handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt);
|
||||||
});
|
});
|
||||||
|
} else if (additionalArgs && ('sendCompressed' in additionalArgs || 'sendGrouped' in additionalArgs)) {
|
||||||
|
const { sendCompressed = false, sendGrouped = false } = additionalArgs;
|
||||||
|
void handleSendAttachments(sendCompressed, sendGrouped, true);
|
||||||
} else {
|
} else {
|
||||||
void handleSend(true);
|
void handleSend(true);
|
||||||
}
|
}
|
||||||
}, [handleMessageSchedule, handleSend, requestCalendar, shouldSchedule]);
|
}, [handleMessageSchedule, handleSend, handleSendAttachments, requestCalendar, shouldSchedule]);
|
||||||
|
|
||||||
const handleSearchOpen = useCallback((type: 'stickers' | 'gifs') => {
|
const handleSearchOpen = useCallback((type: 'stickers' | 'gifs') => {
|
||||||
if (type === 'stickers') {
|
if (type === 'stickers') {
|
||||||
@ -1087,12 +1136,28 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
}, [handleMessageSchedule, requestCalendar]);
|
}, [handleMessageSchedule, requestCalendar]);
|
||||||
|
|
||||||
|
const handleSendSilent = useCallback(() => {
|
||||||
|
sendSilent();
|
||||||
|
}, [sendSilent]);
|
||||||
|
|
||||||
|
const handleSendScheduledAttachments = useCallback((sendCompressed: boolean, sendGrouped: boolean) => {
|
||||||
|
requestCalendar((scheduledAt) => {
|
||||||
|
handleMessageSchedule({ sendCompressed, sendGrouped }, scheduledAt);
|
||||||
|
});
|
||||||
|
}, [handleMessageSchedule, requestCalendar]);
|
||||||
|
|
||||||
|
const handleSendSilentAttachments = useCallback((sendCompressed: boolean, sendGrouped: boolean) => {
|
||||||
|
sendSilent({ sendCompressed, sendGrouped });
|
||||||
|
}, [sendSilent]);
|
||||||
|
|
||||||
const onSend = mainButtonState === MainButtonState.Edit
|
const onSend = mainButtonState === MainButtonState.Edit
|
||||||
? handleEditComplete
|
? handleEditComplete
|
||||||
: mainButtonState === MainButtonState.Schedule ? handleSendScheduled
|
: mainButtonState === MainButtonState.Schedule ? handleSendScheduled
|
||||||
: handleSend;
|
: handleSend;
|
||||||
|
|
||||||
const isBotMenuButtonCommands = botMenuButton && botMenuButton?.type === 'commands';
|
const isBotMenuButtonCommands = botMenuButton && botMenuButton?.type === 'commands';
|
||||||
|
const shouldDisplayBotCommands = isChatWithBot && isBotMenuButtonCommands && botCommands !== false
|
||||||
|
&& !activeVoiceRecording && !editingMessage;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
@ -1109,24 +1174,16 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
canShowCustomSendMenu={canShowCustomSendMenu}
|
canShowCustomSendMenu={canShowCustomSendMenu}
|
||||||
attachments={attachments}
|
attachments={attachments}
|
||||||
captionLimit={captionLimit}
|
|
||||||
caption={attachments.length ? html : ''}
|
caption={attachments.length ? html : ''}
|
||||||
groupChatMembers={groupChatMembers}
|
|
||||||
currentUserId={currentUserId}
|
|
||||||
recentEmojis={recentEmojis}
|
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
isChatWithSelf={isChatWithSelf}
|
shouldSuggestCompression={shouldSuggestCompression}
|
||||||
onCaptionUpdate={setHtml}
|
onCaptionUpdate={onCaptionUpdate}
|
||||||
baseEmojiKeywords={baseEmojiKeywords}
|
onSendSilent={handleSendSilentAttachments}
|
||||||
emojiKeywords={emojiKeywords}
|
onSend={handleSendAttachments}
|
||||||
shouldSchedule={shouldSchedule}
|
onSendScheduled={handleSendScheduledAttachments}
|
||||||
onSendSilent={handleSendSilent}
|
|
||||||
onSend={handleSend}
|
|
||||||
onSendScheduled={handleSendScheduled}
|
|
||||||
onFileAppend={handleAppendFiles}
|
onFileAppend={handleAppendFiles}
|
||||||
onClear={handleClearAttachment}
|
onClear={handleClearAttachments}
|
||||||
shouldSuggestCustomEmoji={shouldSuggestCustomEmoji}
|
onDelete={handleDeleteAttachment}
|
||||||
customEmojiForEmoji={customEmojiForEmoji}
|
|
||||||
/>
|
/>
|
||||||
<PollModal
|
<PollModal
|
||||||
isOpen={pollModal.isOpen}
|
isOpen={pollModal.isOpen}
|
||||||
@ -1196,8 +1253,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isDisabled={Boolean(activeVoiceRecording)}
|
isDisabled={Boolean(activeVoiceRecording)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(isChatWithBot && isBotMenuButtonCommands
|
{shouldDisplayBotCommands && (
|
||||||
&& botCommands !== false && !activeVoiceRecording && !editingMessage) && (
|
|
||||||
<ResponsiveHoverButton
|
<ResponsiveHoverButton
|
||||||
className={buildClassName('bot-commands', isBotCommandMenuOpen && 'activated')}
|
className={buildClassName('bot-commands', isBotCommandMenuOpen && 'activated')}
|
||||||
round
|
round
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export type OwnProps = {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
withQuick?: boolean;
|
withQuick?: boolean;
|
||||||
onHide: NoneToVoidFunction;
|
onHide: NoneToVoidFunction;
|
||||||
onFileSelect: (files: File[], isQuick: boolean) => void;
|
onFileSelect: (files: File[], suggestCompression?: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum DropAreaState {
|
export enum DropAreaState {
|
||||||
@ -48,14 +48,14 @@ const DropArea: FC<OwnProps> = ({
|
|||||||
files = files.concat(Array.from(dt.files));
|
files = files.concat(Array.from(dt.files));
|
||||||
} else if (dt.items && dt.items.length > 0) {
|
} else if (dt.items && dt.items.length > 0) {
|
||||||
const folderFiles = await getFilesFromDataTransferItems(dt.items);
|
const folderFiles = await getFilesFromDataTransferItems(dt.items);
|
||||||
if (folderFiles.length) {
|
if (folderFiles?.length) {
|
||||||
files = files.concat(folderFiles);
|
files = files.concat(folderFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onHide();
|
onHide();
|
||||||
onFileSelect(files, false);
|
onFileSelect(files, withQuick ? false : undefined);
|
||||||
}, [onFileSelect, onHide]);
|
}, [onFileSelect, onHide, withQuick]);
|
||||||
|
|
||||||
const handleQuickFilesDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleQuickFilesDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
const { dataTransfer: dt } = e;
|
const { dataTransfer: dt } = e;
|
||||||
@ -89,6 +89,8 @@ const DropArea: FC<OwnProps> = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldRenderQuick = withQuick || prevWithQuick;
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
'DropArea',
|
'DropArea',
|
||||||
transitionClassNames,
|
transitionClassNames,
|
||||||
@ -103,8 +105,8 @@ const DropArea: FC<OwnProps> = ({
|
|||||||
onDrop={onHide}
|
onDrop={onHide}
|
||||||
onClick={onHide}
|
onClick={onHide}
|
||||||
>
|
>
|
||||||
<DropTarget onFileSelect={handleFilesDrop} />
|
<DropTarget onFileSelect={handleFilesDrop} isGeneric={!shouldRenderQuick} />
|
||||||
{(withQuick || prevWithQuick) && <DropTarget onFileSelect={handleQuickFilesDrop} isQuick />}
|
{shouldRenderQuick && <DropTarget onFileSelect={handleQuickFilesDrop} isQuick />}
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,10 +8,11 @@ import './DropTarget.scss';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isQuick?: boolean;
|
isQuick?: boolean;
|
||||||
|
isGeneric?: boolean;
|
||||||
onFileSelect: (e: React.DragEvent<HTMLDivElement>) => void;
|
onFileSelect: (e: React.DragEvent<HTMLDivElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DropTarget: FC<OwnProps> = ({ isQuick, onFileSelect }) => {
|
const DropTarget: FC<OwnProps> = ({ isQuick, isGeneric, onFileSelect }) => {
|
||||||
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
||||||
|
|
||||||
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
@ -40,7 +41,7 @@ const DropTarget: FC<OwnProps> = ({ isQuick, onFileSelect }) => {
|
|||||||
<div className="target-content">
|
<div className="target-content">
|
||||||
<div className={`icon icon-${isQuick ? 'photo' : 'document'}`} />
|
<div className={`icon icon-${isQuick ? 'photo' : 'document'}`} />
|
||||||
<div className="title">Drop files here to send them</div>
|
<div className="title">Drop files here to send them</div>
|
||||||
<div className="description">{isQuick ? 'in a quick way' : 'without compression'}</div>
|
{!isGeneric && <div className="description">{isQuick ? 'in a quick way' : 'without compression'}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const MAX_QUICK_IMG_SIZE = 1280; // px
|
|||||||
const FILE_EXT_REGEX = /\.[^/.]+$/;
|
const FILE_EXT_REGEX = /\.[^/.]+$/;
|
||||||
|
|
||||||
export default async function buildAttachment(
|
export default async function buildAttachment(
|
||||||
filename: string, blob: Blob, isQuick: boolean, options?: Partial<ApiAttachment>,
|
filename: string, blob: Blob, options?: Partial<ApiAttachment>,
|
||||||
): Promise<ApiAttachment> {
|
): Promise<ApiAttachment> {
|
||||||
const blobUrl = URL.createObjectURL(blob);
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
const { type: mimeType, size } = blob;
|
const { type: mimeType, size } = blob;
|
||||||
@ -26,28 +26,25 @@ export default async function buildAttachment(
|
|||||||
let previewBlobUrl;
|
let previewBlobUrl;
|
||||||
|
|
||||||
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
|
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
|
||||||
if (isQuick) {
|
const img = await preloadImage(blobUrl);
|
||||||
const img = await preloadImage(blobUrl);
|
const { width, height } = img;
|
||||||
const { width, height } = img;
|
const shouldShrink = Math.max(width, height) > MAX_QUICK_IMG_SIZE;
|
||||||
const shouldShrink = width > MAX_QUICK_IMG_SIZE || height > MAX_QUICK_IMG_SIZE;
|
|
||||||
|
|
||||||
if (shouldShrink || mimeType !== 'image/jpeg') {
|
if (shouldShrink || mimeType !== 'image/jpeg') {
|
||||||
const resizedUrl = await scaleImage(
|
const resizedUrl = await scaleImage(
|
||||||
blobUrl, shouldShrink ? MAX_QUICK_IMG_SIZE / Math.max(width, height) : 1, 'image/jpeg',
|
blobUrl, shouldShrink ? MAX_QUICK_IMG_SIZE / Math.max(width, height) : 1, 'image/jpeg',
|
||||||
);
|
);
|
||||||
URL.revokeObjectURL(blobUrl);
|
URL.revokeObjectURL(blobUrl);
|
||||||
const newBlob = await fetchBlob(resizedUrl);
|
const newBlob = await fetchBlob(resizedUrl);
|
||||||
return buildAttachment(filename, newBlob, true, options);
|
return buildAttachment(filename, newBlob, options);
|
||||||
}
|
|
||||||
|
|
||||||
if (mimeType === 'image/jpeg') {
|
|
||||||
filename = filename.replace(FILE_EXT_REGEX, '.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
quick = { width, height };
|
|
||||||
} else {
|
|
||||||
previewBlobUrl = blobUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mimeType === 'image/jpeg') {
|
||||||
|
filename = filename.replace(FILE_EXT_REGEX, '.jpg');
|
||||||
|
}
|
||||||
|
|
||||||
|
quick = { width, height };
|
||||||
|
previewBlobUrl = blobUrl;
|
||||||
} else if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
} else if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
||||||
const { videoWidth: width, videoHeight: height, duration } = await preloadVideo(blobUrl);
|
const { videoWidth: width, videoHeight: height, duration } = await preloadVideo(blobUrl);
|
||||||
quick = { width, height, duration };
|
quick = { width, height, duration };
|
||||||
@ -73,6 +70,13 @@ export default async function buildAttachment(
|
|||||||
quick,
|
quick,
|
||||||
audio,
|
audio,
|
||||||
previewBlobUrl,
|
previewBlobUrl,
|
||||||
|
uniqueId: `${Date.now()}-${Math.random()}`,
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prepareAttachmentsToSend(attachments: ApiAttachment[], shouldSendCompressed?: boolean) {
|
||||||
|
return !shouldSendCompressed
|
||||||
|
? attachments.map((attachment) => ({ ...attachment, shouldSendAsFile: true }))
|
||||||
|
: attachments;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { validateFiles } from '../../../../util/files';
|
||||||
|
|
||||||
export default async function getFilesFromDataTransferItems(dataTransferItems: DataTransferItemList) {
|
export default async function getFilesFromDataTransferItems(dataTransferItems: DataTransferItemList) {
|
||||||
const files: File[] = [];
|
const files: File[] = [];
|
||||||
|
|
||||||
@ -45,14 +47,5 @@ export default async function getFilesFromDataTransferItems(dataTransferItems: D
|
|||||||
|
|
||||||
await Promise.all(entriesPromises);
|
await Promise.all(entriesPromises);
|
||||||
|
|
||||||
return files.map(fixMovMime);
|
return validateFiles(files);
|
||||||
}
|
|
||||||
|
|
||||||
// .mov MIME type not reported sometimes https://developer.mozilla.org/en-US/docs/Web/API/File/type#sect1
|
|
||||||
function fixMovMime(file: File) {
|
|
||||||
const ext = file.name.split('.').pop()!;
|
|
||||||
if (!file.type && ext.toLowerCase() === 'mov') {
|
|
||||||
return new File([file], file.name, { type: 'video/quicktime' });
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/components/middle/composer/hooks/useAttachmentModal.ts
Normal file
66
src/components/middle/composer/hooks/useAttachmentModal.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { useCallback, useState } from '../../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../../global';
|
||||||
|
|
||||||
|
import type { ApiAttachment } from '../../../../api/types';
|
||||||
|
|
||||||
|
import buildAttachment from '../helpers/buildAttachment';
|
||||||
|
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
||||||
|
|
||||||
|
export default function useAttachmentModal({
|
||||||
|
attachments,
|
||||||
|
fileSizeLimit,
|
||||||
|
setHtml,
|
||||||
|
setAttachments,
|
||||||
|
}: {
|
||||||
|
attachments: ApiAttachment[];
|
||||||
|
fileSizeLimit: number;
|
||||||
|
setHtml: (html: string) => void;
|
||||||
|
setAttachments: (attachments: ApiAttachment[]) => void;
|
||||||
|
}) {
|
||||||
|
const { openLimitReachedModal } = getActions();
|
||||||
|
const [shouldSuggestCompression, setShouldSuggestCompression] = useState<boolean | undefined>(undefined);
|
||||||
|
|
||||||
|
const handleClearAttachments = useCallback(() => {
|
||||||
|
setAttachments(MEMO_EMPTY_ARRAY);
|
||||||
|
}, [setAttachments]);
|
||||||
|
|
||||||
|
const handleDeleteAttachment = useCallback((index: number) => {
|
||||||
|
const newAttachments = attachments.filter((_, i) => i !== index);
|
||||||
|
setAttachments(newAttachments?.length ? newAttachments : MEMO_EMPTY_ARRAY);
|
||||||
|
}, [attachments, setAttachments]);
|
||||||
|
|
||||||
|
const handleSetAttachments = useCallback(
|
||||||
|
(newValue: ApiAttachment[] | ((current: ApiAttachment[]) => ApiAttachment[])) => {
|
||||||
|
const newAttachments = typeof newValue === 'function' ? newValue(attachments) : newValue;
|
||||||
|
if (newAttachments.some(({ size }) => size > fileSizeLimit)) {
|
||||||
|
openLimitReachedModal({
|
||||||
|
limit: 'uploadMaxFileparts',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setAttachments(newAttachments);
|
||||||
|
}
|
||||||
|
}, [attachments, fileSizeLimit, openLimitReachedModal, setAttachments],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAppendFiles = useCallback(async (files: File[]) => {
|
||||||
|
handleSetAttachments([
|
||||||
|
...attachments,
|
||||||
|
...await Promise.all(files.map((file) => buildAttachment(file.name, file))),
|
||||||
|
]);
|
||||||
|
}, [attachments, handleSetAttachments]);
|
||||||
|
|
||||||
|
const handleFileSelect = useCallback(async (files: File[], suggestCompression?: boolean) => {
|
||||||
|
handleSetAttachments(await Promise.all(files.map((file) => buildAttachment(file.name, file))));
|
||||||
|
setShouldSuggestCompression(suggestCompression);
|
||||||
|
}, [handleSetAttachments]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldSuggestCompression,
|
||||||
|
handleAppendFiles,
|
||||||
|
handleFileSelect,
|
||||||
|
onCaptionUpdate: setHtml,
|
||||||
|
handleClearAttachments,
|
||||||
|
handleDeleteAttachment,
|
||||||
|
handleSetAttachments,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -10,7 +10,6 @@ import getFilesFromDataTransferItems from '../helpers/getFilesFromDataTransferIt
|
|||||||
import parseMessageInput, { ENTITY_CLASS_BY_NODE_NAME } from '../../../../util/parseMessageInput';
|
import parseMessageInput, { ENTITY_CLASS_BY_NODE_NAME } from '../../../../util/parseMessageInput';
|
||||||
import { containsCustomEmoji, stripCustomEmoji } from '../../../../global/helpers/symbols';
|
import { containsCustomEmoji, stripCustomEmoji } from '../../../../global/helpers/symbols';
|
||||||
|
|
||||||
const CLIPBOARD_ACCEPTED_TYPES = ['image/png', 'image/jpeg', 'image/gif'];
|
|
||||||
const MAX_MESSAGE_LENGTH = 4096;
|
const MAX_MESSAGE_LENGTH = 4096;
|
||||||
|
|
||||||
const STYLE_TAG_REGEX = /<style>(.*?)<\/style>/gs;
|
const STYLE_TAG_REGEX = /<style>(.*?)<\/style>/gs;
|
||||||
@ -90,20 +89,20 @@ const useClipboardPaste = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { items } = e.clipboardData;
|
const { items } = e.clipboardData;
|
||||||
let files: File[] = [];
|
let files: File[] | undefined = [];
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
files = await getFilesFromDataTransferItems(items);
|
files = await getFilesFromDataTransferItems(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.length === 0 && !pastedText) {
|
if (!files?.length && !pastedText) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.length > 0 && !editedMessage) {
|
if (files?.length && !editedMessage) {
|
||||||
const newAttachments = await Promise.all(files.map((file) => {
|
const newAttachments = await Promise.all(files.map((file) => {
|
||||||
return buildAttachment(file.name, file, files.length === 1 && CLIPBOARD_ACCEPTED_TYPES.includes(file.type));
|
return buildAttachment(file.name, file);
|
||||||
}));
|
}));
|
||||||
setAttachments((attachments) => attachments.concat(newAttachments));
|
setAttachments((attachments) => attachments.concat(newAttachments));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.has-open-dialog &:not(.CustomSendMenu):not(.web-app-more-menu) .bubble {
|
body.has-open-dialog &:not(.CustomSendMenu):not(.web-app-more-menu):not(.attachment-modal-more-menu) .bubble {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -194,4 +194,8 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-button-spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ type OwnProps = {
|
|||||||
header?: TeactNode;
|
header?: TeactNode;
|
||||||
hasCloseButton?: boolean;
|
hasCloseButton?: boolean;
|
||||||
noBackdrop?: boolean;
|
noBackdrop?: boolean;
|
||||||
|
noBackdropClose?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
style?: string;
|
style?: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@ -48,6 +49,7 @@ const Modal: FC<OwnProps & StateProps> = ({
|
|||||||
header,
|
header,
|
||||||
hasCloseButton,
|
hasCloseButton,
|
||||||
noBackdrop,
|
noBackdrop,
|
||||||
|
noBackdropClose,
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
onClose,
|
onClose,
|
||||||
@ -145,7 +147,7 @@ const Modal: FC<OwnProps & StateProps> = ({
|
|||||||
role="dialog"
|
role="dialog"
|
||||||
>
|
>
|
||||||
<div className="modal-container">
|
<div className="modal-container">
|
||||||
<div className="modal-backdrop" onClick={onClose} />
|
<div className="modal-backdrop" onClick={!noBackdropClose ? onClose : undefined} />
|
||||||
<div className="modal-dialog" ref={dialogRef}>
|
<div className="modal-dialog" ref={dialogRef}>
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
<div className="modal-content custom-scroll" style={style}>
|
<div className="modal-content custom-scroll" style={style}>
|
||||||
|
|||||||
@ -25,6 +25,9 @@ import {
|
|||||||
RE_TG_LINK,
|
RE_TG_LINK,
|
||||||
RE_TME_LINK,
|
RE_TME_LINK,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
|
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||||
|
SUPPORTED_IMAGE_CONTENT_TYPES,
|
||||||
|
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { IS_IOS } from '../../../util/environment';
|
import { IS_IOS } from '../../../util/environment';
|
||||||
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
||||||
@ -235,7 +238,7 @@ addActionHandler('sendMessage', (global, actions, payload) => {
|
|||||||
actions.clearWebPagePreview({ chatId, threadId, value: false });
|
actions.clearWebPagePreview({ chatId, threadId, value: false });
|
||||||
|
|
||||||
const isSingle = !payload.attachments || payload.attachments.length <= 1;
|
const isSingle = !payload.attachments || payload.attachments.length <= 1;
|
||||||
const isGrouped = !isSingle && payload.attachments && payload.attachments.length > 1;
|
const isGrouped = !isSingle && payload.shouldGroupMessages;
|
||||||
|
|
||||||
if (isSingle) {
|
if (isSingle) {
|
||||||
const { attachments, ...restParams } = params;
|
const { attachments, ...restParams } = params;
|
||||||
@ -247,27 +250,33 @@ addActionHandler('sendMessage', (global, actions, payload) => {
|
|||||||
const {
|
const {
|
||||||
text, entities, attachments, ...commonParams
|
text, entities, attachments, ...commonParams
|
||||||
} = params;
|
} = params;
|
||||||
const groupedAttachments = split(attachments as ApiAttachment[], MAX_MEDIA_FILES_FOR_ALBUM);
|
const byType = splitAttachmentsByType(attachments);
|
||||||
for (let i = 0; i < groupedAttachments.length; i++) {
|
|
||||||
const [firstAttachment, ...restAttachments] = groupedAttachments[i];
|
|
||||||
const groupedId = `${Date.now()}${i}`;
|
|
||||||
|
|
||||||
sendMessage({
|
byType.forEach((group, groupIndex) => {
|
||||||
...commonParams,
|
const groupedAttachments = split(group as ApiAttachment[], MAX_MEDIA_FILES_FOR_ALBUM);
|
||||||
text: i === 0 ? text : undefined,
|
for (let i = 0; i < groupedAttachments.length; i++) {
|
||||||
entities: i === 0 ? entities : undefined,
|
const [firstAttachment, ...restAttachments] = groupedAttachments[i];
|
||||||
attachment: firstAttachment,
|
const groupedId = `${Date.now()}${groupIndex}${i}`;
|
||||||
groupedId: restAttachments.length > 0 ? groupedId : undefined,
|
|
||||||
});
|
const isFirst = i === 0 && groupIndex === 0;
|
||||||
|
|
||||||
restAttachments.forEach((attachment: ApiAttachment) => {
|
|
||||||
sendMessage({
|
sendMessage({
|
||||||
...commonParams,
|
...commonParams,
|
||||||
attachment,
|
text: isFirst ? text : undefined,
|
||||||
groupedId,
|
entities: isFirst ? entities : undefined,
|
||||||
|
attachment: firstAttachment,
|
||||||
|
groupedId: restAttachments.length > 0 ? groupedId : undefined,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
restAttachments.forEach((attachment: ApiAttachment) => {
|
||||||
|
sendMessage({
|
||||||
|
...commonParams,
|
||||||
|
attachment,
|
||||||
|
groupedId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const {
|
const {
|
||||||
text, entities, attachments, replyingTo, ...commonParams
|
text, entities, attachments, replyingTo, ...commonParams
|
||||||
@ -1382,3 +1391,33 @@ function countSortedIds(ids: number[], from: number, to: number) {
|
|||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function splitAttachmentsByType(attachments: ApiAttachment[]) {
|
||||||
|
return attachments.reduce((acc, attachment, index, arr) => {
|
||||||
|
if (index === 0) {
|
||||||
|
acc.push([attachment]);
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = getAttachmentType(attachment);
|
||||||
|
const previousType = getAttachmentType(arr[index - 1]);
|
||||||
|
if (type === previousType) {
|
||||||
|
acc[acc.length - 1].push(attachment);
|
||||||
|
} else {
|
||||||
|
acc.push([attachment]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [] as ApiAttachment[][]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAttachmentType(attachment: ApiAttachment) {
|
||||||
|
const {
|
||||||
|
shouldSendAsFile, mimeType,
|
||||||
|
} = attachment;
|
||||||
|
if (shouldSendAsFile) return 'file';
|
||||||
|
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType) || SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) return 'media';
|
||||||
|
if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) return 'audio';
|
||||||
|
if (attachment.voice) return 'voice';
|
||||||
|
return 'file';
|
||||||
|
}
|
||||||
|
|||||||
@ -390,6 +390,20 @@ addActionHandler('requestConfetti', (global, actions, payload) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('updateAttachmentSettings', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
shouldCompress, shouldSendGrouped,
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
attachmentSettings: {
|
||||||
|
shouldCompress: shouldCompress ?? global.attachmentSettings.shouldCompress,
|
||||||
|
shouldSendGrouped: shouldSendGrouped ?? global.attachmentSettings.shouldSendGrouped,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('openLimitReachedModal', (global, actions, payload) => {
|
addActionHandler('openLimitReachedModal', (global, actions, payload) => {
|
||||||
const { limit } = payload;
|
const { limit } = payload;
|
||||||
|
|
||||||
|
|||||||
@ -427,6 +427,7 @@ export function serializeGlobal(global: GlobalState) {
|
|||||||
'shouldShowContextMenuHint',
|
'shouldShowContextMenuHint',
|
||||||
'leftColumnWidth',
|
'leftColumnWidth',
|
||||||
'serviceNotifications',
|
'serviceNotifications',
|
||||||
|
'attachmentSettings',
|
||||||
]),
|
]),
|
||||||
audioPlayer: {
|
audioPlayer: {
|
||||||
volume: global.audioPlayer.volume,
|
volume: global.audioPlayer.volume,
|
||||||
|
|||||||
@ -51,6 +51,11 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
byId: {},
|
byId: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
attachmentSettings: {
|
||||||
|
shouldCompress: true,
|
||||||
|
shouldSendGrouped: true,
|
||||||
|
},
|
||||||
|
|
||||||
scheduledMessages: {
|
scheduledMessages: {
|
||||||
byChatId: {},
|
byChatId: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -229,6 +229,8 @@ export type GlobalState = {
|
|||||||
sticker?: ApiSticker;
|
sticker?: ApiSticker;
|
||||||
poll?: ApiNewPoll;
|
poll?: ApiNewPoll;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
|
sendGrouped?: boolean;
|
||||||
|
sendCompressed?: boolean;
|
||||||
};
|
};
|
||||||
sponsoredByChatId: Record<string, ApiSponsoredMessage>;
|
sponsoredByChatId: Record<string, ApiSponsoredMessage>;
|
||||||
};
|
};
|
||||||
@ -250,6 +252,11 @@ export type GlobalState = {
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
attachmentSettings: {
|
||||||
|
shouldCompress: boolean;
|
||||||
|
shouldSendGrouped: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
chatFolders: {
|
chatFolders: {
|
||||||
orderedIds?: number[];
|
orderedIds?: number[];
|
||||||
byId: Record<number, ApiChatFolder>;
|
byId: Record<number, ApiChatFolder>;
|
||||||
@ -1237,6 +1244,11 @@ export interface ActionPayloads {
|
|||||||
height: number;
|
height: number;
|
||||||
} | undefined;
|
} | undefined;
|
||||||
|
|
||||||
|
updateAttachmentSettings: {
|
||||||
|
shouldCompress?: boolean;
|
||||||
|
shouldSendGrouped?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
openUrl: {
|
openUrl: {
|
||||||
url: string;
|
url: string;
|
||||||
shouldSkipModal?: boolean;
|
shouldSkipModal?: boolean;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "Telegram T",
|
"name": "Telegram T",
|
||||||
"lastOpened": 0,
|
"lastOpened": 0,
|
||||||
"created": 1670592461603
|
"created": 1674067291539
|
||||||
},
|
},
|
||||||
"iconSets": [
|
"iconSets": [
|
||||||
{
|
{
|
||||||
@ -2065,6 +2065,72 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icons": [
|
"icons": [
|
||||||
|
{
|
||||||
|
"id": 101,
|
||||||
|
"paths": [
|
||||||
|
"M660.48 128c0 25.6-20.48 40.96-40.96 40.96-25.6 0-40.96-20.48-40.96-40.96 0-25.6 20.48-40.96 40.96-40.96s40.96 15.36 40.96 40.96zM619.52 363.52c30.72 0 51.2-25.6 51.2-51.2 0-30.72-25.6-51.2-51.2-51.2-30.72 0-51.2 25.6-51.2 51.2-5.12 25.6 20.48 51.2 51.2 51.2zM788.48 563.2c30.72 0 51.2-25.6 51.2-51.2 0-30.72-25.6-51.2-51.2-51.2-30.72 0-51.2 25.6-51.2 51.2 0 30.72 20.48 51.2 51.2 51.2zM983.040 512c0 25.6-20.48 40.96-40.96 40.96-25.6 0-40.96-20.48-40.96-40.96 0-25.6 20.48-40.96 40.96-40.96s40.96 15.36 40.96 40.96zM619.52 936.96c25.6 0 40.96-20.48 40.96-40.96s-20.48-40.96-40.96-40.96c-25.6 0-40.96 20.48-40.96 40.96s15.36 40.96 40.96 40.96zM788.48 353.28c25.6 0 40.96-20.48 40.96-40.96 0-25.6-20.48-40.96-40.96-40.96-25.6 0-40.96 20.48-40.96 40.96s20.48 40.96 40.96 40.96zM819.2 128c0 15.36-15.36 30.72-30.72 30.72s-30.72-15.36-30.72-30.72c0-15.36 15.36-30.72 30.72-30.72 20.48 0 30.72 15.36 30.72 30.72zM936.96 343.040c20.48 0 35.84-15.36 35.84-35.84s-15.36-30.72-30.72-30.72c-15.36 0-30.72 15.36-30.72 30.72s10.24 35.84 25.6 35.84zM972.8 727.040c0 15.36-15.36 30.72-30.72 30.72s-30.72-15.36-30.72-30.72c0-15.36 15.36-30.72 30.72-30.72 15.36-5.12 30.72 10.24 30.72 30.72zM936.96 148.48c10.24 0 20.48-10.24 20.48-20.48s-10.24-20.48-20.48-20.48c-10.24 0-20.48 10.24-20.48 20.48s10.24 20.48 20.48 20.48zM399.36 87.040v0 0c-46.080 0-87.040 0-117.76 0-25.6 5.12-56.32 10.24-81.92 20.48-30.72 15.36-56.32 40.96-76.8 71.68l61.44 61.44c0 0 0 0 0-5.12 10.24-25.6 30.72-46.080 56.32-56.32 10.24-5.12 25.6-10.24 51.2-10.24s61.44 0 107.52 0h66.56c25.6-0 46.080-15.36 46.080-40.96s-20.48-40.96-40.96-40.96h-71.68zM174.080 291.84c0 25.6 0 61.44 0 107.52v220.16c0 46.080 0 81.92 0 107.52 0 5.12 0 5.12 0 5.12l66.56-107.52c15.36-20.48 25.6-40.96 35.84-56.32s25.6-30.72 46.080-35.84c25.6-10.24 56.32-10.24 87.040 0 20.48 10.24 35.84 25.6 46.080 35.84 10.24 15.36 20.48 35.84 35.84 56.32v0l5.12 5.12c10.24 20.48 5.12 46.080-15.36 56.32s-46.080 5.12-56.32-15.36l-5.12-5.12c-15.36-25.6-25.6-40.96-30.72-51.2-15.36 5.12-15.36 0-20.48 0s-10.24 0-15.36 0c0 0-5.12 0-10.24 10.24-10.24 10.24-15.36 25.6-30.72 51.2l-92.16 148.48c5.12 5.12 15.36 10.24 20.48 15.36 10.24 5.12 25.6 10.24 51.2 10.24s61.44 0 107.52 0h66.56c25.6 0 40.96 20.48 40.96 40.96 0 25.6-20.48 40.96-40.96 40.96h-66.56c-46.080 0-81.92 0-112.64 0s-56.32-10.24-81.92-20.48c-40.96-20.48-71.68-51.2-92.16-92.16-15.36-25.6-20.48-51.2-20.48-81.92s0-66.56 0-112.64v0-225.28c0-46.080 0-81.92 0-112.64 0-25.6 5.12-46.080 10.24-66.56l71.68 71.68z",
|
||||||
|
"M128 128l768 768z",
|
||||||
|
"M896 936.96c-10.24 0-20.48-5.12-30.72-10.24l-768-768c-15.36-15.36-15.36-46.080 0-61.44s46.080-15.36 61.44 0l768 768c15.36 15.36 15.36 46.080 0 61.44-10.24 10.24-20.48 10.24-30.72 10.24z"
|
||||||
|
],
|
||||||
|
"attrs": [
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
"isMulticolor": false,
|
||||||
|
"isMulticolor2": false,
|
||||||
|
"grid": 24,
|
||||||
|
"tags": [
|
||||||
|
"spoiler-disable"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 100,
|
||||||
|
"paths": [
|
||||||
|
"M936.96 286.72c0-30.72-10.24-56.32-20.48-81.92-20.48-40.96-51.2-71.68-92.16-92.16-25.6-10.24-51.2-20.48-81.92-20.48s-66.56 0-112.64 0h-230.4c-46.080 0-81.92 0-112.64 0s-61.44 5.12-87.040 15.36c-40.96 20.48-71.68 51.2-92.16 92.16-10.24 25.6-15.36 56.32-20.48 87.040 0 30.72 0 66.56 0 112.64v225.28c0 46.080 0 81.92 0 112.64s10.24 56.32 20.48 81.92c20.48 40.96 51.2 71.68 92.16 92.16 25.6 10.24 51.2 20.48 81.92 20.48s66.56 0 112.64 0h225.28c46.080 0 81.92 0 112.64 0s56.32-10.24 81.92-20.48c40.96-20.48 71.68-51.2 92.16-92.16 10.24-25.6 20.48-51.2 20.48-81.92s0-66.56 0-112.64v-225.28c10.24-46.080 10.24-81.92 10.24-112.64zM552.96 168.96h66.56c46.080 0 81.92 0 107.52 0s40.96 5.12 51.2 10.24 46.080 30.72 56.32 56.32c5.12 10.24 10.24 25.6 10.24 51.2s0 61.44 0 107.52v66.56h-296.96v-291.84zM471.040 855.040h-71.68c-46.080 0-81.92 0-107.52 0s-40.96-5.12-51.2-10.24-46.080-30.72-56.32-56.32c-5.12-10.24-10.24-25.6-10.24-51.2s0-61.44 0-107.52v-230.4c0-46.080 0-81.92 0-107.52s5.12-40.96 10.24-51.2 30.72-46.080 56.32-56.32c10.24-5.12 25.6-10.24 51.2-10.24s61.44 0 107.52 0h66.56v680.96zM855.040 624.64c0 46.080 0 81.92 0 107.52s-5.12 40.96-10.24 51.2-30.72 46.080-56.32 56.32c-10.24 5.12-25.6 10.24-51.2 10.24s-61.44 0-107.52 0h-66.56v-296.96h296.96v71.68z"
|
||||||
|
],
|
||||||
|
"attrs": [
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
"grid": 24,
|
||||||
|
"tags": [
|
||||||
|
"grouped"
|
||||||
|
],
|
||||||
|
"isMulticolor": false,
|
||||||
|
"isMulticolor2": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 99,
|
||||||
|
"paths": [
|
||||||
|
"M732.16 849.92c-25.6 0-61.44 0-107.52 0h-66.56v-179.2l-87.040-87.040v261.12h-71.68c-46.080 0-81.92 0-107.52 0s-40.96-5.12-51.2-10.24-46.080-30.72-56.32-56.32c-5.12-10.24-10.24-25.6-10.24-51.2s0-61.44 0-107.52v-220.16c0-46.080 0-81.92 0-107.52l-71.68-71.68c-10.24 20.48-10.24 40.96-15.36 66.56 0 30.72 0 66.56 0 112.64v225.28c0 46.080 0 81.92 0 112.64s10.24 56.32 20.48 81.92c20.48 40.96 51.2 71.68 92.16 92.16 25.6 10.24 51.2 20.48 81.92 20.48s66.56 0 112.64 0h225.28c46.080 0 81.92 0 112.64 0 25.6 0 46.080-5.12 66.56-10.24l-66.56-71.68z",
|
||||||
|
"M926.72 865.28l-25.6-20.48c5.12-5.12 10.24-15.36 10.24-20.48 10.24-25.6 20.48-51.2 20.48-81.92s0-66.56 0-112.64v-230.4c0-46.080 0-81.92 0-112.64s-10.24-56.32-20.48-81.92c-20.48-40.96-51.2-71.68-92.16-92.16-25.6-10.24-51.2-20.48-81.92-20.48s-66.56 0-112.64 0h-225.28c-46.080 0-81.92 0-112.64 0s-61.44 5.12-87.040 15.36c-5.12 5.12-10.24 10.24-20.48 15.36l-20.48-25.6c-15.36-15.36-46.080-15.36-61.44 0s-15.36 46.080 0 61.44l25.6 20.48 721.92 721.92 20.48 25.6c10.24 10.24 20.48 10.24 30.72 10.24s20.48-5.12 30.72-10.24c15.36-15.36 15.36-46.080 0-61.44zM471.040 409.6l-225.28-225.28c10.24-5.12 25.6-10.24 51.2-10.24s61.44 0 107.52 0h66.56v235.52zM855.040 624.64c0 46.080 0 81.92 0 107.52s-5.12 35.84-10.24 51.2l-230.4-230.4h240.64v71.68zM855.040 471.040h-296.96v-302.080h66.56c46.080 0 81.92 0 107.52 0s40.96 5.12 51.2 10.24 46.080 30.72 56.32 56.32c5.12 10.24 10.24 25.6 10.24 51.2s0 61.44 0 107.52v76.8z"
|
||||||
|
],
|
||||||
|
"attrs": [
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
"grid": 24,
|
||||||
|
"tags": [
|
||||||
|
"grouped-disable"
|
||||||
|
],
|
||||||
|
"isMulticolor": false,
|
||||||
|
"isMulticolor2": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 98,
|
||||||
|
"paths": [
|
||||||
|
"M661.335 128.001c0 23.564-19.103 42.666-42.67 42.666-23.562 0-42.665-19.103-42.665-42.666s19.103-42.667 42.665-42.667c23.567 0 42.67 19.103 42.67 42.667zM672 309.334c0 29.455-23.88 53.334-53.335 53.334s-53.33-23.878-53.33-53.334c0-29.455 23.875-53.333 53.33-53.333s53.335 23.878 53.335 53.333zM618.665 576c35.348 0 64-28.652 64-64 0-35.346-28.652-63.999-64-63.999-35.343 0-64 28.654-64 63.999 0 35.348 28.657 64 64 64zM789.335 565.335c29.455 0 53.33-23.88 53.33-53.335s-23.875-53.333-53.33-53.333c-29.455 0-53.335 23.878-53.335 53.333s23.88 53.335 53.335 53.335zM618.665 768c29.455 0 53.335-23.88 53.335-53.335s-23.88-53.33-53.335-53.33c-29.455 0-53.33 23.875-53.33 53.33s23.875 53.335 53.33 53.335zM981.335 512c0 23.562-19.103 42.665-42.67 42.665-23.562 0-42.665-19.103-42.665-42.665 0-23.564 19.103-42.666 42.665-42.666 23.567 0 42.67 19.103 42.67 42.666zM618.665 938.665c23.567 0 42.67-19.103 42.67-42.665s-19.103-42.665-42.67-42.665c-23.562 0-42.665 19.103-42.665 42.665s19.103 42.665 42.665 42.665zM789.335 768c23.562 0 42.665-19.103 42.665-42.665 0-23.567-19.103-42.67-42.665-42.67-23.567 0-42.67 19.103-42.67 42.67 0 23.562 19.103 42.665 42.67 42.665zM832 309.334c0 23.564-19.103 42.667-42.665 42.667-23.567 0-42.67-19.103-42.67-42.667s19.103-42.666 42.67-42.666c23.562 0 42.665 19.102 42.665 42.666zM789.335 160.001c17.669 0 32-14.327 32-32s-14.331-32-32-32c-17.674 0-32 14.327-32 32s14.326 32 32 32zM970.665 309.334c0 17.673-14.326 32-32 32-17.669 0-32-14.327-32-32s14.331-32 32-32c17.674 0 32 14.327 32 32zM938.665 757.335c17.674 0 32-14.331 32-32 0-17.674-14.326-32-32-32-17.669 0-32 14.326-32 32 0 17.669 14.331 32 32 32zM960 896c0 11.781-9.549 21.335-21.335 21.335-11.781 0-21.33-9.554-21.33-21.335s9.549-21.335 21.33-21.335c11.786 0 21.335 9.554 21.335 21.335zM938.665 149.334c11.786 0 21.335-9.551 21.335-21.333s-9.549-21.334-21.335-21.334c-11.781 0-21.33 9.551-21.33 21.334s9.549 21.333 21.33 21.333zM821.335 896c0 17.674-14.331 32-32 32-17.674 0-32-14.326-32-32s14.326-32 32-32c17.669 0 32 14.326 32 32zM399.241 85.334h70.093c23.564 0 42.666 19.102 42.666 42.666s-19.102 42.666-42.666 42.666h-68.266c-48.495 0-82.3 0.033-108.619 2.184-25.82 2.109-40.655 6.043-51.892 11.768-24.084 12.272-43.666 31.853-55.938 55.938-5.725 11.236-9.658 26.071-11.768 51.892-2.15 26.318-2.183 60.124-2.183 108.619v221.868c0 48.492 0.033 82.299 2.183 108.616 0.21 2.57 0.438 5.028 0.684 7.388l66.466-108.447c13.569-22.139 25.257-41.211 35.977-55.572 10.939-14.659 24.677-29.834 44.331-38.339 27.029-11.699 57.691-11.699 84.719 0 19.654 8.504 33.393 23.68 44.331 38.339 10.72 14.362 22.408 33.434 35.977 55.572l5.237 8.545c12.314 20.091 6.009 46.362-14.082 58.675s-46.36 6.006-58.674-14.085l-4.22-6.881c-14.874-24.269-24.528-39.941-32.624-50.79-6.471-8.668-9.547-10.839-9.997-11.131-5.31-2.248-11.304-2.248-16.614 0-0.45 0.292-3.527 2.463-9.997 11.131-8.096 10.849-17.75 26.522-32.624 50.79l-91.69 149.596c6.374 4.961 13.236 9.334 20.508 13.041 11.237 5.724 26.072 9.656 51.892 11.766 26.319 2.15 60.124 2.186 108.619 2.186h68.266c23.564 0 42.666 19.103 42.666 42.665s-19.102 42.665-42.666 42.665h-70.094c-46.241 0.005-83.537 0.005-113.74-2.463-31.097-2.545-58.412-7.91-83.683-20.787-40.141-20.454-72.777-53.089-93.23-93.23-12.876-25.272-18.244-52.588-20.785-83.681-2.467-30.203-2.467-67.502-2.467-113.746v-225.517c-0.001-46.241-0.001-83.538 2.467-113.741 2.541-31.098 7.909-58.413 20.785-83.683 20.453-40.141 53.089-72.777 93.23-93.23 25.271-12.876 52.586-18.245 83.683-20.785 30.203-2.468 67.5-2.467 113.741-2.467h0.001z"
|
||||||
|
],
|
||||||
|
"attrs": [
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
"isMulticolor": false,
|
||||||
|
"isMulticolor2": false,
|
||||||
|
"grid": 24,
|
||||||
|
"tags": [
|
||||||
|
"spoiler"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": 97,
|
"id": 97,
|
||||||
"paths": [
|
"paths": [
|
||||||
@ -2453,19 +2519,19 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"paths": [
|
"paths": [
|
||||||
"M519.32 85.333c33.948 0 66.505 13.486 90.51 37.49l291.346 291.346c24.005 24.005 37.49 56.562 37.49 90.51v305.987c0 70.692-57.308 128-128 128h-597.333c-70.692 0-128-57.308-128-128v-597.333c0-70.692 57.308-128 128-128h305.987zM469.333 170.667h-256c-23.564 0-42.667 19.103-42.667 42.667v597.333c0 23.564 19.103 42.667 42.667 42.667h597.333c23.564 0 42.667-19.103 42.667-42.667v-256h-256c-68.168 0-123.89-53.287-127.783-120.479l-0.217-7.521v-256zM835.669 469.333l-281.003-281.003v238.336c0 21.881 16.471 39.915 37.691 42.38l4.976 0.287h238.336z"
|
"M469.334 170.76v138.202c-0.001 34.345-0.001 62.691 1.886 85.783 1.959 23.983 6.165 46.029 16.716 66.737 16.362 32.113 42.471 58.22 74.584 74.583 20.71 10.552 42.752 14.756 66.739 16.717 23.086 1.884 51.43 1.884 85.775 1.884h138.209c0.072 7.752 0.092 16.87 0.092 27.776v40.494c0 48.492-0.036 82.299-2.186 108.616-2.109 25.82-6.042 40.658-11.766 51.891-12.273 24.084-31.852 43.668-55.941 55.941-11.233 5.724-26.071 9.656-51.891 11.766-26.317 2.15-60.124 2.186-108.616 2.186h-221.868c-48.495 0-82.3-0.036-108.619-2.186-25.82-2.109-40.655-6.042-51.892-11.766-24.084-12.273-43.666-31.857-55.938-55.941-5.725-11.233-9.658-26.071-11.767-51.891-2.15-26.317-2.184-60.124-2.184-108.616v-221.869c0-48.495 0.033-82.3 2.184-108.619 2.109-25.82 6.042-40.655 11.767-51.892 12.272-24.084 31.854-43.666 55.938-55.938 11.237-5.725 26.072-9.658 51.892-11.768 26.319-2.15 60.124-2.183 108.619-2.183h40.492c10.907 0 20.023 0.017 27.774 0.093zM832 469.333h-115.2c-36.547 0-61.394-0.033-80.594-1.602-18.708-1.528-28.273-4.299-34.944-7.698-16.056-8.181-29.112-21.236-37.294-37.292-3.4-6.673-6.17-16.239-7.7-34.945-1.567-19.204-1.603-44.048-1.603-80.595v-115.2c10.639 7.285 22.17 18.509 49.812 46.149l181.376 181.374c27.638 27.64 38.861 39.173 46.147 49.809zM533.775 91.228c-24.631-5.914-50.288-5.906-87.354-5.896l-47.18 0.001c-46.242-0.001-83.539-0.001-113.742 2.467-31.097 2.541-58.412 7.909-83.683 20.785-40.141 20.453-72.777 53.089-93.23 93.23-12.876 25.271-18.244 52.586-20.785 83.683-2.467 30.203-2.467 67.5-2.467 113.742v225.518c-0.001 46.244-0.001 83.538 2.467 113.741 2.541 31.099 7.909 58.414 20.785 83.686 20.453 40.141 53.089 72.776 93.23 93.23 25.271 12.877 52.586 18.243 83.683 20.782 30.203 2.468 67.5 2.468 113.741 2.468h225.523c46.239 0 83.538 0 113.741-2.468 31.094-2.54 58.409-7.905 83.681-20.782 40.141-20.454 72.776-53.089 93.23-93.23 12.877-25.272 18.243-52.588 20.787-83.686 2.468-30.198 2.463-67.497 2.463-113.736v-42.322l0.005-4.864c0.010-37.064 0.015-62.72-5.898-87.353-5.228-21.768-13.844-42.577-25.544-61.664-13.235-21.599-31.38-39.736-57.6-65.938l-188.247-188.25c-26.204-26.217-44.339-44.365-65.94-57.601-19.087-11.697-39.895-20.316-61.665-25.542z"
|
||||||
],
|
],
|
||||||
"attrs": [
|
"attrs": [
|
||||||
{}
|
{}
|
||||||
],
|
],
|
||||||
"isMulticolor": false,
|
|
||||||
"isMulticolor2": false,
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"document"
|
"document"
|
||||||
],
|
],
|
||||||
"defaultCode": 59679,
|
"defaultCode": 59679,
|
||||||
"grid": 24,
|
"grid": 24,
|
||||||
"id": 25
|
"id": 25,
|
||||||
|
"isMulticolor": false,
|
||||||
|
"isMulticolor2": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"paths": [
|
"paths": [
|
||||||
@ -3568,8 +3634,8 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "icomoon",
|
"name": "icomoon",
|
||||||
"importSize": {
|
"importSize": {
|
||||||
"width": 24,
|
"width": 20,
|
||||||
"height": 24
|
"height": 20
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"preferences": {
|
"preferences": {
|
||||||
@ -3610,13 +3676,45 @@
|
|||||||
"showGrid": true
|
"showGrid": true
|
||||||
},
|
},
|
||||||
"selection": [
|
"selection": [
|
||||||
|
{
|
||||||
|
"order": 753,
|
||||||
|
"id": 67,
|
||||||
|
"name": "spoiler-disable",
|
||||||
|
"prevSize": 32,
|
||||||
|
"code": 59829,
|
||||||
|
"tempChar": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"order": 752,
|
||||||
|
"id": 66,
|
||||||
|
"name": "grouped",
|
||||||
|
"prevSize": 32,
|
||||||
|
"code": 59830,
|
||||||
|
"tempChar": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"order": 751,
|
||||||
|
"id": 65,
|
||||||
|
"name": "grouped-disable",
|
||||||
|
"prevSize": 32,
|
||||||
|
"code": 59831,
|
||||||
|
"tempChar": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"order": 749,
|
||||||
|
"id": 64,
|
||||||
|
"name": "spoiler",
|
||||||
|
"prevSize": 32,
|
||||||
|
"code": 59832,
|
||||||
|
"tempChar": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"order": 576,
|
"order": 576,
|
||||||
"id": 63,
|
"id": 63,
|
||||||
"name": "select",
|
"name": "select",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59744,
|
"code": 59744,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 480,
|
"order": 480,
|
||||||
@ -3624,7 +3722,7 @@
|
|||||||
"name": "folder",
|
"name": "folder",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59667,
|
"code": 59667,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 481,
|
"order": 481,
|
||||||
@ -3632,7 +3730,7 @@
|
|||||||
"name": "bots",
|
"name": "bots",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59669,
|
"code": 59669,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 482,
|
"order": 482,
|
||||||
@ -3640,7 +3738,7 @@
|
|||||||
"name": "calendar",
|
"name": "calendar",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59670,
|
"code": 59670,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 483,
|
"order": 483,
|
||||||
@ -3648,7 +3746,7 @@
|
|||||||
"name": "cloud-download",
|
"name": "cloud-download",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59671,
|
"code": 59671,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 484,
|
"order": 484,
|
||||||
@ -3656,7 +3754,7 @@
|
|||||||
"name": "colorize",
|
"name": "colorize",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59672,
|
"code": 59672,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 651,
|
"order": 651,
|
||||||
@ -3664,7 +3762,7 @@
|
|||||||
"name": "forward",
|
"name": "forward",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59687,
|
"code": 59687,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 650,
|
"order": 650,
|
||||||
@ -3672,7 +3770,7 @@
|
|||||||
"name": "reply",
|
"name": "reply",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59719,
|
"code": 59719,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 487,
|
"order": 487,
|
||||||
@ -3680,7 +3778,7 @@
|
|||||||
"name": "help",
|
"name": "help",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59690,
|
"code": 59690,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 488,
|
"order": 488,
|
||||||
@ -3688,7 +3786,7 @@
|
|||||||
"name": "info",
|
"name": "info",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59691,
|
"code": 59691,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 489,
|
"order": 489,
|
||||||
@ -3696,7 +3794,7 @@
|
|||||||
"name": "info-filled",
|
"name": "info-filled",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59675,
|
"code": 59675,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 490,
|
"order": 490,
|
||||||
@ -3704,7 +3802,7 @@
|
|||||||
"name": "delete-filled",
|
"name": "delete-filled",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59676,
|
"code": 59676,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 491,
|
"order": 491,
|
||||||
@ -3712,7 +3810,7 @@
|
|||||||
"name": "delete",
|
"name": "delete",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59677,
|
"code": 59677,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 492,
|
"order": 492,
|
||||||
@ -3720,7 +3818,7 @@
|
|||||||
"name": "edit",
|
"name": "edit",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59683,
|
"code": 59683,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 493,
|
"order": 493,
|
||||||
@ -3728,7 +3826,7 @@
|
|||||||
"name": "new-chat-filled",
|
"name": "new-chat-filled",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59705,
|
"code": 59705,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 494,
|
"order": 494,
|
||||||
@ -3736,7 +3834,7 @@
|
|||||||
"name": "send",
|
"name": "send",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59722,
|
"code": 59722,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 495,
|
"order": 495,
|
||||||
@ -3744,7 +3842,7 @@
|
|||||||
"name": "send-outline",
|
"name": "send-outline",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59723,
|
"code": 59723,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 496,
|
"order": 496,
|
||||||
@ -3752,7 +3850,7 @@
|
|||||||
"name": "add-user-filled",
|
"name": "add-user-filled",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59652,
|
"code": 59652,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 497,
|
"order": 497,
|
||||||
@ -3760,7 +3858,7 @@
|
|||||||
"name": "add-user",
|
"name": "add-user",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59653,
|
"code": 59653,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 498,
|
"order": 498,
|
||||||
@ -3768,7 +3866,7 @@
|
|||||||
"name": "delete-user",
|
"name": "delete-user",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59678,
|
"code": 59678,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 499,
|
"order": 499,
|
||||||
@ -3776,7 +3874,7 @@
|
|||||||
"name": "microphone",
|
"name": "microphone",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59701,
|
"code": 59701,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 500,
|
"order": 500,
|
||||||
@ -3784,7 +3882,7 @@
|
|||||||
"name": "microphone-alt",
|
"name": "microphone-alt",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59707,
|
"code": 59707,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 501,
|
"order": 501,
|
||||||
@ -3792,7 +3890,7 @@
|
|||||||
"name": "poll",
|
"name": "poll",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59704,
|
"code": 59704,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 502,
|
"order": 502,
|
||||||
@ -3800,7 +3898,7 @@
|
|||||||
"name": "revote",
|
"name": "revote",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59706,
|
"code": 59706,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 503,
|
"order": 503,
|
||||||
@ -3808,15 +3906,15 @@
|
|||||||
"name": "photo",
|
"name": "photo",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59712,
|
"code": 59712,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 504,
|
"order": 748,
|
||||||
"id": 18,
|
"id": 18,
|
||||||
"name": "document",
|
"name": "document",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59679,
|
"code": 59679,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 505,
|
"order": 505,
|
||||||
@ -3824,7 +3922,7 @@
|
|||||||
"name": "camera",
|
"name": "camera",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59662,
|
"code": 59662,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 506,
|
"order": 506,
|
||||||
@ -3832,7 +3930,7 @@
|
|||||||
"name": "camera-add",
|
"name": "camera-add",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59663,
|
"code": 59663,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 507,
|
"order": 507,
|
||||||
@ -3840,7 +3938,7 @@
|
|||||||
"name": "logout",
|
"name": "logout",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59698,
|
"code": 59698,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 508,
|
"order": 508,
|
||||||
@ -3848,7 +3946,7 @@
|
|||||||
"name": "saved-messages",
|
"name": "saved-messages",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59720,
|
"code": 59720,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 509,
|
"order": 509,
|
||||||
@ -3856,7 +3954,7 @@
|
|||||||
"name": "settings",
|
"name": "settings",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59726,
|
"code": 59726,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 652,
|
"order": 652,
|
||||||
@ -3864,7 +3962,7 @@
|
|||||||
"name": "phone",
|
"name": "phone",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59711,
|
"code": 59711,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 653,
|
"order": 653,
|
||||||
@ -3872,7 +3970,7 @@
|
|||||||
"name": "attach",
|
"name": "attach",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59657,
|
"code": 59657,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 512,
|
"order": 512,
|
||||||
@ -3880,7 +3978,7 @@
|
|||||||
"name": "copy",
|
"name": "copy",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59674,
|
"code": 59674,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 513,
|
"order": 513,
|
||||||
@ -3888,7 +3986,7 @@
|
|||||||
"name": "channel",
|
"name": "channel",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59665,
|
"code": 59665,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 514,
|
"order": 514,
|
||||||
@ -3896,7 +3994,7 @@
|
|||||||
"name": "group",
|
"name": "group",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59689,
|
"code": 59689,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 515,
|
"order": 515,
|
||||||
@ -3904,7 +4002,7 @@
|
|||||||
"name": "user",
|
"name": "user",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59737,
|
"code": 59737,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 516,
|
"order": 516,
|
||||||
@ -3912,7 +4010,7 @@
|
|||||||
"name": "non-contacts",
|
"name": "non-contacts",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59688,
|
"code": 59688,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 517,
|
"order": 517,
|
||||||
@ -3920,7 +4018,7 @@
|
|||||||
"name": "active-sessions",
|
"name": "active-sessions",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59650,
|
"code": 59650,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 518,
|
"order": 518,
|
||||||
@ -3928,7 +4026,7 @@
|
|||||||
"name": "admin",
|
"name": "admin",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59654,
|
"code": 59654,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 519,
|
"order": 519,
|
||||||
@ -3936,7 +4034,7 @@
|
|||||||
"name": "download",
|
"name": "download",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59681,
|
"code": 59681,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 520,
|
"order": 520,
|
||||||
@ -3944,7 +4042,7 @@
|
|||||||
"name": "location",
|
"name": "location",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59696,
|
"code": 59696,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 521,
|
"order": 521,
|
||||||
@ -3952,7 +4050,7 @@
|
|||||||
"name": "stop",
|
"name": "stop",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59730,
|
"code": 59730,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 523,
|
"order": 523,
|
||||||
@ -3960,7 +4058,7 @@
|
|||||||
"name": "archive",
|
"name": "archive",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59656,
|
"code": 59656,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 524,
|
"order": 524,
|
||||||
@ -3968,7 +4066,7 @@
|
|||||||
"name": "unarchive",
|
"name": "unarchive",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59731,
|
"code": 59731,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 525,
|
"order": 525,
|
||||||
@ -3976,7 +4074,7 @@
|
|||||||
"name": "readchats",
|
"name": "readchats",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59699,
|
"code": 59699,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 526,
|
"order": 526,
|
||||||
@ -3984,7 +4082,7 @@
|
|||||||
"name": "unread",
|
"name": "unread",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59735,
|
"code": 59735,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 654,
|
"order": 654,
|
||||||
@ -3992,7 +4090,7 @@
|
|||||||
"name": "message",
|
"name": "message",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59700,
|
"code": 59700,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 659,
|
"order": 659,
|
||||||
@ -4000,7 +4098,7 @@
|
|||||||
"name": "lock",
|
"name": "lock",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59697,
|
"code": 59697,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 529,
|
"order": 529,
|
||||||
@ -4008,7 +4106,7 @@
|
|||||||
"name": "unlock",
|
"name": "unlock",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59732,
|
"code": 59732,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 530,
|
"order": 530,
|
||||||
@ -4016,7 +4114,7 @@
|
|||||||
"name": "mute",
|
"name": "mute",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59703,
|
"code": 59703,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 531,
|
"order": 531,
|
||||||
@ -4024,7 +4122,7 @@
|
|||||||
"name": "unmute",
|
"name": "unmute",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59733,
|
"code": 59733,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 532,
|
"order": 532,
|
||||||
@ -4032,7 +4130,7 @@
|
|||||||
"name": "pin",
|
"name": "pin",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59713,
|
"code": 59713,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 533,
|
"order": 533,
|
||||||
@ -4040,7 +4138,7 @@
|
|||||||
"name": "unpin",
|
"name": "unpin",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59734,
|
"code": 59734,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 534,
|
"order": 534,
|
||||||
@ -4048,7 +4146,7 @@
|
|||||||
"name": "smallscreen",
|
"name": "smallscreen",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59742,
|
"code": 59742,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 535,
|
"order": 535,
|
||||||
@ -4056,7 +4154,7 @@
|
|||||||
"name": "fullscreen",
|
"name": "fullscreen",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59743,
|
"code": 59743,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 536,
|
"order": 536,
|
||||||
@ -4064,7 +4162,7 @@
|
|||||||
"name": "large-pause",
|
"name": "large-pause",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59694,
|
"code": 59694,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 537,
|
"order": 537,
|
||||||
@ -4072,7 +4170,7 @@
|
|||||||
"name": "large-play",
|
"name": "large-play",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59695,
|
"code": 59695,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 538,
|
"order": 538,
|
||||||
@ -4080,7 +4178,7 @@
|
|||||||
"name": "pause",
|
"name": "pause",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59709,
|
"code": 59709,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 539,
|
"order": 539,
|
||||||
@ -4088,7 +4186,7 @@
|
|||||||
"name": "play",
|
"name": "play",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59715,
|
"code": 59715,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 540,
|
"order": 540,
|
||||||
@ -4096,7 +4194,7 @@
|
|||||||
"name": "channelviews",
|
"name": "channelviews",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59666,
|
"code": 59666,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 541,
|
"order": 541,
|
||||||
@ -4104,7 +4202,7 @@
|
|||||||
"name": "message-succeeded",
|
"name": "message-succeeded",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59648,
|
"code": 59648,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 657,
|
"order": 657,
|
||||||
@ -4112,7 +4210,7 @@
|
|||||||
"name": "message-read",
|
"name": "message-read",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59649,
|
"code": 59649,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 543,
|
"order": 543,
|
||||||
@ -4120,7 +4218,7 @@
|
|||||||
"name": "message-pending",
|
"name": "message-pending",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59724,
|
"code": 59724,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 544,
|
"order": 544,
|
||||||
@ -4128,7 +4226,7 @@
|
|||||||
"name": "message-failed",
|
"name": "message-failed",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59725,
|
"code": 59725,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 545,
|
"order": 545,
|
||||||
@ -4136,7 +4234,7 @@
|
|||||||
"name": "favorite",
|
"name": "favorite",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59710,
|
"code": 59710,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 546,
|
"order": 546,
|
||||||
@ -4144,7 +4242,7 @@
|
|||||||
"name": "keyboard",
|
"name": "keyboard",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59716,
|
"code": 59716,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 547,
|
"order": 547,
|
||||||
@ -4152,7 +4250,7 @@
|
|||||||
"name": "delete-left",
|
"name": "delete-left",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59717,
|
"code": 59717,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 548,
|
"order": 548,
|
||||||
@ -4160,7 +4258,7 @@
|
|||||||
"name": "recent",
|
"name": "recent",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59718,
|
"code": 59718,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 549,
|
"order": 549,
|
||||||
@ -4168,7 +4266,7 @@
|
|||||||
"name": "gifs",
|
"name": "gifs",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59727,
|
"code": 59727,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 550,
|
"order": 550,
|
||||||
@ -4176,7 +4274,7 @@
|
|||||||
"name": "stickers",
|
"name": "stickers",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59739,
|
"code": 59739,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 551,
|
"order": 551,
|
||||||
@ -4184,7 +4282,7 @@
|
|||||||
"name": "smile",
|
"name": "smile",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59728,
|
"code": 59728,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 552,
|
"order": 552,
|
||||||
@ -4192,7 +4290,7 @@
|
|||||||
"name": "animals",
|
"name": "animals",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59655,
|
"code": 59655,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 553,
|
"order": 553,
|
||||||
@ -4200,7 +4298,7 @@
|
|||||||
"name": "eats",
|
"name": "eats",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59682,
|
"code": 59682,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 554,
|
"order": 554,
|
||||||
@ -4208,7 +4306,7 @@
|
|||||||
"name": "sport",
|
"name": "sport",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59729,
|
"code": 59729,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 555,
|
"order": 555,
|
||||||
@ -4216,7 +4314,7 @@
|
|||||||
"name": "car",
|
"name": "car",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59664,
|
"code": 59664,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 556,
|
"order": 556,
|
||||||
@ -4224,7 +4322,7 @@
|
|||||||
"name": "lamp",
|
"name": "lamp",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59692,
|
"code": 59692,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 557,
|
"order": 557,
|
||||||
@ -4232,7 +4330,7 @@
|
|||||||
"name": "language",
|
"name": "language",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59693,
|
"code": 59693,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 558,
|
"order": 558,
|
||||||
@ -4240,7 +4338,7 @@
|
|||||||
"name": "flag",
|
"name": "flag",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59686,
|
"code": 59686,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 559,
|
"order": 559,
|
||||||
@ -4248,7 +4346,7 @@
|
|||||||
"name": "more",
|
"name": "more",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59702,
|
"code": 59702,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 560,
|
"order": 560,
|
||||||
@ -4256,7 +4354,7 @@
|
|||||||
"name": "search",
|
"name": "search",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59721,
|
"code": 59721,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 561,
|
"order": 561,
|
||||||
@ -4264,7 +4362,7 @@
|
|||||||
"name": "remove",
|
"name": "remove",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59740,
|
"code": 59740,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 562,
|
"order": 562,
|
||||||
@ -4272,7 +4370,7 @@
|
|||||||
"name": "add",
|
"name": "add",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59651,
|
"code": 59651,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 563,
|
"order": 563,
|
||||||
@ -4280,7 +4378,7 @@
|
|||||||
"name": "check",
|
"name": "check",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59668,
|
"code": 59668,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 564,
|
"order": 564,
|
||||||
@ -4288,7 +4386,7 @@
|
|||||||
"name": "close",
|
"name": "close",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59673,
|
"code": 59673,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 610,
|
"order": 610,
|
||||||
@ -4296,7 +4394,7 @@
|
|||||||
"name": "arrow-left",
|
"name": "arrow-left",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59661,
|
"code": 59661,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 566,
|
"order": 566,
|
||||||
@ -4304,7 +4402,7 @@
|
|||||||
"name": "arrow-right",
|
"name": "arrow-right",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59708,
|
"code": 59708,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 730,
|
"order": 730,
|
||||||
@ -4312,7 +4410,7 @@
|
|||||||
"name": "down",
|
"name": "down",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59680,
|
"code": 59680,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 568,
|
"order": 568,
|
||||||
@ -4320,7 +4418,7 @@
|
|||||||
"name": "up",
|
"name": "up",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59736,
|
"code": 59736,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 569,
|
"order": 569,
|
||||||
@ -4328,7 +4426,7 @@
|
|||||||
"name": "eye-closed",
|
"name": "eye-closed",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59685,
|
"code": 59685,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 570,
|
"order": 570,
|
||||||
@ -4336,7 +4434,7 @@
|
|||||||
"name": "eye",
|
"name": "eye",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59684,
|
"code": 59684,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 571,
|
"order": 571,
|
||||||
@ -4344,7 +4442,7 @@
|
|||||||
"name": "muted",
|
"name": "muted",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59741,
|
"code": 59741,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 572,
|
"order": 572,
|
||||||
@ -4352,7 +4450,7 @@
|
|||||||
"name": "avatar-archived-chats",
|
"name": "avatar-archived-chats",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59658,
|
"code": 59658,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 573,
|
"order": 573,
|
||||||
@ -4360,7 +4458,7 @@
|
|||||||
"name": "avatar-deleted-account",
|
"name": "avatar-deleted-account",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59659,
|
"code": 59659,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 747,
|
"order": 747,
|
||||||
@ -4368,7 +4466,7 @@
|
|||||||
"name": "avatar-saved-messages",
|
"name": "avatar-saved-messages",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59660,
|
"code": 59660,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"order": 575,
|
"order": 575,
|
||||||
@ -4376,7 +4474,7 @@
|
|||||||
"name": "pinned-chat",
|
"name": "pinned-chat",
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
"code": 59714,
|
"code": 59714,
|
||||||
"tempChar": ""
|
"tempChar": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"prevSize": 32,
|
"prevSize": 32,
|
||||||
|
|||||||
@ -288,6 +288,18 @@
|
|||||||
.icon-zoom-out:before {
|
.icon-zoom-out:before {
|
||||||
content: "\e975";
|
content: "\e975";
|
||||||
}
|
}
|
||||||
|
.icon-spoiler-disable:before {
|
||||||
|
content: "\e9b5";
|
||||||
|
}
|
||||||
|
.icon-grouped:before {
|
||||||
|
content: "\e9b6";
|
||||||
|
}
|
||||||
|
.icon-grouped-disable:before {
|
||||||
|
content: "\e9b7";
|
||||||
|
}
|
||||||
|
.icon-spoiler:before {
|
||||||
|
content: "\e9b8";
|
||||||
|
}
|
||||||
.icon-select:before {
|
.icon-select:before {
|
||||||
content: "\e960";
|
content: "\e960";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -127,3 +127,19 @@ export function imgToCanvas(img: HTMLImageElement) {
|
|||||||
export function hasPreview(file: File) {
|
export function hasPreview(file: File) {
|
||||||
return CONTENT_TYPES_WITH_PREVIEW.has(file.type);
|
return CONTENT_TYPES_WITH_PREVIEW.has(file.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateFiles(files: File[] | FileList | null): File[] | undefined {
|
||||||
|
if (!files?.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return Array.from(files).map(fixMovMime).filter((file) => file.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// .mov MIME type not reported sometimes https://developer.mozilla.org/en-US/docs/Web/API/File/type#sect1
|
||||||
|
function fixMovMime(file: File) {
|
||||||
|
const ext = file.name.split('.').pop()!;
|
||||||
|
if (!file.type && ext.toLowerCase() === 'mov') {
|
||||||
|
return new File([file], file.name, { type: 'video/quicktime' });
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { DEBUG, DEBUG_MORE, IS_TEST } from '../config';
|
|||||||
import { getActions } from '../global';
|
import { getActions } from '../global';
|
||||||
import { formatShareText } from './deeplink';
|
import { formatShareText } from './deeplink';
|
||||||
import { IS_ANDROID, IS_IOS, IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
import { IS_ANDROID, IS_IOS, IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
||||||
|
import { validateFiles } from './files';
|
||||||
import { notifyClientReady, playNotifySoundDebounced } from './notifications';
|
import { notifyClientReady, playNotifySoundDebounced } from './notifications';
|
||||||
|
|
||||||
type WorkerAction = {
|
type WorkerAction = {
|
||||||
@ -30,7 +31,7 @@ function handleWorkerMessage(e: MessageEvent) {
|
|||||||
case 'share':
|
case 'share':
|
||||||
dispatch.openChatWithDraft({
|
dispatch.openChatWithDraft({
|
||||||
text: formatShareText(payload.url, payload.text, payload.title),
|
text: formatShareText(payload.url, payload.text, payload.title),
|
||||||
files: payload.files,
|
files: validateFiles(payload.files),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user