Attachment Modal: Redesign (#6777)
This commit is contained in:
parent
a44ecb0113
commit
6770fff857
@ -366,6 +366,7 @@ lang('SimpleKey');
|
|||||||
|
|
||||||
// Plurals
|
// Plurals
|
||||||
lang('PluralKey', undefined, { pluralValue: 3 });
|
lang('PluralKey', undefined, { pluralValue: 3 });
|
||||||
|
lang('PluralKey', { count: 3 }, { pluralValue: 3 }); // if key has variables
|
||||||
|
|
||||||
// String replacements
|
// String replacements
|
||||||
lang('ReplKey', { name: 'Amy' });
|
lang('ReplKey', { name: 'Amy' });
|
||||||
|
|||||||
1
src/assets/font-icons/check-bold.svg
Normal file
1
src/assets/font-icons/check-bold.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M25.468 9.259a1.82 1.82 0 0 0-2.572 0l-9.623 9.625-3.26-3.26a1.82 1.82 0 0 0-2.573 2.571l4.548 4.545c.71.71 1.861.71 2.571 0l10.909-10.909a1.82 1.82 0 0 0 0-2.572"/></svg>
|
||||||
|
After Width: | Height: | Size: 243 B |
1
src/assets/font-icons/new-send.svg
Normal file
1
src/assets/font-icons/new-send.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M4.219 14.363s10.066-4.358 13.425-5.746c6.393-2.643 7.72-3.102 8.586-3.117.19-.003.616.044.892.266.233.188.297.442.327.62s.07.583.039.9c-.347 3.617-1.846 12.395-2.608 16.447-.323 1.714-.958 2.289-1.573 2.345-1.337.122-2.352-.878-3.647-1.721-2.026-1.32-3.17-2.142-5.138-3.43-2.272-1.488-.799-2.306.496-3.643.34-.35 6.23-5.674 6.343-6.157.015-.06.028-.285-.107-.404-.134-.119-.333-.078-.476-.046-.305.069-9.71 6.378-9.71 6.378s-1.379.94-2.497.916c-.822-.017-2.403-.462-3.578-.841-1.442-.466-2.588-.712-2.488-1.503.078-.618 1.713-1.264 1.713-1.264"/></svg>
|
||||||
|
After Width: | Height: | Size: 625 B |
@ -302,6 +302,7 @@
|
|||||||
"ConversationPinMessagesFor" = "Pin for me and {user}";
|
"ConversationPinMessagesFor" = "Pin for me and {user}";
|
||||||
"ConversationPinMessageAlertPinAndNotifyMembers" = "Pin and notify all members";
|
"ConversationPinMessageAlertPinAndNotifyMembers" = "Pin and notify all members";
|
||||||
"SavedMessages" = "Saved Messages";
|
"SavedMessages" = "Saved Messages";
|
||||||
|
"SavedMessagesShort" = "Saved";
|
||||||
"AccDescrPrevious" = "Previous";
|
"AccDescrPrevious" = "Previous";
|
||||||
"ReportReasonSpam" = "Spam";
|
"ReportReasonSpam" = "Spam";
|
||||||
"ReportReasonViolence" = "Violence";
|
"ReportReasonViolence" = "Violence";
|
||||||
@ -584,6 +585,8 @@
|
|||||||
"ContactShare" = "Share Contact";
|
"ContactShare" = "Share Contact";
|
||||||
"OK" = "OK";
|
"OK" = "OK";
|
||||||
"ForwardTo" = "Forward to...";
|
"ForwardTo" = "Forward to...";
|
||||||
|
"ShareWith" = "Share with";
|
||||||
|
"SelectChats" = "Select chats";
|
||||||
"AttachGame" = "Game";
|
"AttachGame" = "Game";
|
||||||
"JumpToDate" = "Jump to Date";
|
"JumpToDate" = "Jump to Date";
|
||||||
"FloodWait" = "Too many attempts, please try again later.";
|
"FloodWait" = "Too many attempts, please try again later.";
|
||||||
@ -617,6 +620,7 @@
|
|||||||
"WebAppAddToAttachmentAdd" = "Add";
|
"WebAppAddToAttachmentAdd" = "Add";
|
||||||
"AccActionDownload" = "Download";
|
"AccActionDownload" = "Download";
|
||||||
"Forward" = "Forward";
|
"Forward" = "Forward";
|
||||||
|
"ForwardForStars" = "Forward for {price}";
|
||||||
"MediaZoomOut" = "Zoom Out";
|
"MediaZoomOut" = "Zoom Out";
|
||||||
"MediaZoomIn" = "Zoom In";
|
"MediaZoomIn" = "Zoom In";
|
||||||
"PeerInfoReportProfileVideo" = "Report Profile Photo";
|
"PeerInfoReportProfileVideo" = "Report Profile Photo";
|
||||||
@ -2143,6 +2147,11 @@
|
|||||||
"ConfirmationModalPaymentForMessage_other" = "{user} charges **{amount}** per incoming message. Would you like to pay **{totalAmount}** to send **{count} messages?**";
|
"ConfirmationModalPaymentForMessage_other" = "{user} charges **{amount}** per incoming message. Would you like to pay **{totalAmount}** to send **{count} messages?**";
|
||||||
"PayForMessage_one" = "Pay for {count} message";
|
"PayForMessage_one" = "Pay for {count} message";
|
||||||
"PayForMessage_other" = "Pay for {count} messages";
|
"PayForMessage_other" = "Pay for {count} messages";
|
||||||
|
"ForwardPaidChatsConfirmation" = "{chatsSelected} {payConfirmation}";
|
||||||
|
"ForwardPaidChatsSelected_one" = "You selected **{paidChatsCount}** chat that charges Stars for messages.";
|
||||||
|
"ForwardPaidChatsSelected_other" = "You selected **{paidChatsCount}** chats that charge Stars for messages.";
|
||||||
|
"ForwardPaidChatsPayConfirmation_one" = "Would you like to pay **{totalAmount}** to send **{count}** message?";
|
||||||
|
"ForwardPaidChatsPayConfirmation_other" = "Would you like to pay **{totalAmount}** to send **{count}** messages?";
|
||||||
"MessageSentPaidToastTitle_one" = "Message sent!";
|
"MessageSentPaidToastTitle_one" = "Message sent!";
|
||||||
"MessageSentPaidToastTitle_other" = "{count} messages sent!";
|
"MessageSentPaidToastTitle_other" = "{count} messages sent!";
|
||||||
"MessageSentPaidToastText" = "You paid {amount}";
|
"MessageSentPaidToastText" = "You paid {amount}";
|
||||||
@ -2690,8 +2699,6 @@
|
|||||||
"AttachmentSendAudio_other" = "Send {count} Audios";
|
"AttachmentSendAudio_other" = "Send {count} Audios";
|
||||||
"AttachmentSendFile_one" = "Send File";
|
"AttachmentSendFile_one" = "Send File";
|
||||||
"AttachmentSendFile_other" = "Send {count} Files";
|
"AttachmentSendFile_other" = "Send {count} Files";
|
||||||
"AttachmentSendGif" = "Send GIF";
|
|
||||||
"AttachmentReplaceGif" = "Replace GIF";
|
|
||||||
"AttachmentDragAddItems" = "Add Items";
|
"AttachmentDragAddItems" = "Add Items";
|
||||||
"AttachmentCaptionPlaceholder" = "Add a caption...";
|
"AttachmentCaptionPlaceholder" = "Add a caption...";
|
||||||
"MessageSummaryTitle" = "AI Summary";
|
"MessageSummaryTitle" = "AI Summary";
|
||||||
@ -2772,6 +2779,8 @@
|
|||||||
"RankEditTextOwn" = "Share your role, title or how you're known in this group. Your tag is visible to all members.";
|
"RankEditTextOwn" = "Share your role, title or how you're known in this group. Your tag is visible to all members.";
|
||||||
"RankEditText" = "Add a short tag next to {user}'s name.";
|
"RankEditText" = "Add a short tag next to {user}'s name.";
|
||||||
"MenuAddCaption" = "Add Caption";
|
"MenuAddCaption" = "Add Caption";
|
||||||
|
"FwdMessagesToChats_one" = "Message forwarded to {count} chat";
|
||||||
|
"FwdMessagesToChats_other" = "Message forwarded to {count} chats";
|
||||||
"MenuCopyDate" = "Copy Date";
|
"MenuCopyDate" = "Copy Date";
|
||||||
"DateCopiedToast" = "Date copied to clipboard";
|
"DateCopiedToast" = "Date copied to clipboard";
|
||||||
"ReminderSetToast" = "You set up a reminder in **Saved Messages**";
|
"ReminderSetToast" = "You set up a reminder in **Saved Messages**";
|
||||||
|
|||||||
@ -896,7 +896,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#caption-input-text .placeholder-text {
|
#caption-input-text .placeholder-text {
|
||||||
bottom: 0.8125rem;
|
bottom: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#story-input-text .placeholder-text {
|
#story-input-text .placeholder-text {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import File from './File';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
document: ApiDocument;
|
document: ApiDocument;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
smaller?: boolean;
|
fileSize?: 'small' | 'medium' | 'large';
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
isSelectable?: boolean;
|
isSelectable?: boolean;
|
||||||
canAutoLoad?: boolean;
|
canAutoLoad?: boolean;
|
||||||
@ -59,7 +59,7 @@ const BYTES_PER_MB = 1024 * 1024;
|
|||||||
const Document = ({
|
const Document = ({
|
||||||
document,
|
document,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
smaller,
|
fileSize,
|
||||||
canAutoLoad,
|
canAutoLoad,
|
||||||
autoLoadFileMaxSizeMb,
|
autoLoadFileMaxSizeMb,
|
||||||
uploadProgress,
|
uploadProgress,
|
||||||
@ -201,7 +201,7 @@ const Document = ({
|
|||||||
timestamp={datetime}
|
timestamp={datetime}
|
||||||
thumbnailDataUri={thumbDataUri}
|
thumbnailDataUri={thumbDataUri}
|
||||||
previewData={localBlobUrl || previewData}
|
previewData={localBlobUrl || previewData}
|
||||||
smaller={smaller}
|
previewSize={fileSize}
|
||||||
isTransferring={isTransferring}
|
isTransferring={isTransferring}
|
||||||
isUploading={isUploading}
|
isUploading={isUploading}
|
||||||
transferProgress={transferProgress}
|
transferProgress={transferProgress}
|
||||||
|
|||||||
@ -185,7 +185,7 @@
|
|||||||
padding-inline: 0.25rem;
|
padding-inline: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.smaller {
|
&.size-small {
|
||||||
--background-color: var(--color-background);
|
--background-color: var(--color-background);
|
||||||
--border-radius-messages-small: 0.3125rem;
|
--border-radius-messages-small: 0.3125rem;
|
||||||
|
|
||||||
@ -218,6 +218,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.size-large {
|
||||||
|
.action-icon,
|
||||||
|
.file-progress,
|
||||||
|
.file-icon,
|
||||||
|
.file-preview {
|
||||||
|
width: 4.5rem;
|
||||||
|
height: 4.5rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-subtitle {
|
||||||
|
line-height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon-container {
|
||||||
|
margin-inline-end: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:dir(rtl),
|
&:dir(rtl),
|
||||||
&[dir="rtl"] {
|
&[dir="rtl"] {
|
||||||
.file-progress,
|
.file-progress,
|
||||||
|
|||||||
@ -26,6 +26,8 @@ import Icon from './icons/Icon';
|
|||||||
|
|
||||||
import './File.scss';
|
import './File.scss';
|
||||||
|
|
||||||
|
type FileSize = 'small' | 'medium' | 'large';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
ref?: ElementRef<HTMLDivElement>;
|
ref?: ElementRef<HTMLDivElement>;
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -37,7 +39,7 @@ type OwnProps = {
|
|||||||
thumbnailDataUri?: string;
|
thumbnailDataUri?: string;
|
||||||
previewData?: string;
|
previewData?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
smaller?: boolean;
|
previewSize?: FileSize;
|
||||||
isTransferring?: boolean;
|
isTransferring?: boolean;
|
||||||
isUploading?: boolean;
|
isUploading?: boolean;
|
||||||
isSelectable?: boolean;
|
isSelectable?: boolean;
|
||||||
@ -59,7 +61,7 @@ const File = ({
|
|||||||
thumbnailDataUri,
|
thumbnailDataUri,
|
||||||
previewData,
|
previewData,
|
||||||
className,
|
className,
|
||||||
smaller,
|
previewSize = 'medium',
|
||||||
isTransferring,
|
isTransferring,
|
||||||
isUploading,
|
isUploading,
|
||||||
isSelectable,
|
isSelectable,
|
||||||
@ -89,12 +91,12 @@ const File = ({
|
|||||||
|
|
||||||
const color = getColorFromExtension(extension);
|
const color = getColorFromExtension(extension);
|
||||||
|
|
||||||
const { width, height } = getDocumentThumbnailDimensions(smaller);
|
const { width, height } = getDocumentThumbnailDimensions(previewSize);
|
||||||
|
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
'File',
|
'File',
|
||||||
className,
|
className,
|
||||||
smaller && 'smaller',
|
previewSize !== 'medium' && `size-${previewSize}`,
|
||||||
onClick && !isUploading && 'interactive',
|
onClick && !isUploading && 'interactive',
|
||||||
isSelected && 'file-is-selected',
|
isSelected && 'file-is-selected',
|
||||||
);
|
);
|
||||||
@ -135,7 +137,7 @@ const File = ({
|
|||||||
<div className={buildClassName('file-progress', color, spinnerClassNames)}>
|
<div className={buildClassName('file-progress', color, spinnerClassNames)}>
|
||||||
<ProgressSpinner
|
<ProgressSpinner
|
||||||
progress={transferProgress}
|
progress={transferProgress}
|
||||||
size={smaller ? 's' : 'm'}
|
size={previewSize === 'small' ? 's' : 'm'}
|
||||||
onClick={isUploading ? onClick : undefined}
|
onClick={isUploading ? onClick : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 2rem;
|
height: var(--chip-size, 2rem);
|
||||||
margin-inline: 0.25rem;
|
margin-inline: 0.25rem;
|
||||||
padding-right: 0.75rem;
|
padding-right: 0.75rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
@ -47,12 +47,17 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
margin-inline: 0;
|
||||||
|
border-radius: 1.4375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.avatar,
|
.avatar,
|
||||||
.iconWrapper {
|
.iconWrapper {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
width: 2rem;
|
width: var(--chip-size, 2rem);
|
||||||
height: 2rem;
|
height: var(--chip-size, 2rem);
|
||||||
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
@ -102,8 +107,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
width: 2rem;
|
width: var(--chip-size, 2rem);
|
||||||
height: 2rem;
|
height: var(--chip-size, 2rem);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
@ -118,10 +123,9 @@
|
|||||||
&.squareAvatar {
|
&.squareAvatar {
|
||||||
--border-radius-forum-avatar: 0.625rem;
|
--border-radius-forum-avatar: 0.625rem;
|
||||||
|
|
||||||
border-start-start-radius: 0.625rem;
|
border-radius: 0.625rem;
|
||||||
border-end-start-radius: 0.625rem;
|
|
||||||
|
|
||||||
&.minimized, .remove {
|
.remove {
|
||||||
border-radius: 0.625rem;
|
border-radius: 0.625rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { getPeerTitle, isApiPeerChat } from '../../global/helpers/peers';
|
|||||||
import { selectPeer, selectTheme, selectUser } from '../../global/selectors';
|
import { selectPeer, selectTheme, selectUser } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildStyle from '../../util/buildStyle';
|
||||||
|
import { REM } from './helpers/mediaDimensions';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import usePeerColor from '../../hooks/usePeerColor';
|
import usePeerColor from '../../hooks/usePeerColor';
|
||||||
@ -20,10 +21,14 @@ import Icon from './icons/Icon';
|
|||||||
|
|
||||||
import styles from './PeerChip.module.scss';
|
import styles from './PeerChip.module.scss';
|
||||||
|
|
||||||
|
const CHIP_SIZE_SMALL = 1.875 * REM;
|
||||||
|
const CHIP_SIZE_MEDIUM = 2 * REM;
|
||||||
|
|
||||||
|
export type PeerChipSize = 'small' | 'medium';
|
||||||
|
|
||||||
type OwnProps<T = undefined> = {
|
type OwnProps<T = undefined> = {
|
||||||
|
|
||||||
peerId?: string;
|
peerId?: string;
|
||||||
|
size?: PeerChipSize;
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
customPeer?: CustomPeer;
|
customPeer?: CustomPeer;
|
||||||
mockPeer?: ApiPeer;
|
mockPeer?: ApiPeer;
|
||||||
@ -33,11 +38,11 @@ type OwnProps<T = undefined> = {
|
|||||||
canClose?: boolean;
|
canClose?: boolean;
|
||||||
isCloseNonDestructive?: boolean;
|
isCloseNonDestructive?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
itemClassName?: string;
|
||||||
withPeerColors?: boolean;
|
withPeerColors?: boolean;
|
||||||
withEmojiStatus?: boolean;
|
withEmojiStatus?: boolean;
|
||||||
clickArg?: T;
|
clickArg?: T;
|
||||||
onClick?: (arg: T) => void;
|
onClick?: (arg: T) => void;
|
||||||
itemClassName?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -49,6 +54,7 @@ type StateProps = {
|
|||||||
const PeerChip = <T,>({
|
const PeerChip = <T,>({
|
||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
|
size = 'medium',
|
||||||
isMinimized,
|
isMinimized,
|
||||||
canClose,
|
canClose,
|
||||||
isCloseNonDestructive,
|
isCloseNonDestructive,
|
||||||
@ -57,10 +63,10 @@ const PeerChip = <T,>({
|
|||||||
mockPeer,
|
mockPeer,
|
||||||
customPeer,
|
customPeer,
|
||||||
className,
|
className,
|
||||||
|
itemClassName,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
withPeerColors,
|
withPeerColors,
|
||||||
withEmojiStatus,
|
withEmojiStatus,
|
||||||
itemClassName,
|
|
||||||
theme,
|
theme,
|
||||||
onClick,
|
onClick,
|
||||||
}: OwnProps<T> & StateProps) => {
|
}: OwnProps<T> & StateProps) => {
|
||||||
@ -106,6 +112,7 @@ const PeerChip = <T,>({
|
|||||||
|
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
styles.root,
|
styles.root,
|
||||||
|
size === 'small' && styles.small,
|
||||||
(chat?.isForum || customPeer?.isAvatarSquare) && styles.squareAvatar,
|
(chat?.isForum || customPeer?.isAvatarSquare) && styles.squareAvatar,
|
||||||
isMinimized && styles.minimized,
|
isMinimized && styles.minimized,
|
||||||
canClose && styles.closeable,
|
canClose && styles.closeable,
|
||||||
@ -115,7 +122,10 @@ const PeerChip = <T,>({
|
|||||||
className,
|
className,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const chipSize = size === 'small' ? CHIP_SIZE_SMALL : CHIP_SIZE_MEDIUM;
|
||||||
|
|
||||||
const style = buildStyle(
|
const style = buildStyle(
|
||||||
|
`--chip-size: ${chipSize}px`,
|
||||||
withPeerColors && peerColorStyle,
|
withPeerColors && peerColorStyle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
7
src/components/common/RecipientPicker.module.scss
Normal file
7
src/components/common/RecipientPicker.module.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.recentContacts {
|
||||||
|
margin-block: 1rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { memo, useMemo, useState } from '../../lib/teact/teact';
|
import type { TeactNode } from '../../lib/teact/teact';
|
||||||
|
import { memo, useCallback, useMemo, useState } from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiChatFolder, ApiChatType } from '../../api/types';
|
import type { ApiChatFolder, ApiChatType } from '../../api/types';
|
||||||
@ -7,36 +8,51 @@ import type { ThreadId } from '../../types';
|
|||||||
import { ALL_FOLDER_ID, API_CHAT_TYPES } from '../../config';
|
import { ALL_FOLDER_ID, API_CHAT_TYPES } from '../../config';
|
||||||
import {
|
import {
|
||||||
getCanPostInChat,
|
getCanPostInChat,
|
||||||
|
getChatTitle,
|
||||||
getHasAdminRight,
|
getHasAdminRight,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isDeletedUser,
|
isDeletedUser,
|
||||||
|
isSystemBot,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import { filterPeersByQuery } from '../../global/helpers/peers';
|
import { filterPeersByQuery } from '../../global/helpers/peers';
|
||||||
import {
|
import {
|
||||||
filterChatIdsByType, selectChat, selectChatFullInfo, selectIsMonoforumAdmin, selectUser,
|
filterChatIdsByType, selectCanAnimateInterface, selectChat, selectChatFullInfo, selectIsMonoforumAdmin,
|
||||||
|
selectTopic, selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { selectCurrentLimit } from '../../global/selectors/limits';
|
import { selectCurrentLimit } from '../../global/selectors/limits';
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { unique } from '../../util/iteratees';
|
import { unique } from '../../util/iteratees';
|
||||||
import sortChatIds from './helpers/sortChatIds';
|
import sortChatIds from './helpers/sortChatIds';
|
||||||
|
|
||||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||||
import { useFolderManagerForOrderedIds } from '../../hooks/useFolderManager';
|
import { useFolderManagerForOrderedIds } from '../../hooks/useFolderManager';
|
||||||
import useFolderTabs from '../../hooks/useFolderTabs';
|
import useFolderTabs from '../../hooks/useFolderTabs';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
import { useStateRef } from '../../hooks/useStateRef';
|
||||||
|
|
||||||
import TabList from '../ui/TabList';
|
import TabList from '../ui/TabList';
|
||||||
import ChatOrUserPicker from './pickers/ChatOrUserPicker';
|
import PeerChip from './PeerChip';
|
||||||
|
import ChatOrUserPicker, { type SearchRowRenderProps } from './pickers/ChatOrUserPicker';
|
||||||
|
import PickerRecentContacts from './pickers/PickerRecentContacts';
|
||||||
|
|
||||||
|
import styles from './RecipientPicker.module.scss';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
title?: string;
|
||||||
searchPlaceholder: string;
|
searchPlaceholder: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
filter?: readonly ApiChatType[];
|
filter?: readonly ApiChatType[];
|
||||||
isLowStackPriority?: boolean;
|
isLowStackPriority?: boolean;
|
||||||
isForwarding?: boolean;
|
isForwarding?: boolean;
|
||||||
|
isMultiSelect?: boolean;
|
||||||
withFolders?: boolean;
|
withFolders?: boolean;
|
||||||
|
footer?: TeactNode;
|
||||||
|
viewportFooter?: TeactNode;
|
||||||
loadMore?: NoneToVoidFunction;
|
loadMore?: NoneToVoidFunction;
|
||||||
onSelectRecipient: (peerId: string, threadId?: ThreadId) => void;
|
onSelectRecipient: (peerId: string, threadId?: ThreadId) => void;
|
||||||
|
onSelectedIdsChange?: (ids: string[]) => void;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
@ -52,6 +68,8 @@ type StateProps = {
|
|||||||
maxFolders: number;
|
maxFolders: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RECENT_CONTACTS_LIMIT = 15;
|
||||||
|
|
||||||
const RecipientPicker = ({
|
const RecipientPicker = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
@ -60,21 +78,34 @@ const RecipientPicker = ({
|
|||||||
pinnedIds,
|
pinnedIds,
|
||||||
contactIds,
|
contactIds,
|
||||||
filter = API_CHAT_TYPES,
|
filter = API_CHAT_TYPES,
|
||||||
|
title,
|
||||||
className,
|
className,
|
||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
isLowStackPriority,
|
isLowStackPriority,
|
||||||
chatFoldersById,
|
chatFoldersById,
|
||||||
orderedFolderIds,
|
orderedFolderIds,
|
||||||
isForwarding,
|
isForwarding,
|
||||||
|
isMultiSelect,
|
||||||
maxFolders,
|
maxFolders,
|
||||||
withFolders,
|
withFolders,
|
||||||
|
footer,
|
||||||
|
viewportFooter,
|
||||||
loadMore,
|
loadMore,
|
||||||
onSelectRecipient,
|
onSelectRecipient,
|
||||||
|
onSelectedIdsChange,
|
||||||
onClose,
|
onClose,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const { openLimitReachedModal } = getActions();
|
const { openLimitReachedModal } = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||||
|
const [removingIds, setRemovingIds] = useState<string[]>([]);
|
||||||
|
const [appearingIds, setAppearingIds] = useState<string[]>([]);
|
||||||
|
const selectedIdsRef = useStateRef(selectedIds);
|
||||||
|
const removingIdsRef = useStateRef(removingIds);
|
||||||
|
const appearingIdsRef = useStateRef(appearingIds);
|
||||||
|
|
||||||
const [activeFolderIndex, setActiveFolderIndex] = useState(0);
|
const [activeFolderIndex, setActiveFolderIndex] = useState(0);
|
||||||
const { displayedFolders, folderTabs } = useFolderTabs({
|
const { displayedFolders, folderTabs } = useFolderTabs({
|
||||||
@ -85,7 +116,7 @@ const RecipientPicker = ({
|
|||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const shouldRenderFolders = withFolders && folderTabs?.length && !search;
|
const shouldRenderFolders = withFolders && folderTabs && folderTabs.length > 1 && !search;
|
||||||
const displayedFolderId = displayedFolders?.[activeFolderIndex]?.id || ALL_FOLDER_ID;
|
const displayedFolderId = displayedFolders?.[activeFolderIndex]?.id || ALL_FOLDER_ID;
|
||||||
const orderedChatIds = useFolderManagerForOrderedIds(displayedFolderId);
|
const orderedChatIds = useFolderManagerForOrderedIds(displayedFolderId);
|
||||||
|
|
||||||
@ -103,6 +134,57 @@ const RecipientPicker = ({
|
|||||||
setActiveFolderIndex(index);
|
setActiveFolderIndex(index);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updateSelectedIds = useLastCallback((newIds: string[], newlyAddedId?: string) => {
|
||||||
|
setSelectedIds(newIds);
|
||||||
|
onSelectedIdsChange?.(newIds);
|
||||||
|
|
||||||
|
if (newlyAddedId && selectCanAnimateInterface(getGlobal())) {
|
||||||
|
setAppearingIds([...appearingIdsRef.current, newlyAddedId]);
|
||||||
|
setTimeout(() => {
|
||||||
|
setAppearingIds(appearingIdsRef.current.filter((id) => id !== newlyAddedId));
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRemoveSelected = useLastCallback((selectionId: string) => {
|
||||||
|
if (removingIdsRef.current.includes(selectionId)) return;
|
||||||
|
|
||||||
|
const canAnimate = selectCanAnimateInterface(getGlobal());
|
||||||
|
if (!canAnimate) {
|
||||||
|
const newIds = selectedIdsRef.current.filter((id) => id !== selectionId);
|
||||||
|
setSelectedIds(newIds);
|
||||||
|
onSelectedIdsChange?.(newIds);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRemovingIds([...removingIdsRef.current, selectionId]);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setRemovingIds(removingIdsRef.current.filter((id) => id !== selectionId));
|
||||||
|
const newIds = selectedIdsRef.current.filter((id) => id !== selectionId);
|
||||||
|
setSelectedIds(newIds);
|
||||||
|
onSelectedIdsChange?.(newIds);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleToggleSelection = useLastCallback((peerId: string, threadId?: ThreadId) => {
|
||||||
|
const selectionId = threadId ? `${peerId}:${threadId}` : peerId;
|
||||||
|
|
||||||
|
if (selectedIds.includes(selectionId)) {
|
||||||
|
handleRemoveSelected(selectionId);
|
||||||
|
} else {
|
||||||
|
updateSelectedIds([...selectedIds, selectionId], selectionId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelect = useLastCallback((peerId: string, threadId?: ThreadId) => {
|
||||||
|
if (isMultiSelect) {
|
||||||
|
handleToggleSelection(peerId, threadId);
|
||||||
|
} else {
|
||||||
|
onSelectRecipient(peerId, threadId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const ids = useMemo(() => {
|
const ids = useMemo(() => {
|
||||||
if (!isOpen) return [];
|
if (!isOpen) return [];
|
||||||
|
|
||||||
@ -120,6 +202,8 @@ const RecipientPicker = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const peerIds = allIds.filter((id) => {
|
const peerIds = allIds.filter((id) => {
|
||||||
|
if (isSystemBot(id)) return false;
|
||||||
|
|
||||||
const chat = selectChat(global, id);
|
const chat = selectChat(global, id);
|
||||||
const user = selectUser(global, id);
|
const user = selectUser(global, id);
|
||||||
const hasAdminRights = chat && getHasAdminRight(chat, 'postMessages');
|
const hasAdminRights = chat && getHasAdminRight(chat, 'postMessages');
|
||||||
@ -171,16 +255,145 @@ const RecipientPicker = ({
|
|||||||
|
|
||||||
const renderingIds = useCurrentOrPrev(ids, true);
|
const renderingIds = useCurrentOrPrev(ids, true);
|
||||||
|
|
||||||
const chatFolders = useMemo(() => {
|
const recentContactIds = useMemo(() => {
|
||||||
if (!shouldRenderFolders) return undefined;
|
if (!contactIds) return [];
|
||||||
|
return contactIds.slice(0, RECENT_CONTACTS_LIMIT);
|
||||||
|
}, [contactIds]);
|
||||||
|
|
||||||
|
const hasSelectedChips = isMultiSelect && selectedIds.length > 0;
|
||||||
|
|
||||||
|
const parseSelectionId = useLastCallback((selectionId: string): { peerId: string; topicId?: number } => {
|
||||||
|
const colonIndex = selectionId.indexOf(':');
|
||||||
|
if (colonIndex === -1) {
|
||||||
|
return { peerId: selectionId };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
peerId: selectionId.substring(0, colonIndex),
|
||||||
|
topicId: Number(selectionId.substring(colonIndex + 1)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getChipTitle = useLastCallback((selectionId: string): string | undefined => {
|
||||||
|
const { peerId, topicId } = parseSelectionId(selectionId);
|
||||||
|
if (!topicId) return undefined;
|
||||||
|
|
||||||
|
const global = getGlobal();
|
||||||
|
const topic = selectTopic(global, peerId, topicId);
|
||||||
|
const chat = selectChat(global, peerId);
|
||||||
|
|
||||||
|
if (!topic || !chat) return undefined;
|
||||||
|
|
||||||
|
const chatTitle = getChatTitle(lang, chat);
|
||||||
|
return `${topic.title} • ${chatTitle}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderSearchRow = useCallback((props: SearchRowRenderProps) => {
|
||||||
|
if (!hasSelectedChips) {
|
||||||
|
return (
|
||||||
|
<div className="search-input-wrapper">
|
||||||
|
<i className="icon icon-search" />
|
||||||
|
<input
|
||||||
|
ref={props.inputRef}
|
||||||
|
className="search-input"
|
||||||
|
type="text"
|
||||||
|
dir="auto"
|
||||||
|
placeholder={props.placeholder}
|
||||||
|
value={props.value}
|
||||||
|
onChange={props.onChange}
|
||||||
|
onKeyDown={props.onKeyDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabList
|
<div className="search-row-with-chips">
|
||||||
tabs={folderTabs}
|
<div className="chips-and-search-scroll no-scrollbar">
|
||||||
activeTab={activeFolderIndex}
|
{selectedIds.map((selectionId) => {
|
||||||
onSwitchTab={handleSwitchFolderIndex}
|
const { peerId } = parseSelectionId(selectionId);
|
||||||
/>
|
const chipTitle = getChipTitle(selectionId);
|
||||||
|
const isAppearing = appearingIds.includes(selectionId);
|
||||||
|
const isRemoving = removingIds.includes(selectionId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={selectionId}
|
||||||
|
className={buildClassName(
|
||||||
|
'picker-chip-wrapper',
|
||||||
|
isAppearing && 'picker-chip-appear',
|
||||||
|
isRemoving && 'picker-chip-disappear',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<PeerChip
|
||||||
|
peerId={peerId}
|
||||||
|
title={chipTitle}
|
||||||
|
size="small"
|
||||||
|
forceShowSelf
|
||||||
|
canClose
|
||||||
|
className="picker-chip"
|
||||||
|
itemClassName="picker-chip-name"
|
||||||
|
clickArg={selectionId}
|
||||||
|
onClick={handleRemoveSelected}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div className="inline-search">
|
||||||
|
<i className="icon icon-search" />
|
||||||
|
<input
|
||||||
|
ref={props.inputRef}
|
||||||
|
className="search-input"
|
||||||
|
type="text"
|
||||||
|
dir="auto"
|
||||||
|
placeholder={props.placeholder}
|
||||||
|
value={props.value}
|
||||||
|
onChange={props.onChange}
|
||||||
|
onKeyDown={props.onKeyDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}, [folderTabs, activeFolderIndex, shouldRenderFolders]);
|
}, [hasSelectedChips, selectedIds, appearingIds, removingIds]);
|
||||||
|
|
||||||
|
const subheaderContent = useMemo(() => {
|
||||||
|
const hasRecentContacts = recentContactIds.length > 0 && !search;
|
||||||
|
const hasFolderTabs = shouldRenderFolders;
|
||||||
|
|
||||||
|
if (!hasRecentContacts && !hasFolderTabs) return undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{hasRecentContacts && (
|
||||||
|
<PickerRecentContacts
|
||||||
|
contactIds={recentContactIds}
|
||||||
|
currentUserId={currentUserId}
|
||||||
|
selectedIds={isMultiSelect ? selectedIds : undefined}
|
||||||
|
className={styles.recentContacts}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{Boolean(hasFolderTabs) && folderTabs && (
|
||||||
|
<TabList
|
||||||
|
tabs={folderTabs}
|
||||||
|
activeTab={activeFolderIndex}
|
||||||
|
onSwitchTab={handleSwitchFolderIndex}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
recentContactIds,
|
||||||
|
search,
|
||||||
|
shouldRenderFolders,
|
||||||
|
currentUserId,
|
||||||
|
handleSelect,
|
||||||
|
isMultiSelect,
|
||||||
|
selectedIds,
|
||||||
|
folderTabs,
|
||||||
|
activeFolderIndex,
|
||||||
|
handleSwitchFolderIndex,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatOrUserPicker
|
<ChatOrUserPicker
|
||||||
@ -188,13 +401,18 @@ const RecipientPicker = ({
|
|||||||
className={className}
|
className={className}
|
||||||
chatOrUserIds={renderingIds}
|
chatOrUserIds={renderingIds}
|
||||||
currentUserId={currentUserId}
|
currentUserId={currentUserId}
|
||||||
|
title={title}
|
||||||
searchPlaceholder={searchPlaceholder}
|
searchPlaceholder={searchPlaceholder}
|
||||||
search={search}
|
search={search}
|
||||||
subheader={chatFolders}
|
renderSearchRow={renderSearchRow}
|
||||||
|
subheader={subheaderContent}
|
||||||
|
footer={footer}
|
||||||
|
viewportFooter={viewportFooter}
|
||||||
listActiveKey={activeFolderIndex}
|
listActiveKey={activeFolderIndex}
|
||||||
|
selectedIds={isMultiSelect ? selectedIds : undefined}
|
||||||
onSearchChange={setSearch}
|
onSearchChange={setSearch}
|
||||||
loadMore={loadMore}
|
loadMore={loadMore}
|
||||||
onSelectChatOrUser={onSelectRecipient}
|
onSelectChatOrUser={handleSelect}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||||
isLowStackPriority={isLowStackPriority}
|
isLowStackPriority={isLowStackPriority}
|
||||||
|
|||||||
@ -192,18 +192,26 @@ export function getPictogramDimensions(): ApiDimensions {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDocumentThumbnailDimensions(smaller?: boolean): ApiDimensions {
|
export function getDocumentThumbnailDimensions(
|
||||||
if (smaller) {
|
size: 'small' | 'medium' | 'large' = 'medium',
|
||||||
return {
|
): ApiDimensions {
|
||||||
width: 3 * REM,
|
switch (size) {
|
||||||
height: 3 * REM,
|
case 'small':
|
||||||
};
|
return {
|
||||||
|
width: 3 * REM,
|
||||||
|
height: 3 * REM,
|
||||||
|
};
|
||||||
|
case 'large':
|
||||||
|
return {
|
||||||
|
width: 4.5 * REM,
|
||||||
|
height: 4.5 * REM,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
width: 3.375 * REM,
|
||||||
|
height: 3.375 * REM,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
width: 3.375 * REM,
|
|
||||||
height: 3.375 * REM,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStickerDimensions(sticker: ApiSticker, isMobile?: boolean): ApiDimensions {
|
export function getStickerDimensions(sticker: ApiSticker, isMobile?: boolean): ApiDimensions {
|
||||||
|
|||||||
@ -1,13 +1,49 @@
|
|||||||
@use "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
|
@keyframes pickerChipAppear {
|
||||||
|
from {
|
||||||
|
transform: scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable plugin/no-low-performance-animation-properties */
|
||||||
|
@keyframes pickerChipDisappear {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
max-width: 10rem;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(0.8);
|
||||||
|
max-width: 10rem;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(0.8);
|
||||||
|
max-width: 0;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* stylelint-enable plugin/no-low-performance-animation-properties */
|
||||||
|
|
||||||
.ChatOrUserPicker {
|
.ChatOrUserPicker {
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: 25rem;
|
|
||||||
height: 70%;
|
max-width: 26.25rem;
|
||||||
|
height: 38.75rem;
|
||||||
|
max-height: 80%;
|
||||||
|
border-radius: 2.5rem;
|
||||||
|
|
||||||
|
background-color: var(--color-background-secondary);
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
height: 90%;
|
height: 90%;
|
||||||
|
max-height: 90%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,40 +52,144 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.picker-header {
|
||||||
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
.search-wrapper {
|
&-topics {
|
||||||
display: flex;
|
flex-direction: row;
|
||||||
|
gap: 0.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.Button {
|
.picker-back-button {
|
||||||
margin-right: 0.5rem;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
.search-input-wrapper,
|
||||||
|
.search-row-with-chips {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
unicode-bidi: plaintext;
|
|
||||||
|
|
||||||
height: 2.75rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
font-size: 1.25rem;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabList {
|
.search-input-wrapper {
|
||||||
margin-bottom: -0.375rem;
|
position: relative;
|
||||||
margin-inline: -1rem;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon-search {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
left: 1rem;
|
||||||
|
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
height: 3rem;
|
||||||
|
padding: 0.5rem 0.75rem 0.5rem 3.125rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
|
background-color: var(--color-background);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chips-and-search-scroll {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 3rem;
|
||||||
|
padding: 0.5rem 0.625rem;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-chip-wrapper {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-chip-appear {
|
||||||
|
animation: pickerChipAppear 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-chip-disappear {
|
||||||
|
overflow: hidden;
|
||||||
|
animation: pickerChipDisappear 0.3s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-chip {
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 10rem;
|
||||||
|
|
||||||
|
&:not(:hover) {
|
||||||
|
background-color: var(--color-primary-opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-chip:not(:hover) .picker-chip-name {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-chip-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-search {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 10rem;
|
||||||
|
|
||||||
|
.icon-search {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
min-width: 0;
|
||||||
|
height: auto;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +197,9 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0 !important;
|
|
||||||
|
padding-block: 0 !important;
|
||||||
|
padding-inline: 1rem !important;
|
||||||
|
|
||||||
> .Transition {
|
> .Transition {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -69,10 +211,15 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-block: 0.125rem;
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-block: 0.375rem;
|
||||||
padding-inline: 0.5rem;
|
padding-inline: 0.5rem;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
background-color: var(--color-background);
|
||||||
|
|
||||||
|
@include mixins.adapt-padding-to-scrollbar(0.5rem, 0.125rem);
|
||||||
|
|
||||||
body.is-ios &,
|
body.is-ios &,
|
||||||
body.is-android & {
|
body.is-android & {
|
||||||
@ -85,6 +232,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:has(.picker-footer) {
|
||||||
|
.picker-list {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.no-results {
|
.no-results {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -97,8 +252,16 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll-container {
|
.picker-list-loading {
|
||||||
position: relative;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-list-spacer {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ChatOrUserPicker-item {
|
.ChatOrUserPicker-item {
|
||||||
@ -108,6 +271,86 @@
|
|||||||
.online {
|
.online {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
&:hover .forum-badge {
|
||||||
|
outline-color: var(--color-item-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-avatar-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forum-badge {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
right: -0.125rem;
|
||||||
|
bottom: -0.125rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 0.875rem;
|
||||||
|
height: 0.875rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
font-size: 0.625rem;
|
||||||
|
color: var(--color-white);
|
||||||
|
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
backdrop-filter: blur(50px);
|
||||||
|
/* stylelint-disable-next-line plugin/whole-pixel */
|
||||||
|
outline: 1.5px solid var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-checkbox {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 1.375rem;
|
||||||
|
height: 1.375rem;
|
||||||
|
border: 2px solid var(--color-borders-input);
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--color-white);
|
||||||
|
|
||||||
|
transition: background-color 0.15s, border-color 0.15s;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-checkbox-count {
|
||||||
|
position: absolute;
|
||||||
|
right: -0.375rem;
|
||||||
|
bottom: -0.375rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
min-width: 0.875rem;
|
||||||
|
height: 0.875rem;
|
||||||
|
border: 1px solid var(--color-white);
|
||||||
|
border-radius: 0.4375rem;
|
||||||
|
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
line-height: 0.625rem;
|
||||||
|
color: var(--color-white);
|
||||||
|
|
||||||
|
background-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-icon {
|
.topic-icon {
|
||||||
@ -143,4 +386,84 @@
|
|||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.picker-footer {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
|
||||||
|
padding-top: 1rem;
|
||||||
|
|
||||||
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
to top,
|
||||||
|
var(--color-background-secondary) 0%,
|
||||||
|
var(--color-background-secondary) 70%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-caption-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 0.1875rem;
|
||||||
|
padding-inline-start: 1rem;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-caption-input {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
height: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--color-placeholders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-footer-button {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-footer-input {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-send-button {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: auto !important;
|
||||||
|
min-width: auto !important;
|
||||||
|
height: 2.625rem;
|
||||||
|
padding: 0 1.0625rem !important;
|
||||||
|
border-radius: 1.375rem;
|
||||||
|
|
||||||
|
font-size: 2rem;
|
||||||
|
|
||||||
|
&:has(.icon-star) {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { TeactNode } from '../../../lib/teact/teact';
|
import type { ElementRef, TeactNode } from '../../../lib/teact/teact';
|
||||||
import {
|
import {
|
||||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
@ -34,14 +34,13 @@ import useLang from '../../../hooks/useLang';
|
|||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
|
||||||
import InfiniteScroll, { type OwnProps as InfiniteScrollProps } from '../../ui/InfiniteScroll';
|
import InfiniteScroll, { type OwnProps as InfiniteScrollProps } from '../../ui/InfiniteScroll';
|
||||||
import InputText from '../../ui/InputText';
|
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import Modal from '../../ui/Modal';
|
import Modal from '../../ui/Modal';
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import FullNameTitle from '../FullNameTitle';
|
import FullNameTitle from '../FullNameTitle';
|
||||||
|
import Icon from '../icons/Icon';
|
||||||
import TopicIcon from '../TopicIcon';
|
import TopicIcon from '../TopicIcon';
|
||||||
import PickerItem from './PickerItem';
|
import PickerItem from './PickerItem';
|
||||||
|
|
||||||
@ -51,12 +50,17 @@ export type OwnProps = {
|
|||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
chatOrUserIds: string[];
|
chatOrUserIds: string[];
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
title?: string;
|
||||||
searchPlaceholder: string;
|
searchPlaceholder: string;
|
||||||
search: string;
|
search: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
isLowStackPriority?: boolean;
|
isLowStackPriority?: boolean;
|
||||||
listActiveKey?: number;
|
listActiveKey?: number;
|
||||||
subheader?: TeactNode;
|
subheader?: TeactNode;
|
||||||
|
renderSearchRow?: (props: SearchRowRenderProps) => TeactNode;
|
||||||
|
footer?: TeactNode;
|
||||||
|
viewportFooter?: TeactNode;
|
||||||
|
selectedIds?: string[];
|
||||||
loadMore?: NoneToVoidFunction;
|
loadMore?: NoneToVoidFunction;
|
||||||
onSearchChange: (search: string) => void;
|
onSearchChange: (search: string) => void;
|
||||||
onSelectChatOrUser: (chatOrUserId: string, threadId?: ThreadId) => void;
|
onSelectChatOrUser: (chatOrUserId: string, threadId?: ThreadId) => void;
|
||||||
@ -75,16 +79,29 @@ const TOPIC_ICON_SIZE = 2.75 * REM;
|
|||||||
const ITEM_CLASS_NAME = 'ChatOrUserPicker-item';
|
const ITEM_CLASS_NAME = 'ChatOrUserPicker-item';
|
||||||
const TOPIC_ITEM_HEIGHT_PX = 56;
|
const TOPIC_ITEM_HEIGHT_PX = 56;
|
||||||
|
|
||||||
|
export type SearchRowRenderProps = {
|
||||||
|
inputRef: ElementRef<HTMLInputElement>;
|
||||||
|
value: string;
|
||||||
|
placeholder: string;
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
|
||||||
|
};
|
||||||
|
|
||||||
const ChatOrUserPicker = ({
|
const ChatOrUserPicker = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
chatOrUserIds,
|
chatOrUserIds,
|
||||||
|
title,
|
||||||
search,
|
search,
|
||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
className,
|
className,
|
||||||
isLowStackPriority,
|
isLowStackPriority,
|
||||||
subheader,
|
subheader,
|
||||||
|
renderSearchRow,
|
||||||
|
footer,
|
||||||
|
viewportFooter,
|
||||||
listActiveKey,
|
listActiveKey,
|
||||||
|
selectedIds,
|
||||||
animationLevel,
|
animationLevel,
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
loadMore,
|
loadMore,
|
||||||
@ -113,13 +130,13 @@ const ChatOrUserPicker = ({
|
|||||||
useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch);
|
useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch);
|
||||||
useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE);
|
useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE);
|
||||||
|
|
||||||
const selectTopicsById = useLastCallback((global: GlobalState) => {
|
const selectTopicsById = useCallback((global: GlobalState) => {
|
||||||
if (!forumId) {
|
if (!forumId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectTopics(global, forumId);
|
return selectTopics(global, forumId);
|
||||||
});
|
}, [forumId]);
|
||||||
|
|
||||||
const forumTopicsById = useSelector(selectTopicsById);
|
const forumTopicsById = useSelector(selectTopicsById);
|
||||||
|
|
||||||
@ -190,6 +207,8 @@ const ChatOrUserPicker = ({
|
|||||||
}
|
}
|
||||||
}, `.${ITEM_CLASS_NAME}`, true);
|
}, `.${ITEM_CLASS_NAME}`, true);
|
||||||
|
|
||||||
|
const isMultiSelect = Boolean(selectedIds);
|
||||||
|
|
||||||
const handleClick = useLastCallback((chatId: string) => {
|
const handleClick = useLastCallback((chatId: string) => {
|
||||||
const chatsById = getGlobal().chats.byId;
|
const chatsById = getGlobal().chats.byId;
|
||||||
const chat = chatsById?.[chatId];
|
const chat = chatsById?.[chatId];
|
||||||
@ -214,7 +233,16 @@ const ChatOrUserPicker = ({
|
|||||||
peer = monoforumChannel;
|
peer = monoforumChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chat = global.chats.byId[id];
|
||||||
|
const isForum = chat?.isForum;
|
||||||
|
|
||||||
const isSelf = peer && !isApiPeerChat(peer) ? peer.isSelf : undefined;
|
const isSelf = peer && !isApiPeerChat(peer) ? peer.isSelf : undefined;
|
||||||
|
const isSelected = selectedIds?.includes(id);
|
||||||
|
|
||||||
|
const selectedTopicsCount = isForum && selectedIds
|
||||||
|
? selectedIds.filter((selId) => selId.startsWith(`${id}:`)).length
|
||||||
|
: 0;
|
||||||
|
const hasSelectedTopics = selectedTopicsCount > 0;
|
||||||
|
|
||||||
function getSubtitle() {
|
function getSubtitle() {
|
||||||
if (!peer) return undefined;
|
if (!peer) return undefined;
|
||||||
@ -232,6 +260,15 @@ const ChatOrUserPicker = ({
|
|||||||
|
|
||||||
const [subtitle, subtitleClassName] = getSubtitle() || [];
|
const [subtitle, subtitleClassName] = getSubtitle() || [];
|
||||||
|
|
||||||
|
const checkboxElement = selectedIds ? (
|
||||||
|
<div className={buildClassName('picker-checkbox', (isSelected || hasSelectedTopics) && 'selected')}>
|
||||||
|
{(isSelected || hasSelectedTopics) && <Icon name="check-bold" />}
|
||||||
|
{hasSelectedTopics && (
|
||||||
|
<div className="picker-checkbox-count">{selectedTopicsCount}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PickerItem
|
<PickerItem
|
||||||
key={id}
|
key={id}
|
||||||
@ -247,13 +284,18 @@ const ChatOrUserPicker = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
avatarElement={(
|
avatarElement={(
|
||||||
<Avatar
|
<div className="picker-avatar-wrapper">
|
||||||
peer={peer}
|
<Avatar
|
||||||
asMessageBubble={Boolean(monoforumChannel)}
|
peer={peer}
|
||||||
isSavedMessages={isSelf}
|
asMessageBubble={Boolean(monoforumChannel)}
|
||||||
size="medium"
|
isSavedMessages={isSelf}
|
||||||
/>
|
size="medium"
|
||||||
|
/>
|
||||||
|
{isForum && <Icon name="forums" className="forum-badge" />}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
inputElement={checkboxElement}
|
||||||
|
inputPosition="end"
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
subtitleClassName={subtitleClassName}
|
subtitleClassName={subtitleClassName}
|
||||||
ripple
|
ripple
|
||||||
@ -262,29 +304,33 @@ const ChatOrUserPicker = ({
|
|||||||
onClick={() => handleClick(id)}
|
onClick={() => handleClick(id)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [currentUserId, oldLang, lang, viewportOffset]);
|
}, [currentUserId, oldLang, lang, viewportOffset, selectedIds]);
|
||||||
|
|
||||||
function renderTopicList() {
|
function renderTopicList() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="modal-header modal-header-condensed" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="picker-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
<div className="search-wrapper">
|
{renderSearchRow ? renderSearchRow({
|
||||||
<Button
|
inputRef: topicSearchRef,
|
||||||
round
|
value: topicSearch,
|
||||||
color="translucent"
|
placeholder: searchPlaceholder,
|
||||||
size="tiny"
|
onChange: handleTopicSearchChange,
|
||||||
ariaLabel={oldLang('Back')}
|
onKeyDown: handleTopicKeyDown,
|
||||||
onClick={handleHeaderBackClick}
|
}) : (
|
||||||
iconName="arrow-left"
|
<div className="search-input-wrapper">
|
||||||
/>
|
<i className="icon icon-search" />
|
||||||
<InputText
|
<input
|
||||||
ref={topicSearchRef}
|
ref={topicSearchRef}
|
||||||
value={topicSearch}
|
className="search-input"
|
||||||
onChange={handleTopicSearchChange}
|
type="text"
|
||||||
onKeyDown={handleTopicKeyDown}
|
dir="auto"
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
/>
|
value={topicSearch}
|
||||||
</div>
|
onChange={handleTopicSearchChange}
|
||||||
|
onKeyDown={handleTopicKeyDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{topicIds?.length ? (
|
{topicIds?.length ? (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
@ -295,29 +341,51 @@ const ChatOrUserPicker = ({
|
|||||||
maxHeight={(topicIds?.length || 0) * TOPIC_ITEM_HEIGHT_PX}
|
maxHeight={(topicIds?.length || 0) * TOPIC_ITEM_HEIGHT_PX}
|
||||||
onKeyDown={handleTopicKeyDown}
|
onKeyDown={handleTopicKeyDown}
|
||||||
>
|
>
|
||||||
{topicIds.map((topicId, i) => (
|
{topicIds.map((topicId, i) => {
|
||||||
<PickerItem
|
const selectionId = `${forumId}:${topicId}`;
|
||||||
key={`${forumId}_${topicId}`}
|
const isTopicSelected = selectedIds?.includes(selectionId);
|
||||||
className={ITEM_CLASS_NAME}
|
|
||||||
|
|
||||||
onClick={() => onSelectChatOrUser(forumId!, topicId)}
|
const topicCheckboxElement = isMultiSelect ? (
|
||||||
style={`top: ${i * TOPIC_ITEM_HEIGHT_PX}px;`}
|
<div className={buildClassName('picker-checkbox', isTopicSelected && 'selected')}>
|
||||||
avatarElement={(
|
{isTopicSelected && <Icon name="check-bold" />}
|
||||||
<TopicIcon
|
</div>
|
||||||
size={TOPIC_ICON_SIZE}
|
) : undefined;
|
||||||
topic={topics[topicId]}
|
|
||||||
className="topic-icon"
|
return (
|
||||||
letterClassName="topic-icon-letter"
|
<PickerItem
|
||||||
/>
|
key={`${forumId}_${topicId}`}
|
||||||
)}
|
className={ITEM_CLASS_NAME}
|
||||||
title={renderText(topics[topicId].title)}
|
onClick={() => onSelectChatOrUser(forumId!, topicId)}
|
||||||
|
style={`top: ${i * TOPIC_ITEM_HEIGHT_PX}px;`}
|
||||||
|
avatarElement={(
|
||||||
|
<div className="picker-avatar-wrapper">
|
||||||
|
<TopicIcon
|
||||||
|
size={TOPIC_ICON_SIZE}
|
||||||
|
topic={topics[topicId]}
|
||||||
|
className="topic-icon"
|
||||||
|
letterClassName="topic-icon-letter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
title={renderText(topics[topicId].title)}
|
||||||
|
inputElement={topicCheckboxElement}
|
||||||
|
inputPosition="end"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{Boolean(viewportFooter) && (
|
||||||
|
<div
|
||||||
|
className="picker-list-spacer"
|
||||||
|
style={`top: ${topicIds.length * TOPIC_ITEM_HEIGHT_PX}px`}
|
||||||
/>
|
/>
|
||||||
))}
|
)}
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
) : topicIds && !topicIds.length ? (
|
) : topicIds && !topicIds.length ? (
|
||||||
<p className="no-results">{lang('NothingFound')}</p>
|
<p className="no-results">{lang('NothingFound')}</p>
|
||||||
) : (
|
) : (
|
||||||
<Loading />
|
<div className="picker-list picker-list-loading">
|
||||||
|
<Loading />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -326,24 +394,28 @@ const ChatOrUserPicker = ({
|
|||||||
function renderChatList() {
|
function renderChatList() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="modal-header modal-header-condensed" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="picker-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
<div className="search-wrapper">
|
{renderSearchRow ? renderSearchRow({
|
||||||
<Button
|
inputRef: searchRef,
|
||||||
round
|
value: search,
|
||||||
color="translucent"
|
placeholder: searchPlaceholder,
|
||||||
size="tiny"
|
onChange: handleSearchChange,
|
||||||
ariaLabel={oldLang('Close')}
|
onKeyDown: chatKeyDownHandler,
|
||||||
onClick={onClose}
|
}) : (
|
||||||
iconName="close"
|
<div className="search-input-wrapper">
|
||||||
/>
|
<i className="icon icon-search" />
|
||||||
<InputText
|
<input
|
||||||
ref={searchRef}
|
ref={searchRef}
|
||||||
value={search}
|
className="search-input"
|
||||||
onChange={handleSearchChange}
|
type="text"
|
||||||
onKeyDown={chatKeyDownHandler}
|
dir="auto"
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
/>
|
value={search}
|
||||||
</div>
|
onChange={handleSearchChange}
|
||||||
|
onKeyDown={chatKeyDownHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{subheader}
|
{subheader}
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
@ -355,6 +427,7 @@ const ChatOrUserPicker = ({
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
viewportIds={viewportIds}
|
viewportIds={viewportIds}
|
||||||
maxHeight={chatOrUserIds.length * PEER_PICKER_ITEM_HEIGHT_PX}
|
maxHeight={chatOrUserIds.length * PEER_PICKER_ITEM_HEIGHT_PX}
|
||||||
|
viewportFooter={viewportFooter}
|
||||||
onLoadMore={getMore}
|
onLoadMore={getMore}
|
||||||
onSelect={handleChatSelect}
|
onSelect={handleChatSelect}
|
||||||
renderItem={renderChatItem}
|
renderItem={renderChatItem}
|
||||||
@ -365,12 +438,24 @@ const ChatOrUserPicker = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleModalClose = useLastCallback(() => {
|
||||||
|
if (forumId) {
|
||||||
|
handleHeaderBackClick();
|
||||||
|
} else {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
title={title}
|
||||||
|
hasCloseButton
|
||||||
|
isBackButton={Boolean(forumId)}
|
||||||
|
headerClassName="modal-header-condensed-wide"
|
||||||
className={buildClassName('ChatOrUserPicker', className)}
|
className={buildClassName('ChatOrUserPicker', className)}
|
||||||
isLowStackPriority={isLowStackPriority}
|
isLowStackPriority={isLowStackPriority}
|
||||||
onClose={onClose}
|
onClose={handleModalClose}
|
||||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||||
>
|
>
|
||||||
<Transition activeKey={activeKey} name="slideFade" slideClassName="ChatOrUserPicker_slide">
|
<Transition activeKey={activeKey} name="slideFade" slideClassName="ChatOrUserPicker_slide">
|
||||||
@ -378,6 +463,7 @@ const ChatOrUserPicker = ({
|
|||||||
return activeKey === TOPIC_LIST_SLIDE ? renderTopicList() : renderChatList();
|
return activeKey === TOPIC_LIST_SLIDE ? renderTopicList() : renderChatList();
|
||||||
}}
|
}}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
{footer}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -386,6 +472,7 @@ type ChatListContentProps = {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
viewportIds?: string[];
|
viewportIds?: string[];
|
||||||
maxHeight: number;
|
maxHeight: number;
|
||||||
|
viewportFooter?: TeactNode;
|
||||||
onLoadMore: InfiniteScrollProps['onLoadMore'];
|
onLoadMore: InfiniteScrollProps['onLoadMore'];
|
||||||
onSelect: (index: number) => void;
|
onSelect: (index: number) => void;
|
||||||
renderItem: (id: string, index: number) => TeactNode;
|
renderItem: (id: string, index: number) => TeactNode;
|
||||||
@ -396,6 +483,7 @@ function ChatListContent({
|
|||||||
isOpen,
|
isOpen,
|
||||||
viewportIds,
|
viewportIds,
|
||||||
maxHeight,
|
maxHeight,
|
||||||
|
viewportFooter,
|
||||||
onLoadMore,
|
onLoadMore,
|
||||||
onSelect,
|
onSelect,
|
||||||
onKeyDownHandlerUpdate,
|
onKeyDownHandlerUpdate,
|
||||||
@ -424,6 +512,9 @@ function ChatListContent({
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
{viewportIds.map(renderItem)}
|
{viewportIds.map(renderItem)}
|
||||||
|
{Boolean(viewportFooter) && (
|
||||||
|
<div className="picker-list-spacer" style={`top: ${maxHeight}px`} />
|
||||||
|
)}
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
) : viewportIds && !viewportIds.length ? (
|
) : viewportIds && !viewportIds.length ? (
|
||||||
<p className="no-results">{lang('NothingFound')}</p>
|
<p className="no-results">{lang('NothingFound')}</p>
|
||||||
|
|||||||
110
src/components/common/pickers/PickerRecentContacts.module.scss
Normal file
110
src/components/common/pickers/PickerRecentContacts.module.scss
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
.root {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
padding: 0.3125rem 0;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollContainer {
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
padding-inline: 0.5625rem;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
min-width: 3.375rem;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.375rem;
|
||||||
|
padding-inline: 0.25rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
|
||||||
|
transition: background-color 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-chat-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--color-item-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarWrapper {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 0.3125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
right: -0.125rem;
|
||||||
|
bottom: -0.125rem;
|
||||||
|
transform: scale(0);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
border: 2px solid var(--color-background);
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-white);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
|
||||||
|
transition: transform 0.15s, opacity 0.15s, border-color 0.15s;
|
||||||
|
|
||||||
|
.item:hover & {
|
||||||
|
border-color: var(--color-item-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
transition: color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
.name {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/components/common/pickers/PickerRecentContacts.tsx
Normal file
88
src/components/common/pickers/PickerRecentContacts.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { memo, useRef } from '../../../lib/teact/teact';
|
||||||
|
import { getGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import { getPeerTitle } from '../../../global/helpers/peers';
|
||||||
|
import { selectPeer } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import renderText from '../helpers/renderText';
|
||||||
|
|
||||||
|
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import Avatar from '../Avatar';
|
||||||
|
import Icon from '../icons/Icon';
|
||||||
|
|
||||||
|
import styles from './PickerRecentContacts.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
contactIds: string[];
|
||||||
|
currentUserId?: string;
|
||||||
|
selectedIds?: string[];
|
||||||
|
className?: string;
|
||||||
|
onSelect: (id: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PickerRecentContacts = ({
|
||||||
|
contactIds,
|
||||||
|
currentUserId,
|
||||||
|
selectedIds,
|
||||||
|
className,
|
||||||
|
onSelect,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const lang = useLang();
|
||||||
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
useHorizontalScroll(containerRef, !contactIds.length);
|
||||||
|
|
||||||
|
const handleClick = useLastCallback((id: string) => {
|
||||||
|
onSelect(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Current user (Saved Messages) goes first, then contacts
|
||||||
|
const displayIds = currentUserId
|
||||||
|
? [currentUserId, ...contactIds.filter((id) => id !== currentUserId)]
|
||||||
|
: contactIds;
|
||||||
|
|
||||||
|
if (!displayIds.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={buildClassName(styles.root, className)} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
<div ref={containerRef} className={styles.scrollContainer}>
|
||||||
|
{displayIds.map((peerId) => {
|
||||||
|
const global = getGlobal();
|
||||||
|
const peer = selectPeer(global, peerId);
|
||||||
|
if (!peer) return undefined;
|
||||||
|
|
||||||
|
const isSelf = peerId === currentUserId;
|
||||||
|
const isSelected = selectedIds?.includes(peerId);
|
||||||
|
const name = isSelf ? lang('SavedMessagesShort') : getPeerTitle(lang, peer);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={peerId}
|
||||||
|
className={buildClassName(styles.item, isSelected && styles.selected)}
|
||||||
|
onClick={() => handleClick(peerId)}
|
||||||
|
>
|
||||||
|
<div className={styles.avatarWrapper}>
|
||||||
|
<Avatar
|
||||||
|
peer={peer}
|
||||||
|
isSavedMessages={isSelf}
|
||||||
|
size={48}
|
||||||
|
/>
|
||||||
|
<div className={styles.checkmark}>
|
||||||
|
<Icon name="check-bold" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.name}>{renderText(name || lang('ActionFallbackSomeone'))}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(PickerRecentContacts);
|
||||||
@ -26,7 +26,7 @@ import useScrolledState from '../../../hooks/useScrolledState';
|
|||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
|
||||||
import StoryRibbon from '../../story/StoryRibbon';
|
import StoryRibbon from '../../story/StoryRibbon';
|
||||||
import TabList from '../../ui/TabList';
|
import SquareTabList from '../../ui/SquareTabList';
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import ChatList from './ChatList';
|
import ChatList from './ChatList';
|
||||||
|
|
||||||
@ -259,7 +259,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
{shouldRenderStoryRibbon && <StoryRibbon isClosing={isStoryRibbonClosing} />}
|
{shouldRenderStoryRibbon && <StoryRibbon isClosing={isStoryRibbonClosing} />}
|
||||||
{shouldRenderFolders ? (
|
{shouldRenderFolders ? (
|
||||||
<TabList
|
<SquareTabList
|
||||||
contextRootElementSelector="#LeftColumn"
|
contextRootElementSelector="#LeftColumn"
|
||||||
tabs={folderTabs}
|
tabs={folderTabs}
|
||||||
activeTab={activeChatFolder}
|
activeTab={activeChatFolder}
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabList {
|
.SquareTabList {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@ -48,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--tabs-hidden .TabList {
|
&--tabs-hidden .SquareTabList {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -112,7 +112,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
|||||||
document={getMessageDocument(message)!}
|
document={getMessageDocument(message)!}
|
||||||
message={message}
|
message={message}
|
||||||
datetime={message.date}
|
datetime={message.date}
|
||||||
smaller
|
fileSize="small"
|
||||||
sender={getSenderName(lang, message, chatsById, usersById)}
|
sender={getSenderName(lang, message, chatsById, usersById)}
|
||||||
className="scroll-item"
|
className="scroll-item"
|
||||||
isDownloading={getIsDownloading(activeDownloads, message.content.document!)}
|
isDownloading={getIsDownloading(activeDownloads, message.content.document!)}
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabList {
|
.SquareTabList {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation'
|
|||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import TabList from '../../ui/TabList';
|
import SquareTabList from '../../ui/SquareTabList';
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import AudioResults from './AudioResults';
|
import AudioResults from './AudioResults';
|
||||||
import BotAppResults from './BotAppResults';
|
import BotAppResults from './BotAppResults';
|
||||||
@ -122,7 +122,7 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="LeftSearch" ref={containerRef} onKeyDown={handleKeyDown}>
|
<div className="LeftSearch" ref={containerRef} onKeyDown={handleKeyDown}>
|
||||||
<TabList activeTab={activeTab} tabs={tabs} onSwitchTab={handleSwitchTab} />
|
<SquareTabList activeTab={activeTab} tabs={tabs} onSwitchTab={handleSwitchTab} />
|
||||||
<Transition
|
<Transition
|
||||||
name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
|
name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
|
||||||
renderCount={tabs.length}
|
renderCount={tabs.length}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { filterPeersByQuery } from '../../../global/helpers/peers';
|
|||||||
import { selectTabState } from '../../../global/selectors';
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
|
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import ChatOrUserPicker from '../../common/pickers/ChatOrUserPicker';
|
import ChatOrUserPicker from '../../common/pickers/ChatOrUserPicker';
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
|
|||||||
blockUser,
|
blockUser,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useLang();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -76,7 +76,8 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
|
|||||||
<ChatOrUserPicker
|
<ChatOrUserPicker
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
chatOrUserIds={filteredContactIds}
|
chatOrUserIds={filteredContactIds}
|
||||||
searchPlaceholder={lang('BlockedUsers.BlockUser')}
|
title={lang('BlockedUsersBlockUser')}
|
||||||
|
searchPlaceholder={lang('Search')}
|
||||||
search={search}
|
search={search}
|
||||||
onSearchChange={setSearch}
|
onSearchChange={setSearch}
|
||||||
onSelectChatOrUser={handleRemoveUser}
|
onSelectChatOrUser={handleRemoveUser}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { getActions } from '../../global';
|
|||||||
import type { TabState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import RecipientPicker from '../common/RecipientPicker';
|
import RecipientPicker from '../common/RecipientPicker';
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ const AttachBotRecipientPicker: FC<OwnProps> = ({
|
|||||||
requestedAttachBotInChat,
|
requestedAttachBotInChat,
|
||||||
}) => {
|
}) => {
|
||||||
const { cancelAttachBotInChat, callAttachBot } = getActions();
|
const { cancelAttachBotInChat, callAttachBot } = getActions();
|
||||||
const lang = useOldLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const isOpen = Boolean(requestedAttachBotInChat);
|
const isOpen = Boolean(requestedAttachBotInChat);
|
||||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||||
@ -41,6 +41,7 @@ const AttachBotRecipientPicker: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<RecipientPicker
|
<RecipientPicker
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
title={lang('SelectChat')}
|
||||||
searchPlaceholder={lang('Search')}
|
searchPlaceholder={lang('Search')}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onSelectRecipient={handlePeerRecipient}
|
onSelectRecipient={handlePeerRecipient}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import type { TabState } from '../../global/types';
|
|||||||
import type { ThreadId } from '../../types';
|
import type { ThreadId } from '../../types';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import RecipientPicker from '../common/RecipientPicker';
|
import RecipientPicker from '../common/RecipientPicker';
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ const DraftRecipientPicker: FC<OwnProps> = ({
|
|||||||
resetOpenChatWithDraft,
|
resetOpenChatWithDraft,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -54,7 +54,8 @@ const DraftRecipientPicker: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<RecipientPicker
|
<RecipientPicker
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
searchPlaceholder={lang('ForwardTo')}
|
title={lang('ShareWith')}
|
||||||
|
searchPlaceholder={lang('Search')}
|
||||||
filter={requestedDraft?.filter}
|
filter={requestedDraft?.filter}
|
||||||
onSelectRecipient={handleSelectRecipient}
|
onSelectRecipient={handleSelectRecipient}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
|
|||||||
29
src/components/main/ForwardRecipientPicker.module.scss
Normal file
29
src/components/main/ForwardRecipientPicker.module.scss
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.buttonLayer {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composerLayer {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible {
|
||||||
|
pointer-events: auto;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonSlide {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import {
|
import {
|
||||||
memo, useCallback, useEffect,
|
memo, useCallback, useEffect, useMemo, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -8,17 +8,33 @@ import type { ThreadId } from '../../types';
|
|||||||
|
|
||||||
import { getChatTitle, getUserFirstOrLastName } from '../../global/helpers';
|
import { getChatTitle, getUserFirstOrLastName } from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
|
selectCanCopyMessageLink,
|
||||||
selectChat,
|
selectChat,
|
||||||
|
selectChatMessages,
|
||||||
|
selectPeerPaidMessagesStars,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { isUserId } from '../../util/entities/ids';
|
import { isUserId } from '../../util/entities/ids';
|
||||||
|
import { formatStarsAsIcon, formatStarsAsText } from '../../util/localization/format';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
import useFrozenProps from '../../hooks/useFrozenProps';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||||
|
|
||||||
|
import AnimatedCounter from '../common/AnimatedCounter';
|
||||||
|
import Icon from '../common/icons/Icon';
|
||||||
import RecipientPicker from '../common/RecipientPicker';
|
import RecipientPicker from '../common/RecipientPicker';
|
||||||
|
import Button from '../ui/Button';
|
||||||
|
import Checkbox from '../ui/Checkbox';
|
||||||
|
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||||
|
import Transition from '../ui/Transition';
|
||||||
|
|
||||||
|
import styles from './ForwardRecipientPicker.module.scss';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -29,6 +45,9 @@ interface StateProps {
|
|||||||
isManyMessages?: boolean;
|
isManyMessages?: boolean;
|
||||||
isStory?: boolean;
|
isStory?: boolean;
|
||||||
isForwarding?: boolean;
|
isForwarding?: boolean;
|
||||||
|
fromChatId?: string;
|
||||||
|
forwardMessageIds?: number[];
|
||||||
|
shouldPaidMessageAutoApprove?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||||
@ -37,26 +56,80 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
isManyMessages,
|
isManyMessages,
|
||||||
isStory,
|
isStory,
|
||||||
isForwarding,
|
isForwarding,
|
||||||
|
fromChatId,
|
||||||
|
forwardMessageIds,
|
||||||
|
shouldPaidMessageAutoApprove,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChatOrTopicWithReplyInDraft,
|
openChatOrTopicWithReplyInDraft,
|
||||||
setForwardChatOrTopic,
|
setForwardChatOrTopic,
|
||||||
exitForwardMode,
|
exitForwardMode,
|
||||||
forwardToSavedMessages,
|
forwardToSavedMessages,
|
||||||
|
forwardToMultipleChats,
|
||||||
forwardStory,
|
forwardStory,
|
||||||
showNotification,
|
showNotification,
|
||||||
|
copyMessageLink,
|
||||||
|
openStarsBalanceModal,
|
||||||
|
setPaidMessageAutoApprove,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useLang();
|
||||||
|
const oldLang = useOldLang();
|
||||||
|
|
||||||
const renderingIsStory = usePreviousDeprecated(isStory, true);
|
const renderingIsStory = usePreviousDeprecated(isStory, true);
|
||||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||||
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||||
|
const [caption, setCaption] = useState('');
|
||||||
|
const [isPaymentConfirmOpen, openPaymentConfirm, closePaymentConfirm] = useFlag();
|
||||||
|
const [shouldAutoApprove, setShouldAutoApprove] = useState(shouldPaidMessageAutoApprove);
|
||||||
|
|
||||||
|
const isMultiSelect = isForwarding && !isStory;
|
||||||
|
const messageCount = forwardMessageIds?.length || 0;
|
||||||
|
|
||||||
|
const paidChatsInfo = useMemo(() => {
|
||||||
|
if (!selectedIds.length) return { paidChatsCount: 0, totalStars: 0, totalMessages: 0 };
|
||||||
|
|
||||||
|
const global = getGlobal();
|
||||||
|
let paidChatsCount = 0;
|
||||||
|
let totalStars = 0;
|
||||||
|
const hasCaption = caption.trim().length > 0;
|
||||||
|
const totalMessages = messageCount + (hasCaption ? 1 : 0);
|
||||||
|
|
||||||
|
for (const chatId of selectedIds) {
|
||||||
|
const paidStars = selectPeerPaidMessagesStars(global, chatId);
|
||||||
|
if (paidStars) {
|
||||||
|
paidChatsCount++;
|
||||||
|
totalStars += paidStars * totalMessages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { paidChatsCount, totalStars, totalMessages };
|
||||||
|
}, [selectedIds, messageCount, caption]);
|
||||||
|
|
||||||
|
const canCopyLink = useMemo(() => {
|
||||||
|
if (!fromChatId || forwardMessageIds?.length !== 1) return false;
|
||||||
|
|
||||||
|
const global = getGlobal();
|
||||||
|
const chatMessages = selectChatMessages(global, fromChatId);
|
||||||
|
if (!chatMessages) return false;
|
||||||
|
|
||||||
|
const message = chatMessages[forwardMessageIds[0]];
|
||||||
|
return message && selectCanCopyMessageLink(global, message);
|
||||||
|
}, [fromChatId, forwardMessageIds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
markIsShown();
|
markIsShown();
|
||||||
}
|
}
|
||||||
}, [isOpen, markIsShown]);
|
}, [isOpen, markIsShown]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setSelectedIds([]);
|
||||||
|
setCaption('');
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
const handleSelectRecipient = useCallback((recipientId: string, threadId?: ThreadId) => {
|
const handleSelectRecipient = useCallback((recipientId: string, threadId?: ThreadId) => {
|
||||||
const isSelf = recipientId === currentUserId;
|
const isSelf = recipientId === currentUserId;
|
||||||
if (isStory) {
|
if (isStory) {
|
||||||
@ -65,8 +138,8 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
if (isUserId(recipientId)) {
|
if (isUserId(recipientId)) {
|
||||||
showNotification({
|
showNotification({
|
||||||
message: isSelf
|
message: isSelf
|
||||||
? lang('Conversation.StoryForwardTooltip.SavedMessages.One')
|
? oldLang('Conversation.StoryForwardTooltip.SavedMessages.One')
|
||||||
: lang(
|
: oldLang(
|
||||||
'StorySharedTo',
|
'StorySharedTo',
|
||||||
getUserFirstOrLastName(selectUser(global, recipientId)),
|
getUserFirstOrLastName(selectUser(global, recipientId)),
|
||||||
),
|
),
|
||||||
@ -76,14 +149,14 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
showNotification({
|
showNotification({
|
||||||
message: lang('StorySharedTo', getChatTitle(lang, chat)),
|
message: oldLang('StorySharedTo', getChatTitle(oldLang, chat)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelf) {
|
if (isSelf) {
|
||||||
const message = lang(
|
const message = oldLang(
|
||||||
isManyMessages
|
isManyMessages
|
||||||
? 'Conversation.ForwardTooltip.SavedMessages.Many'
|
? 'Conversation.ForwardTooltip.SavedMessages.Many'
|
||||||
: 'Conversation.ForwardTooltip.SavedMessages.One',
|
: 'Conversation.ForwardTooltip.SavedMessages.One',
|
||||||
@ -100,37 +173,239 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
openChatOrTopicWithReplyInDraft({ chatId, topicId });
|
openChatOrTopicWithReplyInDraft({ chatId, topicId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [currentUserId, isManyMessages, isStory, lang, isForwarding]);
|
}, [currentUserId, isManyMessages, isStory, oldLang, isForwarding]);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
exitForwardMode();
|
exitForwardMode();
|
||||||
}, [exitForwardMode]);
|
}, [exitForwardMode]);
|
||||||
|
|
||||||
|
const handleSelectedIdsChange = useLastCallback((ids: string[]) => {
|
||||||
|
setSelectedIds(ids);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCopyLink = useLastCallback(() => {
|
||||||
|
if (!fromChatId || !forwardMessageIds?.length) return;
|
||||||
|
copyMessageLink({
|
||||||
|
chatId: fromChatId,
|
||||||
|
messageId: forwardMessageIds[0],
|
||||||
|
});
|
||||||
|
exitForwardMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleForwardToMultiple = useLastCallback(() => {
|
||||||
|
if (!selectedIds.length) return;
|
||||||
|
|
||||||
|
if (selectedIds.length === 1) {
|
||||||
|
setForwardChatOrTopic({ chatId: selectedIds[0] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paidChatsInfo.totalStars > 0 && !shouldPaidMessageAutoApprove) {
|
||||||
|
openPaymentConfirm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paidChatsInfo.totalStars > 0) {
|
||||||
|
const starsBalance = getGlobal().stars?.balance?.amount || 0;
|
||||||
|
if (paidChatsInfo.totalStars > starsBalance) {
|
||||||
|
openStarsBalanceModal({
|
||||||
|
topup: {
|
||||||
|
balanceNeeded: paidChatsInfo.totalStars,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeForward();
|
||||||
|
});
|
||||||
|
|
||||||
|
const executeForward = useLastCallback(() => {
|
||||||
|
forwardToMultipleChats({ toChatIds: selectedIds, comment: caption || undefined });
|
||||||
|
|
||||||
|
showNotification({
|
||||||
|
message: lang('FwdMessagesToChats', { count: selectedIds.length }, { pluralValue: selectedIds.length }),
|
||||||
|
});
|
||||||
|
exitForwardMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePaymentConfirm = useLastCallback(() => {
|
||||||
|
const { totalStars } = paidChatsInfo;
|
||||||
|
const starsBalance = getGlobal().stars?.balance?.amount || 0;
|
||||||
|
|
||||||
|
if (totalStars > starsBalance) {
|
||||||
|
openStarsBalanceModal({
|
||||||
|
topup: {
|
||||||
|
balanceNeeded: totalStars,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closePaymentConfirm();
|
||||||
|
if (shouldAutoApprove) {
|
||||||
|
setPaidMessageAutoApprove();
|
||||||
|
}
|
||||||
|
executeForward();
|
||||||
|
});
|
||||||
|
|
||||||
|
const viewportFooter = useMemo(() => (
|
||||||
|
<div className="picker-list-spacer" />
|
||||||
|
), []);
|
||||||
|
|
||||||
|
const selectedCount = selectedIds.length;
|
||||||
|
const showComposer = selectedCount >= 2;
|
||||||
|
const { totalStars: displayedTotalStars } = useFrozenProps(
|
||||||
|
{ totalStars: paidChatsInfo.totalStars },
|
||||||
|
!showComposer,
|
||||||
|
);
|
||||||
|
|
||||||
|
const footerContent = useMemo(() => {
|
||||||
|
if (!isForwarding || isStory) return undefined;
|
||||||
|
|
||||||
|
const renderButton = () => {
|
||||||
|
const isInitial = selectedCount === 0;
|
||||||
|
const singleChatStars = selectedCount === 1 ? paidChatsInfo.totalStars : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className="picker-footer-button"
|
||||||
|
color="primary"
|
||||||
|
disabled={isInitial && !canCopyLink}
|
||||||
|
onClick={isInitial ? handleCopyLink : handleForwardToMultiple}
|
||||||
|
>
|
||||||
|
<Transition name="fade" activeKey={isInitial ? 0 : 1} slideClassName={styles.buttonSlide}>
|
||||||
|
<span>
|
||||||
|
{isInitial
|
||||||
|
? (canCopyLink ? oldLang('CopyLink') : lang('SelectChats'))
|
||||||
|
: (singleChatStars > 0
|
||||||
|
? lang(
|
||||||
|
'ForwardForStars',
|
||||||
|
{ price: formatStarsAsIcon(lang, singleChatStars, { asFont: true }) },
|
||||||
|
{ withNodes: true },
|
||||||
|
)
|
||||||
|
: lang('Forward'))}
|
||||||
|
</span>
|
||||||
|
</Transition>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderComposer = () => (
|
||||||
|
<div className="picker-footer-input">
|
||||||
|
<div className="picker-caption-wrapper">
|
||||||
|
<input
|
||||||
|
className="picker-caption-input"
|
||||||
|
type="text"
|
||||||
|
value={caption}
|
||||||
|
onChange={(e) => setCaption(e.currentTarget.value)}
|
||||||
|
placeholder={lang('AttachmentCaptionPlaceholder')}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className="picker-send-button"
|
||||||
|
color="primary"
|
||||||
|
onClick={handleForwardToMultiple}
|
||||||
|
ariaLabel={lang('Forward')}
|
||||||
|
>
|
||||||
|
{displayedTotalStars > 0 ? (
|
||||||
|
<>
|
||||||
|
<Icon name="star" className="star-icon" />
|
||||||
|
<AnimatedCounter text={String(displayedTotalStars)} />
|
||||||
|
</>
|
||||||
|
) : <i className="icon icon-new-send" />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="picker-footer">
|
||||||
|
<div className={buildClassName(styles.buttonLayer, !showComposer && styles.visible)}>
|
||||||
|
{renderButton()}
|
||||||
|
</div>
|
||||||
|
<div className={buildClassName(styles.composerLayer, showComposer && styles.visible)}>
|
||||||
|
{renderComposer()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [isForwarding, isStory, selectedCount, showComposer, caption, canCopyLink, displayedTotalStars,
|
||||||
|
paidChatsInfo, handleForwardToMultiple, handleCopyLink, lang, oldLang]);
|
||||||
|
|
||||||
if (!isOpen && !isShown) {
|
if (!isOpen && !isShown) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const confirmPaymentMessage = paidChatsInfo.totalStars > 0 ? lang(
|
||||||
|
'ForwardPaidChatsConfirmation',
|
||||||
|
{
|
||||||
|
chatsSelected: lang(
|
||||||
|
'ForwardPaidChatsSelected',
|
||||||
|
{ paidChatsCount: paidChatsInfo.paidChatsCount },
|
||||||
|
{ withNodes: true, withMarkdown: true, pluralValue: paidChatsInfo.paidChatsCount },
|
||||||
|
),
|
||||||
|
payConfirmation: lang(
|
||||||
|
'ForwardPaidChatsPayConfirmation',
|
||||||
|
{
|
||||||
|
totalAmount: formatStarsAsText(lang, paidChatsInfo.totalStars),
|
||||||
|
count: paidChatsInfo.totalMessages,
|
||||||
|
},
|
||||||
|
{ withNodes: true, withMarkdown: true, pluralValue: paidChatsInfo.totalMessages },
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ withNodes: true },
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
|
const confirmLabel = lang('PayForMessage', { count: paidChatsInfo.totalMessages }, {
|
||||||
|
withNodes: true,
|
||||||
|
pluralValue: paidChatsInfo.totalMessages,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecipientPicker
|
<>
|
||||||
isOpen={isOpen}
|
<RecipientPicker
|
||||||
className={renderingIsStory ? 'component-theme-dark' : undefined}
|
isOpen={isOpen}
|
||||||
searchPlaceholder={lang(isForwarding ? 'ForwardTo' : 'ReplyToDialog')}
|
className={renderingIsStory ? 'component-theme-dark' : undefined}
|
||||||
onSelectRecipient={handleSelectRecipient}
|
title={lang('ShareWith')}
|
||||||
onClose={handleClose}
|
searchPlaceholder={lang('Search')}
|
||||||
onCloseAnimationEnd={unmarkIsShown}
|
isMultiSelect={isMultiSelect}
|
||||||
isForwarding={isForwarding}
|
footer={footerContent}
|
||||||
withFolders
|
viewportFooter={viewportFooter}
|
||||||
/>
|
onSelectRecipient={handleSelectRecipient}
|
||||||
|
onSelectedIdsChange={handleSelectedIdsChange}
|
||||||
|
onClose={handleClose}
|
||||||
|
onCloseAnimationEnd={unmarkIsShown}
|
||||||
|
isForwarding={isForwarding}
|
||||||
|
withFolders
|
||||||
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
title={lang('TitleConfirmPayment')}
|
||||||
|
confirmLabel={confirmLabel}
|
||||||
|
isOpen={isPaymentConfirmOpen}
|
||||||
|
onClose={closePaymentConfirm}
|
||||||
|
confirmHandler={handlePaymentConfirm}
|
||||||
|
>
|
||||||
|
{confirmPaymentMessage}
|
||||||
|
<Checkbox
|
||||||
|
label={lang('DoNotAskAgain')}
|
||||||
|
checked={shouldAutoApprove}
|
||||||
|
onCheck={setShouldAutoApprove}
|
||||||
|
/>
|
||||||
|
</ConfirmDialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>((global): Complete<StateProps> => {
|
export default memo(withGlobal<OwnProps>((global): Complete<StateProps> => {
|
||||||
const { messageIds, storyId } = selectTabState(global).forwardMessages;
|
const { messageIds, storyId, fromChatId } = selectTabState(global).forwardMessages;
|
||||||
const isForwarding = (messageIds && messageIds.length > 0);
|
const isForwarding = (messageIds && messageIds.length > 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUserId: global.currentUserId,
|
currentUserId: global.currentUserId,
|
||||||
isManyMessages: (messageIds?.length || 0) > 1,
|
isManyMessages: (messageIds?.length || 0) > 1,
|
||||||
isStory: Boolean(storyId),
|
isStory: Boolean(storyId),
|
||||||
isForwarding,
|
isForwarding,
|
||||||
|
fromChatId,
|
||||||
|
forwardMessageIds: messageIds,
|
||||||
|
shouldPaidMessageAutoApprove: global.settings.byKey.shouldPaidMessageAutoApprove,
|
||||||
};
|
};
|
||||||
})(ForwardRecipientPicker));
|
})(ForwardRecipientPicker));
|
||||||
|
|||||||
@ -4,13 +4,15 @@
|
|||||||
:global {
|
:global {
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
max-width: 26.25rem;
|
max-width: 26.25rem;
|
||||||
|
border-radius: 2.5rem;
|
||||||
|
background-color: var(--color-background-secondary);
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header-condensed {
|
.modal-header-condensed-wide {
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
transition: border-color 250ms ease-in-out;
|
transition: border-color 250ms ease-in-out;
|
||||||
}
|
}
|
||||||
@ -32,15 +34,16 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
|
|
||||||
width: 3.5rem !important;
|
width: 3rem !important;
|
||||||
height: 3.5rem !important;
|
height: 3rem !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.symbol-menu-button, .mobile-symbol-menu-button {
|
.symbol-menu-button, .mobile-symbol-menu-button {
|
||||||
margin-right: -1.75rem;
|
align-self: center;
|
||||||
|
margin-right: -1.5rem;
|
||||||
margin-left: -0.25rem !important;
|
margin-left: -0.25rem !important;
|
||||||
color: var(--color-composer-button);
|
color: var(--color-composer-button);
|
||||||
}
|
}
|
||||||
@ -59,12 +62,8 @@
|
|||||||
|
|
||||||
&.mobile :global {
|
&.mobile :global {
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
align-self: end;
|
|
||||||
|
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,37 +79,49 @@
|
|||||||
transform: translate3d(0, calc((var(--symbol-menu-height) - env(safe-area-inset-bottom)) * -1), 0);
|
transform: translate3d(0, calc((var(--symbol-menu-height) - env(safe-area-inset-bottom)) * -1), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.header-border :global(.modal-header-condensed) {
|
|
||||||
border-bottom-color: var(--color-borders);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachments {
|
.attachments {
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem;
|
gap: 0.125rem;
|
||||||
|
|
||||||
min-height: 5rem;
|
min-height: 5rem;
|
||||||
max-height: 26rem;
|
max-height: 26rem;
|
||||||
margin: 0 0.25rem;
|
margin: 0 1rem;
|
||||||
padding: 0 0.25rem;
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.asFile {
|
||||||
|
gap: 0;
|
||||||
|
padding-block: 0.5rem;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachments-bottom-padding {
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption-wrapper {
|
.caption-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 0.5rem;
|
|
||||||
border-top: 1px solid transparent;
|
margin: 1rem;
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
padding-inline-start: 0.5rem;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
transition: border-color 250ms ease-in-out;
|
transition: border-color 250ms ease-in-out;
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
@ -147,13 +158,14 @@
|
|||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
left: 0.5rem;
|
left: 1rem;
|
||||||
|
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
|
|
||||||
width: calc(100% - 1rem);
|
width: calc(100% - 2rem);
|
||||||
height: calc(100% - 0.5rem);
|
height: calc(100% - 1.125rem);
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.0625rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
@ -207,12 +219,20 @@
|
|||||||
|
|
||||||
.send-wrapper {
|
.send-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
align-self: flex-end;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.send {
|
.send {
|
||||||
padding: 0 1rem !important;
|
display: flex;
|
||||||
|
|
||||||
|
height: 2.625rem;
|
||||||
|
padding: 0 1.0625rem !important;
|
||||||
|
border-radius: 1.375rem;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sendIcon {
|
||||||
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import type { Signal } from '../../../util/signals';
|
|||||||
import {
|
import {
|
||||||
BASE_EMOJI_KEYWORD_LANG,
|
BASE_EMOJI_KEYWORD_LANG,
|
||||||
EDITABLE_INPUT_MODAL_ID,
|
EDITABLE_INPUT_MODAL_ID,
|
||||||
GIF_MIME_TYPE,
|
|
||||||
SUPPORTED_AUDIO_CONTENT_TYPES,
|
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||||
SUPPORTED_PHOTO_CONTENT_TYPES,
|
SUPPORTED_PHOTO_CONTENT_TYPES,
|
||||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||||
@ -39,7 +38,6 @@ import useLang from '../../../hooks/useLang';
|
|||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
||||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||||
import useScrolledState from '../../../hooks/useScrolledState';
|
|
||||||
import useCustomEmojiTooltip from './hooks/useCustomEmojiTooltip';
|
import useCustomEmojiTooltip from './hooks/useCustomEmojiTooltip';
|
||||||
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
||||||
import useMentionTooltip from './hooks/useMentionTooltip';
|
import useMentionTooltip from './hooks/useMentionTooltip';
|
||||||
@ -197,14 +195,6 @@ const AttachmentModal = ({
|
|||||||
);
|
);
|
||||||
const [renderingShouldSendInHighQuality, setRenderingShouldSendInHighQuality] = useState(shouldSendInHighQuality);
|
const [renderingShouldSendInHighQuality, setRenderingShouldSendInHighQuality] = useState(shouldSendInHighQuality);
|
||||||
|
|
||||||
const {
|
|
||||||
handleScroll: handleAttachmentsScroll,
|
|
||||||
isAtBeginning: areAttachmentsNotScrolled,
|
|
||||||
isAtEnd: areAttachmentsScrolledToBottom,
|
|
||||||
} = useScrolledState();
|
|
||||||
|
|
||||||
const { handleScroll: handleCaptionScroll, isAtBeginning: isCaptionNotScrolled } = useScrolledState();
|
|
||||||
|
|
||||||
const isOpen = Boolean(attachments.length);
|
const isOpen = Boolean(attachments.length);
|
||||||
const renderingIsOpen = Boolean(renderingAttachments?.length);
|
const renderingIsOpen = Boolean(renderingAttachments?.length);
|
||||||
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
||||||
@ -520,14 +510,13 @@ const AttachmentModal = ({
|
|||||||
const isQuickGallery = isSendingCompressed && hasOnlyMedia;
|
const isQuickGallery = isSendingCompressed && hasOnlyMedia;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
areAllPhotos, areAllVideos, areAllAudios, areAllGifs, hasAnyPhoto,
|
areAllPhotos, areAllVideos, areAllAudios, hasAnyPhoto,
|
||||||
} = useMemo(() => {
|
} = useMemo(() => {
|
||||||
if (!isQuickGallery || !renderingAttachments) {
|
if (!isQuickGallery || !renderingAttachments) {
|
||||||
return {
|
return {
|
||||||
areAllPhotos: false,
|
areAllPhotos: false,
|
||||||
areAllVideos: false,
|
areAllVideos: false,
|
||||||
areAllAudios: false,
|
areAllAudios: false,
|
||||||
areAllGifs: false,
|
|
||||||
hasAnyPhoto: false,
|
hasAnyPhoto: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -535,7 +524,6 @@ const AttachmentModal = ({
|
|||||||
areAllPhotos: renderingAttachments.every((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)),
|
areAllPhotos: renderingAttachments.every((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)),
|
||||||
areAllVideos: renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType)),
|
areAllVideos: renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType)),
|
||||||
areAllAudios: renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType)),
|
areAllAudios: renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType)),
|
||||||
areAllGifs: renderingAttachments.every((a) => a.gif || a.mimeType === GIF_MIME_TYPE),
|
|
||||||
hasAnyPhoto: renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)),
|
hasAnyPhoto: renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)),
|
||||||
};
|
};
|
||||||
}, [renderingAttachments, isQuickGallery]);
|
}, [renderingAttachments, isQuickGallery]);
|
||||||
@ -575,10 +563,7 @@ const AttachmentModal = ({
|
|||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
const attachmentsLength = renderingAttachments.length;
|
const attachmentsLength = renderingAttachments.length;
|
||||||
|
if (areAllPhotos) {
|
||||||
if (areAllGifs) {
|
|
||||||
title = lang(isEditing ? 'AttachmentReplaceGif' : 'AttachmentSendGif');
|
|
||||||
} else if (areAllPhotos) {
|
|
||||||
title = lang(
|
title = lang(
|
||||||
`Attachment${isEditing ? 'Replace' : 'Send'}Photo`,
|
`Attachment${isEditing ? 'Replace' : 'Send'}Photo`,
|
||||||
{ count: attachmentsLength },
|
{ count: attachmentsLength },
|
||||||
@ -610,7 +595,7 @@ const AttachmentModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-header-condensed" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="modal-header-condensed-wide" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
color="translucent"
|
color="translucent"
|
||||||
@ -701,14 +686,13 @@ const AttachmentModal = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBottomDividerShown = !areAttachmentsScrolledToBottom || !isCaptionNotScrolled;
|
const paidSendButtonCaption = paidMessagesStars ? formatStarsAsIcon(
|
||||||
const buttonSendCaption = paidMessagesStars ? formatStarsAsIcon(
|
|
||||||
lang,
|
lang,
|
||||||
attachmentsLength * paidMessagesStars,
|
attachmentsLength * paidMessagesStars,
|
||||||
{
|
{
|
||||||
asFont: true,
|
asFont: true,
|
||||||
},
|
},
|
||||||
) : lang('Send');
|
) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -717,7 +701,6 @@ const AttachmentModal = ({
|
|||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
styles.root,
|
styles.root,
|
||||||
isHovered && styles.hovered,
|
isHovered && styles.hovered,
|
||||||
!areAttachmentsNotScrolled && styles.headerBorder,
|
|
||||||
isMobile && styles.mobile,
|
isMobile && styles.mobile,
|
||||||
isSymbolMenuOpen && styles.symbolMenuOpen,
|
isSymbolMenuOpen && styles.symbolMenuOpen,
|
||||||
forceDarkTheme && 'component-theme-dark',
|
forceDarkTheme && 'component-theme-dark',
|
||||||
@ -744,9 +727,8 @@ const AttachmentModal = ({
|
|||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
styles.attachments,
|
styles.attachments,
|
||||||
'custom-scroll',
|
'custom-scroll',
|
||||||
isBottomDividerShown && styles.attachmentsBottomPadding,
|
!isSendingCompressed && styles.asFile,
|
||||||
)}
|
)}
|
||||||
onScroll={handleAttachmentsScroll}
|
|
||||||
>
|
>
|
||||||
{renderingAttachments.map((attachment, i) => (
|
{renderingAttachments.map((attachment, i) => (
|
||||||
<AttachmentModalItem
|
<AttachmentModalItem
|
||||||
@ -765,7 +747,6 @@ const AttachmentModal = ({
|
|||||||
<div
|
<div
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
styles.captionWrapper,
|
styles.captionWrapper,
|
||||||
isBottomDividerShown && styles.captionTopBorder,
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MentionTooltip
|
<MentionTooltip
|
||||||
@ -823,7 +804,6 @@ const AttachmentModal = ({
|
|||||||
placeholder={lang('AttachmentCaptionPlaceholder')}
|
placeholder={lang('AttachmentCaptionPlaceholder')}
|
||||||
onUpdate={onCaptionUpdate}
|
onUpdate={onCaptionUpdate}
|
||||||
onSend={handleSendClick}
|
onSend={handleSendClick}
|
||||||
onScroll={handleCaptionScroll}
|
|
||||||
canAutoFocus={Boolean(isReady && isForCurrentMessageList && attachments.length)}
|
canAutoFocus={Boolean(isReady && isForCurrentMessageList && attachments.length)}
|
||||||
captionLimit={leftChars}
|
captionLimit={leftChars}
|
||||||
shouldSuppressFocus={isMobile && isSymbolMenuOpen}
|
shouldSuppressFocus={isMobile && isSymbolMenuOpen}
|
||||||
@ -837,9 +817,11 @@ const AttachmentModal = ({
|
|||||||
inline
|
inline
|
||||||
onClick={handleSendClick}
|
onClick={handleSendClick}
|
||||||
onContextMenu={canShowCustomSendMenu ? handleContextMenu : undefined}
|
onContextMenu={canShowCustomSendMenu ? handleContextMenu : undefined}
|
||||||
|
iconName={!editingMessage && !shouldSchedule && !paidMessagesStars ? 'new-send' : undefined}
|
||||||
|
iconClassName={styles.sendIcon}
|
||||||
>
|
>
|
||||||
{shouldSchedule && !editingMessage ? lang('Next')
|
{shouldSchedule && !editingMessage ? lang('Next')
|
||||||
: editingMessage ? lang('Save') : buttonSendCaption}
|
: editingMessage ? lang('Save') : paidSendButtonCaption}
|
||||||
</Button>
|
</Button>
|
||||||
{canShowCustomSendMenu && (
|
{canShowCustomSendMenu && (
|
||||||
<CustomSendMenu
|
<CustomSendMenu
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border-radius: var(--border-radius-default);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
@ -42,12 +41,22 @@
|
|||||||
|
|
||||||
.no-grouping {
|
.no-grouping {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
|
margin-inline: 0.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
transition: background-color 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-item-hover);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file {
|
.file {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin: 0.5rem;
|
margin-block: 0.25rem;
|
||||||
|
margin-inline-start: 0.25rem;
|
||||||
|
margin-inline-end: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
@ -57,12 +66,23 @@
|
|||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
border-radius: var(--border-radius-default);
|
padding-inline: 0.25rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
font-size: 1.25rem;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
background-color: rgba(0, 0, 0, 0.25);
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
|
|
||||||
|
transition: opacity 0.15s ease-in-out;
|
||||||
|
|
||||||
|
.root:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
:global(body.no-menu-blur) & {
|
:global(body.no-menu-blur) & {
|
||||||
background-color: #707579;
|
background-color: #707579;
|
||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
@ -74,15 +94,15 @@
|
|||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
padding: 0.3125rem;
|
padding: 0.375rem;
|
||||||
border-radius: var(--border-radius-messages-small);
|
border-radius: 1.125rem;
|
||||||
|
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
transition: 0.2s background-color ease-in-out;
|
transition: 0.2s background-color;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.15);
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
@ -91,7 +111,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.delete-file {
|
.delete-file {
|
||||||
margin-right: 1rem;
|
margin-inline-end: 0.625rem;
|
||||||
|
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transition: opacity 0.15s, background-color 0.15s;
|
||||||
|
|
||||||
|
.root:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { getFileExtension } from '../../common/helpers/documentInfo';
|
|||||||
import { REM } from '../../common/helpers/mediaDimensions';
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import File from '../../common/File';
|
import File from '../../common/File';
|
||||||
@ -42,6 +43,7 @@ const AttachmentModalItem = ({
|
|||||||
onToggleSpoiler,
|
onToggleSpoiler,
|
||||||
onEdit,
|
onEdit,
|
||||||
}: OwnProps) => {
|
}: OwnProps) => {
|
||||||
|
const lang = useLang();
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const displayType = getDisplayType(attachment, shouldDisplayCompressed);
|
const displayType = getDisplayType(attachment, shouldDisplayCompressed);
|
||||||
|
|
||||||
@ -82,15 +84,16 @@ const AttachmentModalItem = ({
|
|||||||
);
|
);
|
||||||
default: {
|
default: {
|
||||||
const canEdit = SUPPORTED_PHOTO_CONTENT_TYPES.has(attachment.mimeType) && !isMobile;
|
const canEdit = SUPPORTED_PHOTO_CONTENT_TYPES.has(attachment.mimeType) && !isMobile;
|
||||||
|
const isPhoto = SUPPORTED_PHOTO_CONTENT_TYPES.has(attachment.mimeType);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<File
|
<File
|
||||||
className={styles.file}
|
className={styles.file}
|
||||||
name={attachment.filename}
|
name={attachment.filename}
|
||||||
extension={getFileExtension(attachment.filename, attachment.mimeType)}
|
extension={getFileExtension(attachment.filename, attachment.mimeType)}
|
||||||
previewData={attachment.previewBlobUrl}
|
previewData={isPhoto && attachment.blobUrl ? attachment.blobUrl : attachment.previewBlobUrl}
|
||||||
size={attachment.size}
|
size={attachment.size}
|
||||||
smaller
|
previewSize="large"
|
||||||
onClick={canEdit ? handleEditClick : undefined}
|
onClick={canEdit ? handleEditClick : undefined}
|
||||||
actionIcon={canEdit ? 'edit' : undefined}
|
actionIcon={canEdit ? 'edit' : undefined}
|
||||||
/>
|
/>
|
||||||
@ -117,7 +120,7 @@ const AttachmentModalItem = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={rootClassName}>
|
<div className={rootClassName} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
{content}
|
{content}
|
||||||
<MediaSpoiler
|
<MediaSpoiler
|
||||||
isVisible={shouldDisplaySpoiler}
|
isVisible={shouldDisplaySpoiler}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import Button from '../../../ui/Button';
|
|||||||
import InfiniteScroll from '../../../ui/InfiniteScroll';
|
import InfiniteScroll from '../../../ui/InfiniteScroll';
|
||||||
import Link from '../../../ui/Link';
|
import Link from '../../../ui/Link';
|
||||||
import Modal from '../../../ui/Modal';
|
import Modal from '../../../ui/Modal';
|
||||||
import TabList, { type TabWithProperties } from '../../../ui/TabList';
|
import SquareTabList, { type TabWithProperties } from '../../../ui/SquareTabList';
|
||||||
import Transition from '../../../ui/Transition';
|
import Transition from '../../../ui/Transition';
|
||||||
import GiftAttributeItem from '../GiftAttributeItem';
|
import GiftAttributeItem from '../GiftAttributeItem';
|
||||||
import UniqueGiftHeader from '../UniqueGiftHeader';
|
import UniqueGiftHeader from '../UniqueGiftHeader';
|
||||||
@ -474,7 +474,7 @@ const GiftPreviewModal = ({ modal, animationLevel }: OwnProps & StateProps) => {
|
|||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
>
|
>
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
<TabList
|
<SquareTabList
|
||||||
className={styles.tabs}
|
className={styles.tabs}
|
||||||
activeTab={selectedTabIndex}
|
activeTab={selectedTabIndex}
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ const QuickChatPickerModal = ({
|
|||||||
return (
|
return (
|
||||||
<RecipientPicker
|
<RecipientPicker
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
title={lang('SelectChat')}
|
||||||
searchPlaceholder={lang('Search')}
|
searchPlaceholder={lang('Search')}
|
||||||
onSelectRecipient={handleSelectRecipient}
|
onSelectRecipient={handleSelectRecipient}
|
||||||
onClose={closeQuickChatPicker}
|
onClose={closeQuickChatPicker}
|
||||||
|
|||||||
@ -149,6 +149,7 @@ const SharePreparedMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
<>
|
<>
|
||||||
<RecipientPicker
|
<RecipientPicker
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
title={lang('SelectChat')}
|
||||||
searchPlaceholder={lang('Search')}
|
searchPlaceholder={lang('Search')}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onSelectRecipient={handleSelectRecipient}
|
onSelectRecipient={handleSelectRecipient}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import SafeLink from '../../common/SafeLink';
|
|||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
import Modal from '../../ui/Modal';
|
import Modal from '../../ui/Modal';
|
||||||
import TabList, { type TabWithProperties } from '../../ui/TabList';
|
import SquareTabList, { type TabWithProperties } from '../../ui/SquareTabList';
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import ParticlesHeader from '../common/ParticlesHeader.tsx';
|
import ParticlesHeader from '../common/ParticlesHeader.tsx';
|
||||||
import BalanceBlock from './BalanceBlock';
|
import BalanceBlock from './BalanceBlock';
|
||||||
@ -426,7 +426,7 @@ const StarsBalanceModal = ({
|
|||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<TabList
|
<SquareTabList
|
||||||
ref={tabsRef}
|
ref={tabsRef}
|
||||||
className={buildClassName(styles.tabs, areTabsPinned && styles.pinned)}
|
className={buildClassName(styles.tabs, areTabsPinned && styles.pinned)}
|
||||||
tabClassName={styles.tab}
|
tabClassName={styles.tab}
|
||||||
|
|||||||
@ -89,7 +89,7 @@
|
|||||||
|
|
||||||
@include mixins.with-vt-type('rightColumn');
|
@include mixins.with-vt-type('rightColumn');
|
||||||
|
|
||||||
.TabList {
|
.SquareTabList {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -119,7 +119,7 @@ import InfiniteScroll from '../ui/InfiniteScroll';
|
|||||||
import Link from '../ui/Link';
|
import Link from '../ui/Link';
|
||||||
import ListItem, { type MenuItemContextAction } from '../ui/ListItem';
|
import ListItem, { type MenuItemContextAction } from '../ui/ListItem';
|
||||||
import Spinner from '../ui/Spinner';
|
import Spinner from '../ui/Spinner';
|
||||||
import TabList, { type TabWithProperties } from '../ui/TabList';
|
import SquareTabList, { type TabWithProperties } from '../ui/SquareTabList';
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
import DeleteMemberModal from './DeleteMemberModal';
|
import DeleteMemberModal from './DeleteMemberModal';
|
||||||
import StarGiftCollectionList from './gifts/StarGiftCollectionList';
|
import StarGiftCollectionList from './gifts/StarGiftCollectionList';
|
||||||
@ -953,7 +953,7 @@ const Profile = ({
|
|||||||
id={`shared-media${getMessageHtmlId(id)}`}
|
id={`shared-media${getMessageHtmlId(id)}`}
|
||||||
document={getMessageDocument(messagesById[id])!}
|
document={getMessageDocument(messagesById[id])!}
|
||||||
datetime={messagesById[id].date}
|
datetime={messagesById[id].date}
|
||||||
smaller
|
fileSize="small"
|
||||||
className="scroll-item"
|
className="scroll-item"
|
||||||
isDownloading={getIsDownloading(activeDownloads, getMessageDocument(messagesById[id])!)}
|
isDownloading={getIsDownloading(activeDownloads, getMessageDocument(messagesById[id])!)}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForMedia}
|
||||||
@ -1264,7 +1264,7 @@ const Profile = ({
|
|||||||
>
|
>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</Transition>
|
</Transition>
|
||||||
<TabList activeTab={activeTabIndex} tabs={tabs} onSwitchTab={handleSwitchTab} />
|
<SquareTabList activeTab={activeTabIndex} tabs={tabs} onSwitchTab={handleSwitchTab} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export default function useProfileState({
|
|||||||
useEffectWithPrevDeps(([prevTabType]) => {
|
useEffectWithPrevDeps(([prevTabType]) => {
|
||||||
if ((prevTabType && prevTabType !== tabType && allowAutoScrollToTabs) || (tabType && forceScrollProfileTab)) {
|
if ((prevTabType && prevTabType !== tabType && allowAutoScrollToTabs) || (tabType && forceScrollProfileTab)) {
|
||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
const tabsEl = container.querySelector<HTMLDivElement>('.TabList')!;
|
const tabsEl = container.querySelector<HTMLDivElement>('.SquareTabList')!;
|
||||||
handleStopAutoScrollToTabs();
|
handleStopAutoScrollToTabs();
|
||||||
if (container.scrollTop < tabsEl.offsetTop) {
|
if (container.scrollTop < tabsEl.offsetTop) {
|
||||||
onProfileStateChange(getStateFromTabType(tabType));
|
onProfileStateChange(getStateFromTabType(tabType));
|
||||||
@ -69,7 +69,7 @@ export default function useProfileState({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabListEl = container.querySelector<HTMLDivElement>('.TabList');
|
const tabListEl = container.querySelector<HTMLDivElement>('.SquareTabList');
|
||||||
if (!tabListEl || tabListEl.offsetTop > container.scrollTop) {
|
if (!tabListEl || tabListEl.offsetTop > container.scrollTop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ export default function useProfileState({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabListEl = container.querySelector<HTMLDivElement>('.TabList');
|
const tabListEl = container.querySelector<HTMLDivElement>('.SquareTabList');
|
||||||
if (!tabListEl) {
|
if (!tabListEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export default function useTransitionFixes(
|
|||||||
function setMinHeight() {
|
function setMinHeight() {
|
||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
const transitionEl = container.querySelector<HTMLDivElement>(transitionElSelector);
|
const transitionEl = container.querySelector<HTMLDivElement>(transitionElSelector);
|
||||||
const tabsEl = container.querySelector<HTMLDivElement>('.TabList');
|
const tabsEl = container.querySelector<HTMLDivElement>('.SquareTabList');
|
||||||
if (transitionEl && tabsEl) {
|
if (transitionEl && tabsEl) {
|
||||||
const newHeight = container.clientHeight - tabsEl.offsetHeight;
|
const newHeight = container.clientHeight - tabsEl.offsetHeight;
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import type { ApiChat, ApiChatMember } from '../../../api/types';
|
|||||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||||
import { selectChatFullInfo } from '../../../global/selectors';
|
import { selectChatFullInfo } from '../../../global/selectors';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
|
||||||
|
|
||||||
import ChatOrUserPicker from '../../common/pickers/ChatOrUserPicker';
|
import ChatOrUserPicker from '../../common/pickers/ChatOrUserPicker';
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
|
|||||||
deleteChatMember,
|
deleteChatMember,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useLang();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
const usersId = useMemo(() => {
|
const usersId = useMemo(() => {
|
||||||
@ -65,7 +65,8 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
|
|||||||
<ChatOrUserPicker
|
<ChatOrUserPicker
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
chatOrUserIds={usersId}
|
chatOrUserIds={usersId}
|
||||||
searchPlaceholder={lang('ChannelBlockUser')}
|
title={lang('ChannelBlockUser')}
|
||||||
|
searchPlaceholder={lang('Search')}
|
||||||
search={search}
|
search={search}
|
||||||
onSearchChange={setSearch}
|
onSearchChange={setSearch}
|
||||||
loadMore={handleLoadMore}
|
loadMore={handleLoadMore}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import PrivateChatInfo from '../../common/PrivateChatInfo';
|
|||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import Spinner from '../../ui/Spinner';
|
import Spinner from '../../ui/Spinner';
|
||||||
import TabList from '../../ui/TabList';
|
import SquareTabList from '../../ui/SquareTabList';
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import StatisticsOverview from './StatisticsOverview';
|
import StatisticsOverview from './StatisticsOverview';
|
||||||
|
|
||||||
@ -366,7 +366,7 @@ const BoostStatistics = ({
|
|||||||
>
|
>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</Transition>
|
</Transition>
|
||||||
<TabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={setActiveTab} />
|
<SquareTabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={setActiveTab} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -145,11 +145,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-header-condensed-wide,
|
||||||
.modal-header-condensed {
|
.modal-header-condensed {
|
||||||
@extend %modal-header;
|
@extend %modal-header;
|
||||||
|
|
||||||
min-height: 3.5rem;
|
min-height: 3.5rem;
|
||||||
padding: 0.375rem 0.75rem !important;
|
|
||||||
|
|
||||||
.modal-action-button {
|
.modal-action-button {
|
||||||
width: auto;
|
width: auto;
|
||||||
@ -175,6 +175,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-header-condensed {
|
||||||
|
padding: 0.375rem 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header-condensed-wide {
|
||||||
|
padding: 0.375rem 1.375rem !important;
|
||||||
|
.modal-title {
|
||||||
|
&:not(:only-child) {
|
||||||
|
margin: 0 1.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export type OwnProps = {
|
|||||||
hasCloseButton?: boolean;
|
hasCloseButton?: boolean;
|
||||||
hasAbsoluteCloseButton?: boolean;
|
hasAbsoluteCloseButton?: boolean;
|
||||||
absoluteCloseButtonColor?: ButtonProps['color'];
|
absoluteCloseButtonColor?: ButtonProps['color'];
|
||||||
|
isBackButton?: boolean;
|
||||||
noBackdrop?: boolean;
|
noBackdrop?: boolean;
|
||||||
noBackdropClose?: boolean;
|
noBackdropClose?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -91,6 +92,7 @@ const Modal = (props: OwnProps) => {
|
|||||||
hasCloseButton,
|
hasCloseButton,
|
||||||
hasAbsoluteCloseButton,
|
hasAbsoluteCloseButton,
|
||||||
absoluteCloseButtonColor = 'translucent',
|
absoluteCloseButtonColor = 'translucent',
|
||||||
|
isBackButton,
|
||||||
noBackdrop,
|
noBackdrop,
|
||||||
style,
|
style,
|
||||||
dialogStyle,
|
dialogStyle,
|
||||||
@ -176,16 +178,22 @@ const Modal = (props: OwnProps) => {
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const closeIconClassName = buildClassName(
|
||||||
|
'animated-close-icon',
|
||||||
|
isBackButton && 'state-back',
|
||||||
|
);
|
||||||
|
|
||||||
const closeButton = withCloseButton ? (
|
const closeButton = withCloseButton ? (
|
||||||
<Button
|
<Button
|
||||||
className={buildClassName(hasAbsoluteCloseButton && 'modal-absolute-close-button')}
|
className={buildClassName(hasAbsoluteCloseButton && 'modal-absolute-close-button')}
|
||||||
round
|
round
|
||||||
color={absoluteCloseButtonColor}
|
color={absoluteCloseButtonColor}
|
||||||
size="tiny"
|
size="tiny"
|
||||||
iconName="close"
|
ariaLabel={isBackButton ? lang('Back') : lang('Close')}
|
||||||
ariaLabel={lang('Close')}
|
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
>
|
||||||
|
<div className={closeIconClassName} />
|
||||||
|
</Button>
|
||||||
) : undefined;
|
) : undefined;
|
||||||
|
|
||||||
return title ? (
|
return title ? (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
.TabList {
|
.SquareTabList {
|
||||||
scrollbar-color: rgba(0, 0, 0, 0);
|
scrollbar-color: rgba(0, 0, 0, 0);
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
||||||
111
src/components/ui/SquareTabList.tsx
Normal file
111
src/components/ui/SquareTabList.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import type { ElementRef, TeactNode } from '../../lib/teact/teact';
|
||||||
|
import { memo, useEffect, useRef } from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { ApiMessageEntityCustomEmoji } from '../../api/types';
|
||||||
|
import type { MenuItemContextAction } from './ListItem';
|
||||||
|
|
||||||
|
import animateHorizontalScroll from '../../util/animateHorizontalScroll';
|
||||||
|
import { IS_ANDROID, IS_IOS } from '../../util/browser/windowEnvironment';
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useHorizontalScroll from '../../hooks/useHorizontalScroll';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||||
|
|
||||||
|
import Tab from './Tab';
|
||||||
|
|
||||||
|
import './SquareTabList.scss';
|
||||||
|
|
||||||
|
export type TabWithProperties = {
|
||||||
|
id?: number;
|
||||||
|
title: TeactNode;
|
||||||
|
badgeCount?: number;
|
||||||
|
isBlocked?: boolean;
|
||||||
|
isBadgeActive?: boolean;
|
||||||
|
contextActions?: MenuItemContextAction[];
|
||||||
|
emoticon?: string | ApiMessageEntityCustomEmoji;
|
||||||
|
noTitleAnimations?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
tabs: readonly TabWithProperties[];
|
||||||
|
activeTab: number;
|
||||||
|
className?: string;
|
||||||
|
tabClassName?: string;
|
||||||
|
contextRootElementSelector?: string;
|
||||||
|
ref?: ElementRef<HTMLDivElement>;
|
||||||
|
onSwitchTab: (index: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TAB_SCROLL_THRESHOLD_PX = 16;
|
||||||
|
// Should match duration from `--slide-transition` CSS variable
|
||||||
|
const SCROLL_DURATION = IS_IOS ? 450 : IS_ANDROID ? 400 : 300;
|
||||||
|
|
||||||
|
const SquareTabList = ({
|
||||||
|
tabs,
|
||||||
|
activeTab,
|
||||||
|
className,
|
||||||
|
tabClassName,
|
||||||
|
contextRootElementSelector,
|
||||||
|
ref,
|
||||||
|
onSwitchTab,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const internalRef = useRef<HTMLDivElement>();
|
||||||
|
const containerRef = ref || internalRef;
|
||||||
|
const previousActiveTab = usePreviousDeprecated(activeTab);
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
useHorizontalScroll(containerRef, undefined, true);
|
||||||
|
|
||||||
|
// Scroll container to place active tab in the center
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current!;
|
||||||
|
const { scrollWidth, offsetWidth, scrollLeft } = container;
|
||||||
|
if (scrollWidth <= offsetWidth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTabElement = container.childNodes[activeTab] as HTMLElement | null;
|
||||||
|
if (!activeTabElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { offsetLeft: activeTabOffsetLeft, offsetWidth: activeTabOffsetWidth } = activeTabElement;
|
||||||
|
const newLeft = activeTabOffsetLeft - (offsetWidth / 2) + (activeTabOffsetWidth / 2);
|
||||||
|
|
||||||
|
// Prevent scrolling by only a couple of pixels, which doesn't look smooth
|
||||||
|
if (Math.abs(newLeft - scrollLeft) < TAB_SCROLL_THRESHOLD_PX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animateHorizontalScroll(container, newLeft, SCROLL_DURATION);
|
||||||
|
}, [activeTab, containerRef]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName('SquareTabList', 'no-scrollbar', className)}
|
||||||
|
ref={containerRef}
|
||||||
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tab
|
||||||
|
key={tab.id}
|
||||||
|
title={tab.title}
|
||||||
|
isActive={i === activeTab}
|
||||||
|
isBlocked={tab.isBlocked}
|
||||||
|
badgeCount={tab.badgeCount}
|
||||||
|
isBadgeActive={tab.isBadgeActive}
|
||||||
|
previousActiveTab={previousActiveTab}
|
||||||
|
onClick={onSwitchTab}
|
||||||
|
clickArg={i}
|
||||||
|
contextActions={tab.contextActions}
|
||||||
|
contextRootElementSelector={contextRootElementSelector}
|
||||||
|
className={tabClassName}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SquareTabList);
|
||||||
85
src/components/ui/TabList.module.scss
Normal file
85
src/components/ui/TabList.module.scss
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
.container,
|
||||||
|
.activeIndicator {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding-block: 0.375rem;
|
||||||
|
padding-inline: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
user-select: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
transition: opacity 150ms;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ready {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeIndicator {
|
||||||
|
will-change: clip-path;
|
||||||
|
|
||||||
|
isolation: isolate;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
contain: layout style paint;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
background-color: var(--color-primary-opacity);
|
||||||
|
|
||||||
|
transition: clip-path var(--slide-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
gap: 0.25rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 0.375rem 1rem;
|
||||||
|
border-radius: 1.25rem;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeIndicator & {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lockIcon {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
@ -1,111 +1,90 @@
|
|||||||
import type { ElementRef, TeactNode } from '../../lib/teact/teact';
|
import { memo, useEffect, useRef, useState } from '../../lib/teact/teact';
|
||||||
import { memo, useEffect, useRef } from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { ApiMessageEntityCustomEmoji } from '../../api/types';
|
import type { TabWithProperties } from './SquareTabList';
|
||||||
import type { MenuItemContextAction } from './ListItem';
|
|
||||||
|
export type { TabWithProperties };
|
||||||
|
|
||||||
import animateHorizontalScroll from '../../util/animateHorizontalScroll';
|
|
||||||
import { IS_ANDROID, IS_IOS } from '../../util/browser/windowEnvironment';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
import useHorizontalScroll from '../../hooks/useHorizontalScroll';
|
import useHorizontalScroll from '../../hooks/useHorizontalScroll';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
import useResizeObserver from '../../hooks/useResizeObserver';
|
||||||
|
|
||||||
import Tab from './Tab';
|
import Icon from '../common/icons/Icon';
|
||||||
|
|
||||||
import './TabList.scss';
|
import styles from './TabList.module.scss';
|
||||||
|
|
||||||
export type TabWithProperties = {
|
|
||||||
id?: number;
|
|
||||||
title: TeactNode;
|
|
||||||
badgeCount?: number;
|
|
||||||
isBlocked?: boolean;
|
|
||||||
isBadgeActive?: boolean;
|
|
||||||
contextActions?: MenuItemContextAction[];
|
|
||||||
emoticon?: string | ApiMessageEntityCustomEmoji;
|
|
||||||
noTitleAnimations?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
tabs: readonly TabWithProperties[];
|
tabs: readonly TabWithProperties[];
|
||||||
activeTab: number;
|
activeTab: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
tabClassName?: string;
|
|
||||||
contextRootElementSelector?: string;
|
|
||||||
ref?: ElementRef<HTMLDivElement>;
|
|
||||||
onSwitchTab: (index: number) => void;
|
onSwitchTab: (index: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TAB_SCROLL_THRESHOLD_PX = 16;
|
|
||||||
// Should match duration from `--slide-transition` CSS variable
|
|
||||||
const SCROLL_DURATION = IS_IOS ? 450 : IS_ANDROID ? 400 : 300;
|
|
||||||
|
|
||||||
const TabList = ({
|
const TabList = ({
|
||||||
tabs,
|
tabs,
|
||||||
activeTab,
|
activeTab,
|
||||||
className,
|
className,
|
||||||
tabClassName,
|
|
||||||
contextRootElementSelector,
|
|
||||||
ref,
|
|
||||||
onSwitchTab,
|
onSwitchTab,
|
||||||
}: OwnProps) => {
|
}: OwnProps) => {
|
||||||
let containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
if (ref) {
|
const clipPathContainerRef = useRef<HTMLDivElement>();
|
||||||
containerRef = ref;
|
const [clipPath, setClipPath] = useState<string>('');
|
||||||
}
|
|
||||||
const previousActiveTab = usePreviousDeprecated(activeTab);
|
|
||||||
|
|
||||||
const lang = useLang();
|
useHorizontalScroll(containerRef, !tabs.length, true);
|
||||||
|
|
||||||
useHorizontalScroll(containerRef, undefined, true);
|
const updateClipPath = useLastCallback(() => {
|
||||||
|
const clipPathContainer = clipPathContainerRef.current;
|
||||||
|
const activeTabEl = activeTab >= 0 && clipPathContainer?.childNodes[activeTab] as HTMLElement | undefined;
|
||||||
|
|
||||||
|
if (clipPathContainer && activeTabEl && clipPathContainer.offsetWidth > 0) {
|
||||||
|
const { offsetLeft, offsetWidth } = activeTabEl;
|
||||||
|
const containerWidth = clipPathContainer.offsetWidth;
|
||||||
|
const left = (offsetLeft / containerWidth * 100).toFixed(1);
|
||||||
|
const right = ((containerWidth - (offsetLeft + offsetWidth)) / containerWidth * 100).toFixed(1);
|
||||||
|
|
||||||
|
setClipPath(`inset(0.25rem ${right}% 0.25rem ${left}% round 1.25rem)`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Scroll container to place active tab in the center
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current!;
|
updateClipPath();
|
||||||
const { scrollWidth, offsetWidth, scrollLeft } = container;
|
}, [activeTab, tabs]);
|
||||||
if (scrollWidth <= offsetWidth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeTabElement = container.childNodes[activeTab] as HTMLElement | null;
|
useResizeObserver(clipPathContainerRef, updateClipPath);
|
||||||
if (!activeTabElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { offsetLeft: activeTabOffsetLeft, offsetWidth: activeTabOffsetWidth } = activeTabElement;
|
const handleTabClick = useLastCallback((index: number) => {
|
||||||
const newLeft = activeTabOffsetLeft - (offsetWidth / 2) + (activeTabOffsetWidth / 2);
|
onSwitchTab(index);
|
||||||
|
});
|
||||||
|
|
||||||
// Prevent scrolling by only a couple of pixels, which doesn't look smooth
|
if (!tabs.length) return undefined;
|
||||||
if (Math.abs(newLeft - scrollLeft) < TAB_SCROLL_THRESHOLD_PX) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
animateHorizontalScroll(container, newLeft, SCROLL_DURATION);
|
const renderTab = (tab: TabWithProperties, index: number) => (
|
||||||
}, [activeTab]);
|
<div
|
||||||
|
key={tab.id ?? index}
|
||||||
|
className={styles.tab}
|
||||||
|
onClick={() => handleTabClick(index)}
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
|
{tab.isBlocked && <Icon name="lock-badge" className={styles.lockIcon} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName('TabList', 'no-scrollbar', className)}
|
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
className={buildClassName(styles.container, className, clipPath && styles.ready)}
|
||||||
>
|
>
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map(renderTab)}
|
||||||
<Tab
|
|
||||||
key={tab.id}
|
<div
|
||||||
title={tab.title}
|
ref={clipPathContainerRef}
|
||||||
isActive={i === activeTab}
|
className={styles.activeIndicator}
|
||||||
isBlocked={tab.isBlocked}
|
style={clipPath ? `clip-path: ${clipPath}` : undefined}
|
||||||
badgeCount={tab.badgeCount}
|
aria-hidden
|
||||||
isBadgeActive={tab.isBadgeActive}
|
>
|
||||||
previousActiveTab={previousActiveTab}
|
{tabs.map(renderTab)}
|
||||||
onClick={onSwitchTab}
|
</div>
|
||||||
clickArg={i}
|
|
||||||
contextActions={tab.contextActions}
|
|
||||||
contextRootElementSelector={contextRootElementSelector}
|
|
||||||
className={tabClassName}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import Icon from '../../common/icons/Icon';
|
|||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import FloatingActionButton from '../FloatingActionButton';
|
import FloatingActionButton from '../FloatingActionButton';
|
||||||
import Portal from '../Portal';
|
import Portal from '../Portal';
|
||||||
import TabList from '../TabList';
|
import SquareTabList from '../SquareTabList';
|
||||||
import Transition from '../Transition';
|
import Transition from '../Transition';
|
||||||
import CropOverlay from './CropOverlay';
|
import CropOverlay from './CropOverlay';
|
||||||
import CropPanel from './CropPanel';
|
import CropPanel from './CropPanel';
|
||||||
@ -807,7 +807,7 @@ const MediaEditor = ({
|
|||||||
>
|
>
|
||||||
{renderPanelContent()}
|
{renderPanelContent()}
|
||||||
</Transition>
|
</Transition>
|
||||||
<TabList
|
<SquareTabList
|
||||||
tabs={TABS}
|
tabs={TABS}
|
||||||
activeTab={activeTabIndex}
|
activeTab={activeTabIndex}
|
||||||
onSwitchTab={handleTabSwitch}
|
onSwitchTab={handleTabSwitch}
|
||||||
|
|||||||
@ -145,6 +145,7 @@ import {
|
|||||||
selectMessageReplyInfo,
|
selectMessageReplyInfo,
|
||||||
selectOutlyingListByMessageId,
|
selectOutlyingListByMessageId,
|
||||||
selectPeer,
|
selectPeer,
|
||||||
|
selectPeerPaidMessagesStars,
|
||||||
selectPeerStory,
|
selectPeerStory,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectPollFromMessage,
|
selectPollFromMessage,
|
||||||
@ -2607,6 +2608,143 @@ addActionHandler('forwardToSavedMessages', (global, actions, payload): ActionRet
|
|||||||
actions.forwardMessages({ isSilent: true, scheduledAt, tabId });
|
actions.forwardMessages({ isSilent: true, scheduledAt, tabId });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface ForwardToChatOptions {
|
||||||
|
global: GlobalState;
|
||||||
|
fromChat: ApiChat;
|
||||||
|
toChat: ApiChat;
|
||||||
|
realMessages: ApiMessage[];
|
||||||
|
serviceMessages: ApiMessage[];
|
||||||
|
comment?: string;
|
||||||
|
withMyScore?: boolean;
|
||||||
|
noAuthors?: boolean;
|
||||||
|
noCaptions?: boolean;
|
||||||
|
isCurrentUserPremium: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function forwardMessagesToChat({
|
||||||
|
global,
|
||||||
|
fromChat,
|
||||||
|
toChat,
|
||||||
|
realMessages,
|
||||||
|
serviceMessages,
|
||||||
|
comment,
|
||||||
|
withMyScore,
|
||||||
|
noAuthors,
|
||||||
|
noCaptions,
|
||||||
|
isCurrentUserPremium,
|
||||||
|
}: ForwardToChatOptions) {
|
||||||
|
const sendAs = selectSendAs(global, toChat.id);
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, toChat.id);
|
||||||
|
const messagePriceInStars = selectPeerPaidMessagesStars(global, toChat.id);
|
||||||
|
|
||||||
|
if (comment) {
|
||||||
|
sendMessage(global, {
|
||||||
|
chat: toChat,
|
||||||
|
text: comment,
|
||||||
|
sendAs,
|
||||||
|
lastMessageId,
|
||||||
|
messagePriceInStars,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realMessages.length) {
|
||||||
|
const messageSlices = global.config?.maxForwardedCount
|
||||||
|
? splitMessagesForForwarding(realMessages, global.config.maxForwardedCount)
|
||||||
|
: [realMessages];
|
||||||
|
|
||||||
|
for (const slice of messageSlices) {
|
||||||
|
const forwardParams: ForwardMessagesParams = {
|
||||||
|
fromChat,
|
||||||
|
toChat,
|
||||||
|
toThreadId: MAIN_THREAD_ID,
|
||||||
|
messages: slice,
|
||||||
|
isSilent: true,
|
||||||
|
sendAs,
|
||||||
|
withMyScore,
|
||||||
|
noAuthors,
|
||||||
|
noCaptions,
|
||||||
|
isCurrentUserPremium,
|
||||||
|
wasDrafted: false,
|
||||||
|
lastMessageId,
|
||||||
|
messagePriceInStars,
|
||||||
|
};
|
||||||
|
|
||||||
|
callApi('forwardMessages', forwardParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const message of serviceMessages) {
|
||||||
|
const { text, entities } = message.content.text || {};
|
||||||
|
const { sticker } = message.content;
|
||||||
|
|
||||||
|
sendMessage(global, {
|
||||||
|
chat: toChat,
|
||||||
|
text,
|
||||||
|
entities,
|
||||||
|
sticker,
|
||||||
|
isSilent: true,
|
||||||
|
sendAs,
|
||||||
|
lastMessageId,
|
||||||
|
messagePriceInStars,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addActionHandler('forwardToMultipleChats', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { toChatIds, comment, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const {
|
||||||
|
fromChatId, messageIds, withMyScore, noAuthors, noCaptions,
|
||||||
|
} = selectTabState(global, tabId).forwardMessages;
|
||||||
|
|
||||||
|
const fromChat = fromChatId ? selectChat(global, fromChatId) : undefined;
|
||||||
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|
||||||
|
const messages = fromChatId && messageIds
|
||||||
|
? messageIds
|
||||||
|
.sort((a, b) => a - b)
|
||||||
|
.map((id) => selectChatMessage(global, fromChatId, id)).filter(Boolean)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (!fromChat || !messages?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m));
|
||||||
|
const forwardableRealMessages = realMessages.filter((message) => selectCanForwardMessage(global, message));
|
||||||
|
|
||||||
|
if (!forwardableRealMessages.length && !serviceMessages.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const toChatId of toChatIds) {
|
||||||
|
const toChat = selectChat(global, toChatId);
|
||||||
|
if (!toChat) continue;
|
||||||
|
|
||||||
|
forwardMessagesToChat({
|
||||||
|
global,
|
||||||
|
fromChat,
|
||||||
|
toChat,
|
||||||
|
realMessages: forwardableRealMessages,
|
||||||
|
serviceMessages,
|
||||||
|
comment,
|
||||||
|
withMyScore,
|
||||||
|
noAuthors,
|
||||||
|
noCaptions,
|
||||||
|
isCurrentUserPremium,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
forwardMessages: {},
|
||||||
|
isShareMessageModalShown: false,
|
||||||
|
}, tabId);
|
||||||
|
|
||||||
|
actions.exitMessageSelectMode({ tabId });
|
||||||
|
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('forwardStory', (global, actions, payload): ActionReturnType => {
|
addActionHandler('forwardStory', (global, actions, payload): ActionReturnType => {
|
||||||
const { toChatId, tabId = getCurrentTabId() } = payload || {};
|
const { toChatId, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
|||||||
@ -692,6 +692,20 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectCanCopyMessageLink<T extends GlobalState>(
|
||||||
|
global: T, message: ApiMessage,
|
||||||
|
) {
|
||||||
|
const chat = selectChat(global, message.chatId);
|
||||||
|
if (!chat || selectIsChatRestricted(global, message.chatId)) return false;
|
||||||
|
|
||||||
|
const isLocal = isMessageLocal(message);
|
||||||
|
const isAction = isActionMessage(message);
|
||||||
|
const isChannel = isChatChannel(chat);
|
||||||
|
const isSuperGroup = isChatSuperGroup(chat);
|
||||||
|
|
||||||
|
return !isLocal && !isAction && (isChannel || isSuperGroup) && !chat.isMonoforum;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectCanDeleteMessages<T extends GlobalState>(
|
export function selectCanDeleteMessages<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
|
|||||||
@ -2023,6 +2023,10 @@ export interface ActionPayloads {
|
|||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
exitForwardMode: WithTabId | undefined;
|
exitForwardMode: WithTabId | undefined;
|
||||||
changeRecipient: WithTabId | undefined;
|
changeRecipient: WithTabId | undefined;
|
||||||
|
forwardToMultipleChats: {
|
||||||
|
toChatIds: string[];
|
||||||
|
comment?: string;
|
||||||
|
} & WithTabId;
|
||||||
forwardToSavedMessages: {
|
forwardToSavedMessages: {
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { getActions, getGlobal } from '../global';
|
|||||||
|
|
||||||
import type { ApiMessageEntity, ApiMessageEntityCustomEmoji } from '../api/types';
|
import type { ApiMessageEntity, ApiMessageEntityCustomEmoji } from '../api/types';
|
||||||
import type { MenuItemContextAction } from '../components/ui/ListItem';
|
import type { MenuItemContextAction } from '../components/ui/ListItem';
|
||||||
import type { TabWithProperties } from '../components/ui/TabList';
|
import type { TabWithProperties } from '../components/ui/SquareTabList';
|
||||||
import { type ApiChatFolder, type ApiChatlistExportedInvite, ApiMessageEntityTypes } from '../api/types';
|
import { type ApiChatFolder, type ApiChatlistExportedInvite, ApiMessageEntityTypes } from '../api/types';
|
||||||
import { SettingsScreens } from '../types';
|
import { SettingsScreens } from '../types';
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url("./icons.woff2?4ca2ae9f8c7763ea420459de680c8340") format("woff2"),
|
src: url("./icons.woff2?711696a367cfec0ddfb1ac8f13f32988") format("woff2"),
|
||||||
url("./icons.woff?4ca2ae9f8c7763ea420459de680c8340") format("woff");
|
url("./icons.woff?711696a367cfec0ddfb1ac8f13f32988") format("woff");
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-char::before {
|
.icon-char::before {
|
||||||
@ -444,564 +444,570 @@ url("./icons.woff?4ca2ae9f8c7763ea420459de680c8340") format("woff");
|
|||||||
.icon-next-link::before {
|
.icon-next-link::before {
|
||||||
content: "\f18d";
|
content: "\f18d";
|
||||||
}
|
}
|
||||||
.icon-new-chat-filled::before {
|
.icon-new-send::before {
|
||||||
content: "\f18e";
|
content: "\f18e";
|
||||||
}
|
}
|
||||||
.icon-my-notes::before {
|
.icon-new-chat-filled::before {
|
||||||
content: "\f18f";
|
content: "\f18f";
|
||||||
}
|
}
|
||||||
.icon-muted::before {
|
.icon-my-notes::before {
|
||||||
content: "\f190";
|
content: "\f190";
|
||||||
}
|
}
|
||||||
.icon-mute::before {
|
.icon-muted::before {
|
||||||
content: "\f191";
|
content: "\f191";
|
||||||
}
|
}
|
||||||
.icon-move-caption-up::before {
|
.icon-mute::before {
|
||||||
content: "\f192";
|
content: "\f192";
|
||||||
}
|
}
|
||||||
.icon-move-caption-down::before {
|
.icon-move-caption-up::before {
|
||||||
content: "\f193";
|
content: "\f193";
|
||||||
}
|
}
|
||||||
.icon-more::before {
|
.icon-move-caption-down::before {
|
||||||
content: "\f194";
|
content: "\f194";
|
||||||
}
|
}
|
||||||
.icon-more-circle::before {
|
.icon-more::before {
|
||||||
content: "\f195";
|
content: "\f195";
|
||||||
}
|
}
|
||||||
.icon-monospace::before {
|
.icon-more-circle::before {
|
||||||
content: "\f196";
|
content: "\f196";
|
||||||
}
|
}
|
||||||
.icon-microphone::before {
|
.icon-monospace::before {
|
||||||
content: "\f197";
|
content: "\f197";
|
||||||
}
|
}
|
||||||
.icon-microphone-alt::before {
|
.icon-microphone::before {
|
||||||
content: "\f198";
|
content: "\f198";
|
||||||
}
|
}
|
||||||
.icon-message::before {
|
.icon-microphone-alt::before {
|
||||||
content: "\f199";
|
content: "\f199";
|
||||||
}
|
}
|
||||||
.icon-message-succeeded::before {
|
.icon-message::before {
|
||||||
content: "\f19a";
|
content: "\f19a";
|
||||||
}
|
}
|
||||||
.icon-message-read::before {
|
.icon-message-succeeded::before {
|
||||||
content: "\f19b";
|
content: "\f19b";
|
||||||
}
|
}
|
||||||
.icon-message-pending::before {
|
.icon-message-read::before {
|
||||||
content: "\f19c";
|
content: "\f19c";
|
||||||
}
|
}
|
||||||
.icon-message-failed::before {
|
.icon-message-pending::before {
|
||||||
content: "\f19d";
|
content: "\f19d";
|
||||||
}
|
}
|
||||||
.icon-menu::before {
|
.icon-message-failed::before {
|
||||||
content: "\f19e";
|
content: "\f19e";
|
||||||
}
|
}
|
||||||
.icon-mention::before {
|
.icon-menu::before {
|
||||||
content: "\f19f";
|
content: "\f19f";
|
||||||
}
|
}
|
||||||
.icon-loop::before {
|
.icon-mention::before {
|
||||||
content: "\f1a0";
|
content: "\f1a0";
|
||||||
}
|
}
|
||||||
.icon-logout::before {
|
.icon-loop::before {
|
||||||
content: "\f1a1";
|
content: "\f1a1";
|
||||||
}
|
}
|
||||||
.icon-lock::before {
|
.icon-logout::before {
|
||||||
content: "\f1a2";
|
content: "\f1a2";
|
||||||
}
|
}
|
||||||
.icon-lock-badge::before {
|
.icon-lock::before {
|
||||||
content: "\f1a3";
|
content: "\f1a3";
|
||||||
}
|
}
|
||||||
.icon-location::before {
|
.icon-lock-badge::before {
|
||||||
content: "\f1a4";
|
content: "\f1a4";
|
||||||
}
|
}
|
||||||
.icon-link::before {
|
.icon-location::before {
|
||||||
content: "\f1a5";
|
content: "\f1a5";
|
||||||
}
|
}
|
||||||
.icon-link-broken::before {
|
.icon-link::before {
|
||||||
content: "\f1a6";
|
content: "\f1a6";
|
||||||
}
|
}
|
||||||
.icon-link-badge::before {
|
.icon-link-broken::before {
|
||||||
content: "\f1a7";
|
content: "\f1a7";
|
||||||
}
|
}
|
||||||
.icon-large-play::before {
|
.icon-link-badge::before {
|
||||||
content: "\f1a8";
|
content: "\f1a8";
|
||||||
}
|
}
|
||||||
.icon-large-pause::before {
|
.icon-large-play::before {
|
||||||
content: "\f1a9";
|
content: "\f1a9";
|
||||||
}
|
}
|
||||||
.icon-language::before {
|
.icon-large-pause::before {
|
||||||
content: "\f1aa";
|
content: "\f1aa";
|
||||||
}
|
}
|
||||||
.icon-lamp::before {
|
.icon-language::before {
|
||||||
content: "\f1ab";
|
content: "\f1ab";
|
||||||
}
|
}
|
||||||
.icon-keyboard::before {
|
.icon-lamp::before {
|
||||||
content: "\f1ac";
|
content: "\f1ac";
|
||||||
}
|
}
|
||||||
.icon-key::before {
|
.icon-keyboard::before {
|
||||||
content: "\f1ad";
|
content: "\f1ad";
|
||||||
}
|
}
|
||||||
.icon-italic::before {
|
.icon-key::before {
|
||||||
content: "\f1ae";
|
content: "\f1ae";
|
||||||
}
|
}
|
||||||
.icon-install::before {
|
.icon-italic::before {
|
||||||
content: "\f1af";
|
content: "\f1af";
|
||||||
}
|
}
|
||||||
.icon-info::before {
|
.icon-install::before {
|
||||||
content: "\f1b0";
|
content: "\f1b0";
|
||||||
}
|
}
|
||||||
.icon-info-filled::before {
|
.icon-info::before {
|
||||||
content: "\f1b1";
|
content: "\f1b1";
|
||||||
}
|
}
|
||||||
.icon-help::before {
|
.icon-info-filled::before {
|
||||||
content: "\f1b2";
|
content: "\f1b2";
|
||||||
}
|
}
|
||||||
.icon-heart::before {
|
.icon-help::before {
|
||||||
content: "\f1b3";
|
content: "\f1b3";
|
||||||
}
|
}
|
||||||
.icon-heart-outline::before {
|
.icon-heart::before {
|
||||||
content: "\f1b4";
|
content: "\f1b4";
|
||||||
}
|
}
|
||||||
.icon-hd-photo::before {
|
.icon-heart-outline::before {
|
||||||
content: "\f1b5";
|
content: "\f1b5";
|
||||||
}
|
}
|
||||||
.icon-hashtag::before {
|
.icon-hd-photo::before {
|
||||||
content: "\f1b6";
|
content: "\f1b6";
|
||||||
}
|
}
|
||||||
.icon-hand-stop::before {
|
.icon-hashtag::before {
|
||||||
content: "\f1b7";
|
content: "\f1b7";
|
||||||
}
|
}
|
||||||
.icon-hand-stop-filled::before {
|
.icon-hand-stop::before {
|
||||||
content: "\f1b8";
|
content: "\f1b8";
|
||||||
}
|
}
|
||||||
.icon-grouped::before {
|
.icon-hand-stop-filled::before {
|
||||||
content: "\f1b9";
|
content: "\f1b9";
|
||||||
}
|
}
|
||||||
.icon-grouped-disable::before {
|
.icon-grouped::before {
|
||||||
content: "\f1ba";
|
content: "\f1ba";
|
||||||
}
|
}
|
||||||
.icon-group::before {
|
.icon-grouped-disable::before {
|
||||||
content: "\f1bb";
|
content: "\f1bb";
|
||||||
}
|
}
|
||||||
.icon-group-filled::before {
|
.icon-group::before {
|
||||||
content: "\f1bc";
|
content: "\f1bc";
|
||||||
}
|
}
|
||||||
.icon-gift::before {
|
.icon-group-filled::before {
|
||||||
content: "\f1bd";
|
content: "\f1bd";
|
||||||
}
|
}
|
||||||
.icon-gift-transfer-inline::before {
|
.icon-gift::before {
|
||||||
content: "\f1be";
|
content: "\f1be";
|
||||||
}
|
}
|
||||||
.icon-gifs::before {
|
.icon-gift-transfer-inline::before {
|
||||||
content: "\f1bf";
|
content: "\f1bf";
|
||||||
}
|
}
|
||||||
.icon-fullscreen::before {
|
.icon-gifs::before {
|
||||||
content: "\f1c0";
|
content: "\f1c0";
|
||||||
}
|
}
|
||||||
.icon-frozen-time::before {
|
.icon-fullscreen::before {
|
||||||
content: "\f1c1";
|
content: "\f1c1";
|
||||||
}
|
}
|
||||||
.icon-fragment::before {
|
.icon-frozen-time::before {
|
||||||
content: "\f1c2";
|
content: "\f1c2";
|
||||||
}
|
}
|
||||||
.icon-forward::before {
|
.icon-fragment::before {
|
||||||
content: "\f1c3";
|
content: "\f1c3";
|
||||||
}
|
}
|
||||||
.icon-forums::before {
|
.icon-forward::before {
|
||||||
content: "\f1c4";
|
content: "\f1c4";
|
||||||
}
|
}
|
||||||
.icon-fontsize::before {
|
.icon-forums::before {
|
||||||
content: "\f1c5";
|
content: "\f1c5";
|
||||||
}
|
}
|
||||||
.icon-folder::before {
|
.icon-fontsize::before {
|
||||||
content: "\f1c6";
|
content: "\f1c6";
|
||||||
}
|
}
|
||||||
.icon-folder-badge::before {
|
.icon-folder::before {
|
||||||
content: "\f1c7";
|
content: "\f1c7";
|
||||||
}
|
}
|
||||||
.icon-flip::before {
|
.icon-folder-badge::before {
|
||||||
content: "\f1c8";
|
content: "\f1c8";
|
||||||
}
|
}
|
||||||
.icon-flag::before {
|
.icon-flip::before {
|
||||||
content: "\f1c9";
|
content: "\f1c9";
|
||||||
}
|
}
|
||||||
.icon-file-badge::before {
|
.icon-flag::before {
|
||||||
content: "\f1ca";
|
content: "\f1ca";
|
||||||
}
|
}
|
||||||
.icon-favorite::before {
|
.icon-file-badge::before {
|
||||||
content: "\f1cb";
|
content: "\f1cb";
|
||||||
}
|
}
|
||||||
.icon-favorite-filled::before {
|
.icon-favorite::before {
|
||||||
content: "\f1cc";
|
content: "\f1cc";
|
||||||
}
|
}
|
||||||
.icon-eye::before {
|
.icon-favorite-filled::before {
|
||||||
content: "\f1cd";
|
content: "\f1cd";
|
||||||
}
|
}
|
||||||
.icon-eye-outline::before {
|
.icon-eye::before {
|
||||||
content: "\f1ce";
|
content: "\f1ce";
|
||||||
}
|
}
|
||||||
.icon-eye-crossed::before {
|
.icon-eye-outline::before {
|
||||||
content: "\f1cf";
|
content: "\f1cf";
|
||||||
}
|
}
|
||||||
.icon-eye-crossed-outline::before {
|
.icon-eye-crossed::before {
|
||||||
content: "\f1d0";
|
content: "\f1d0";
|
||||||
}
|
}
|
||||||
.icon-expand::before {
|
.icon-eye-crossed-outline::before {
|
||||||
content: "\f1d1";
|
content: "\f1d1";
|
||||||
}
|
}
|
||||||
.icon-expand-modal::before {
|
.icon-expand::before {
|
||||||
content: "\f1d2";
|
content: "\f1d2";
|
||||||
}
|
}
|
||||||
.icon-enter::before {
|
.icon-expand-modal::before {
|
||||||
content: "\f1d3";
|
content: "\f1d3";
|
||||||
}
|
}
|
||||||
.icon-email::before {
|
.icon-enter::before {
|
||||||
content: "\f1d4";
|
content: "\f1d4";
|
||||||
}
|
}
|
||||||
.icon-edit::before {
|
.icon-email::before {
|
||||||
content: "\f1d5";
|
content: "\f1d5";
|
||||||
}
|
}
|
||||||
.icon-eats::before {
|
.icon-edit::before {
|
||||||
content: "\f1d6";
|
content: "\f1d6";
|
||||||
}
|
}
|
||||||
.icon-dropdown-arrows::before {
|
.icon-eats::before {
|
||||||
content: "\f1d7";
|
content: "\f1d7";
|
||||||
}
|
}
|
||||||
.icon-download::before {
|
.icon-dropdown-arrows::before {
|
||||||
content: "\f1d8";
|
content: "\f1d8";
|
||||||
}
|
}
|
||||||
.icon-down::before {
|
.icon-download::before {
|
||||||
content: "\f1d9";
|
content: "\f1d9";
|
||||||
}
|
}
|
||||||
.icon-double-badge::before {
|
.icon-down::before {
|
||||||
content: "\f1da";
|
content: "\f1da";
|
||||||
}
|
}
|
||||||
.icon-document::before {
|
.icon-double-badge::before {
|
||||||
content: "\f1db";
|
content: "\f1db";
|
||||||
}
|
}
|
||||||
.icon-diamond::before {
|
.icon-document::before {
|
||||||
content: "\f1dc";
|
content: "\f1dc";
|
||||||
}
|
}
|
||||||
.icon-delete::before {
|
.icon-diamond::before {
|
||||||
content: "\f1dd";
|
content: "\f1dd";
|
||||||
}
|
}
|
||||||
.icon-delete-user::before {
|
.icon-delete::before {
|
||||||
content: "\f1de";
|
content: "\f1de";
|
||||||
}
|
}
|
||||||
.icon-delete-left::before {
|
.icon-delete-user::before {
|
||||||
content: "\f1df";
|
content: "\f1df";
|
||||||
}
|
}
|
||||||
.icon-delete-filled::before {
|
.icon-delete-left::before {
|
||||||
content: "\f1e0";
|
content: "\f1e0";
|
||||||
}
|
}
|
||||||
.icon-data::before {
|
.icon-delete-filled::before {
|
||||||
content: "\f1e1";
|
content: "\f1e1";
|
||||||
}
|
}
|
||||||
.icon-darkmode::before {
|
.icon-data::before {
|
||||||
content: "\f1e2";
|
content: "\f1e2";
|
||||||
}
|
}
|
||||||
.icon-crown-wear::before {
|
.icon-darkmode::before {
|
||||||
content: "\f1e3";
|
content: "\f1e3";
|
||||||
}
|
}
|
||||||
.icon-crown-wear-outline::before {
|
.icon-crown-wear::before {
|
||||||
content: "\f1e4";
|
content: "\f1e4";
|
||||||
}
|
}
|
||||||
.icon-crown-take-off::before {
|
.icon-crown-wear-outline::before {
|
||||||
content: "\f1e5";
|
content: "\f1e5";
|
||||||
}
|
}
|
||||||
.icon-crown-take-off-outline::before {
|
.icon-crown-take-off::before {
|
||||||
content: "\f1e6";
|
content: "\f1e6";
|
||||||
}
|
}
|
||||||
.icon-crop::before {
|
.icon-crown-take-off-outline::before {
|
||||||
content: "\f1e7";
|
content: "\f1e7";
|
||||||
}
|
}
|
||||||
.icon-craft::before {
|
.icon-crop::before {
|
||||||
content: "\f1e8";
|
content: "\f1e8";
|
||||||
}
|
}
|
||||||
.icon-copy::before {
|
.icon-craft::before {
|
||||||
content: "\f1e9";
|
content: "\f1e9";
|
||||||
}
|
}
|
||||||
.icon-copy-media::before {
|
.icon-copy::before {
|
||||||
content: "\f1ea";
|
content: "\f1ea";
|
||||||
}
|
}
|
||||||
.icon-comments::before {
|
.icon-copy-media::before {
|
||||||
content: "\f1eb";
|
content: "\f1eb";
|
||||||
}
|
}
|
||||||
.icon-comments-sticker::before {
|
.icon-comments::before {
|
||||||
content: "\f1ec";
|
content: "\f1ec";
|
||||||
}
|
}
|
||||||
.icon-combine-craft::before {
|
.icon-comments-sticker::before {
|
||||||
content: "\f1ed";
|
content: "\f1ed";
|
||||||
}
|
}
|
||||||
.icon-colorize::before {
|
.icon-combine-craft::before {
|
||||||
content: "\f1ee";
|
content: "\f1ee";
|
||||||
}
|
}
|
||||||
.icon-collapse::before {
|
.icon-colorize::before {
|
||||||
content: "\f1ef";
|
content: "\f1ef";
|
||||||
}
|
}
|
||||||
.icon-collapse-modal::before {
|
.icon-collapse::before {
|
||||||
content: "\f1f0";
|
content: "\f1f0";
|
||||||
}
|
}
|
||||||
.icon-cloud-download::before {
|
.icon-collapse-modal::before {
|
||||||
content: "\f1f1";
|
content: "\f1f1";
|
||||||
}
|
}
|
||||||
.icon-closed-gift::before {
|
.icon-cloud-download::before {
|
||||||
content: "\f1f2";
|
content: "\f1f2";
|
||||||
}
|
}
|
||||||
.icon-close::before {
|
.icon-closed-gift::before {
|
||||||
content: "\f1f3";
|
content: "\f1f3";
|
||||||
}
|
}
|
||||||
.icon-close-topic::before {
|
.icon-close::before {
|
||||||
content: "\f1f4";
|
content: "\f1f4";
|
||||||
}
|
}
|
||||||
.icon-close-circle::before {
|
.icon-close-topic::before {
|
||||||
content: "\f1f5";
|
content: "\f1f5";
|
||||||
}
|
}
|
||||||
.icon-clock::before {
|
.icon-close-circle::before {
|
||||||
content: "\f1f6";
|
content: "\f1f6";
|
||||||
}
|
}
|
||||||
.icon-clock-edit::before {
|
.icon-clock::before {
|
||||||
content: "\f1f7";
|
content: "\f1f7";
|
||||||
}
|
}
|
||||||
.icon-check::before {
|
.icon-clock-edit::before {
|
||||||
content: "\f1f8";
|
content: "\f1f8";
|
||||||
}
|
}
|
||||||
.icon-chats-badge::before {
|
.icon-check::before {
|
||||||
content: "\f1f9";
|
content: "\f1f9";
|
||||||
}
|
}
|
||||||
.icon-chat-badge::before {
|
.icon-check-bold::before {
|
||||||
content: "\f1fa";
|
content: "\f1fa";
|
||||||
}
|
}
|
||||||
.icon-channelviews::before {
|
.icon-chats-badge::before {
|
||||||
content: "\f1fb";
|
content: "\f1fb";
|
||||||
}
|
}
|
||||||
.icon-channel::before {
|
.icon-chat-badge::before {
|
||||||
content: "\f1fc";
|
content: "\f1fc";
|
||||||
}
|
}
|
||||||
.icon-channel-filled::before {
|
.icon-channelviews::before {
|
||||||
content: "\f1fd";
|
content: "\f1fd";
|
||||||
}
|
}
|
||||||
.icon-cash-circle::before {
|
.icon-channel::before {
|
||||||
content: "\f1fe";
|
content: "\f1fe";
|
||||||
}
|
}
|
||||||
.icon-card::before {
|
.icon-channel-filled::before {
|
||||||
content: "\f1ff";
|
content: "\f1ff";
|
||||||
}
|
}
|
||||||
.icon-car::before {
|
.icon-cash-circle::before {
|
||||||
content: "\f200";
|
content: "\f200";
|
||||||
}
|
}
|
||||||
.icon-camera::before {
|
.icon-card::before {
|
||||||
content: "\f201";
|
content: "\f201";
|
||||||
}
|
}
|
||||||
.icon-camera-add::before {
|
.icon-car::before {
|
||||||
content: "\f202";
|
content: "\f202";
|
||||||
}
|
}
|
||||||
.icon-calendar::before {
|
.icon-camera::before {
|
||||||
content: "\f203";
|
content: "\f203";
|
||||||
}
|
}
|
||||||
.icon-calendar-filter::before {
|
.icon-camera-add::before {
|
||||||
content: "\f204";
|
content: "\f204";
|
||||||
}
|
}
|
||||||
.icon-bug::before {
|
.icon-calendar::before {
|
||||||
content: "\f205";
|
content: "\f205";
|
||||||
}
|
}
|
||||||
.icon-brush::before {
|
.icon-calendar-filter::before {
|
||||||
content: "\f206";
|
content: "\f206";
|
||||||
}
|
}
|
||||||
.icon-bots::before {
|
.icon-bug::before {
|
||||||
content: "\f207";
|
content: "\f207";
|
||||||
}
|
}
|
||||||
.icon-bot-commands-filled::before {
|
.icon-brush::before {
|
||||||
content: "\f208";
|
content: "\f208";
|
||||||
}
|
}
|
||||||
.icon-bot-command::before {
|
.icon-bots::before {
|
||||||
content: "\f209";
|
content: "\f209";
|
||||||
}
|
}
|
||||||
.icon-boosts::before {
|
.icon-bot-commands-filled::before {
|
||||||
content: "\f20a";
|
content: "\f20a";
|
||||||
}
|
}
|
||||||
.icon-boostcircle::before {
|
.icon-bot-command::before {
|
||||||
content: "\f20b";
|
content: "\f20b";
|
||||||
}
|
}
|
||||||
.icon-boost::before {
|
.icon-boosts::before {
|
||||||
content: "\f20c";
|
content: "\f20c";
|
||||||
}
|
}
|
||||||
.icon-boost-outline::before {
|
.icon-boostcircle::before {
|
||||||
content: "\f20d";
|
content: "\f20d";
|
||||||
}
|
}
|
||||||
.icon-boost-craft-chance::before {
|
.icon-boost::before {
|
||||||
content: "\f20e";
|
content: "\f20e";
|
||||||
}
|
}
|
||||||
.icon-bold::before {
|
.icon-boost-outline::before {
|
||||||
content: "\f20f";
|
content: "\f20f";
|
||||||
}
|
}
|
||||||
.icon-avatar-saved-messages::before {
|
.icon-boost-craft-chance::before {
|
||||||
content: "\f210";
|
content: "\f210";
|
||||||
}
|
}
|
||||||
.icon-avatar-deleted-account::before {
|
.icon-bold::before {
|
||||||
content: "\f211";
|
content: "\f211";
|
||||||
}
|
}
|
||||||
.icon-avatar-archived-chats::before {
|
.icon-avatar-saved-messages::before {
|
||||||
content: "\f212";
|
content: "\f212";
|
||||||
}
|
}
|
||||||
.icon-author-hidden::before {
|
.icon-avatar-deleted-account::before {
|
||||||
content: "\f213";
|
content: "\f213";
|
||||||
}
|
}
|
||||||
.icon-auction::before {
|
.icon-avatar-archived-chats::before {
|
||||||
content: "\f214";
|
content: "\f214";
|
||||||
}
|
}
|
||||||
.icon-auction-next-round::before {
|
.icon-author-hidden::before {
|
||||||
content: "\f215";
|
content: "\f215";
|
||||||
}
|
}
|
||||||
.icon-auction-filled::before {
|
.icon-auction::before {
|
||||||
content: "\f216";
|
content: "\f216";
|
||||||
}
|
}
|
||||||
.icon-auction-drop::before {
|
.icon-auction-next-round::before {
|
||||||
content: "\f217";
|
content: "\f217";
|
||||||
}
|
}
|
||||||
.icon-attach::before {
|
.icon-auction-filled::before {
|
||||||
content: "\f218";
|
content: "\f218";
|
||||||
}
|
}
|
||||||
.icon-ask-support::before {
|
.icon-auction-drop::before {
|
||||||
content: "\f219";
|
content: "\f219";
|
||||||
}
|
}
|
||||||
.icon-arrow-right::before {
|
.icon-attach::before {
|
||||||
content: "\f21a";
|
content: "\f21a";
|
||||||
}
|
}
|
||||||
.icon-arrow-left::before {
|
.icon-ask-support::before {
|
||||||
content: "\f21b";
|
content: "\f21b";
|
||||||
}
|
}
|
||||||
.icon-arrow-down::before {
|
.icon-arrow-right::before {
|
||||||
content: "\f21c";
|
content: "\f21c";
|
||||||
}
|
}
|
||||||
.icon-arrow-down-circle::before {
|
.icon-arrow-left::before {
|
||||||
content: "\f21d";
|
content: "\f21d";
|
||||||
}
|
}
|
||||||
.icon-archive::before {
|
.icon-arrow-down::before {
|
||||||
content: "\f21e";
|
content: "\f21e";
|
||||||
}
|
}
|
||||||
.icon-archive-to-main::before {
|
.icon-arrow-down-circle::before {
|
||||||
content: "\f21f";
|
content: "\f21f";
|
||||||
}
|
}
|
||||||
.icon-archive-from-main::before {
|
.icon-archive::before {
|
||||||
content: "\f220";
|
content: "\f220";
|
||||||
}
|
}
|
||||||
.icon-archive-filled::before {
|
.icon-archive-to-main::before {
|
||||||
content: "\f221";
|
content: "\f221";
|
||||||
}
|
}
|
||||||
.icon-animations::before {
|
.icon-archive-from-main::before {
|
||||||
content: "\f222";
|
content: "\f222";
|
||||||
}
|
}
|
||||||
.icon-animals::before {
|
.icon-archive-filled::before {
|
||||||
content: "\f223";
|
content: "\f223";
|
||||||
}
|
}
|
||||||
.icon-allow-speak::before {
|
.icon-animations::before {
|
||||||
content: "\f224";
|
content: "\f224";
|
||||||
}
|
}
|
||||||
.icon-allow-share::before {
|
.icon-animals::before {
|
||||||
content: "\f225";
|
content: "\f225";
|
||||||
}
|
}
|
||||||
.icon-admin::before {
|
.icon-allow-speak::before {
|
||||||
content: "\f226";
|
content: "\f226";
|
||||||
}
|
}
|
||||||
.icon-add::before {
|
.icon-allow-share::before {
|
||||||
content: "\f227";
|
content: "\f227";
|
||||||
}
|
}
|
||||||
.icon-add-user::before {
|
.icon-admin::before {
|
||||||
content: "\f228";
|
content: "\f228";
|
||||||
}
|
}
|
||||||
.icon-add-user-filled::before {
|
.icon-add::before {
|
||||||
content: "\f229";
|
content: "\f229";
|
||||||
}
|
}
|
||||||
.icon-add-one-badge::before {
|
.icon-add-user::before {
|
||||||
content: "\f22a";
|
content: "\f22a";
|
||||||
}
|
}
|
||||||
.icon-add-filled::before {
|
.icon-add-user-filled::before {
|
||||||
content: "\f22b";
|
content: "\f22b";
|
||||||
}
|
}
|
||||||
.icon-add-caption::before {
|
.icon-add-one-badge::before {
|
||||||
content: "\f22c";
|
content: "\f22c";
|
||||||
}
|
}
|
||||||
.icon-active-sessions::before {
|
.icon-add-filled::before {
|
||||||
content: "\f22d";
|
content: "\f22d";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-negative::before {
|
.icon-add-caption::before {
|
||||||
content: "\f22e";
|
content: "\f22e";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level90::before {
|
.icon-active-sessions::before {
|
||||||
content: "\f22f";
|
content: "\f22f";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level9::before {
|
.icon-rating-icons-negative::before {
|
||||||
content: "\f230";
|
content: "\f230";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level80::before {
|
.icon-rating-icons-level90::before {
|
||||||
content: "\f231";
|
content: "\f231";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level8::before {
|
.icon-rating-icons-level9::before {
|
||||||
content: "\f232";
|
content: "\f232";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level70::before {
|
.icon-rating-icons-level80::before {
|
||||||
content: "\f233";
|
content: "\f233";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level7::before {
|
.icon-rating-icons-level8::before {
|
||||||
content: "\f234";
|
content: "\f234";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level60::before {
|
.icon-rating-icons-level70::before {
|
||||||
content: "\f235";
|
content: "\f235";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level6::before {
|
.icon-rating-icons-level7::before {
|
||||||
content: "\f236";
|
content: "\f236";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level50::before {
|
.icon-rating-icons-level60::before {
|
||||||
content: "\f237";
|
content: "\f237";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level5::before {
|
.icon-rating-icons-level6::before {
|
||||||
content: "\f238";
|
content: "\f238";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level40::before {
|
.icon-rating-icons-level50::before {
|
||||||
content: "\f239";
|
content: "\f239";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level4::before {
|
.icon-rating-icons-level5::before {
|
||||||
content: "\f23a";
|
content: "\f23a";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level30::before {
|
.icon-rating-icons-level40::before {
|
||||||
content: "\f23b";
|
content: "\f23b";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level3::before {
|
.icon-rating-icons-level4::before {
|
||||||
content: "\f23c";
|
content: "\f23c";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level20::before {
|
.icon-rating-icons-level30::before {
|
||||||
content: "\f23d";
|
content: "\f23d";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level2::before {
|
.icon-rating-icons-level3::before {
|
||||||
content: "\f23e";
|
content: "\f23e";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level10::before {
|
.icon-rating-icons-level20::before {
|
||||||
content: "\f23f";
|
content: "\f23f";
|
||||||
}
|
}
|
||||||
.icon-rating-icons-level1::before {
|
.icon-rating-icons-level2::before {
|
||||||
content: "\f240";
|
content: "\f240";
|
||||||
}
|
}
|
||||||
.icon-folder-tabs-user::before {
|
.icon-rating-icons-level10::before {
|
||||||
content: "\f241";
|
content: "\f241";
|
||||||
}
|
}
|
||||||
.icon-folder-tabs-star::before {
|
.icon-rating-icons-level1::before {
|
||||||
content: "\f242";
|
content: "\f242";
|
||||||
}
|
}
|
||||||
.icon-folder-tabs-group::before {
|
.icon-folder-tabs-user::before {
|
||||||
content: "\f243";
|
content: "\f243";
|
||||||
}
|
}
|
||||||
.icon-folder-tabs-folder::before {
|
.icon-folder-tabs-star::before {
|
||||||
content: "\f244";
|
content: "\f244";
|
||||||
}
|
}
|
||||||
.icon-folder-tabs-chats::before {
|
.icon-folder-tabs-group::before {
|
||||||
content: "\f245";
|
content: "\f245";
|
||||||
}
|
}
|
||||||
.icon-folder-tabs-chat::before {
|
.icon-folder-tabs-folder::before {
|
||||||
content: "\f246";
|
content: "\f246";
|
||||||
}
|
}
|
||||||
.icon-folder-tabs-channel::before {
|
.icon-folder-tabs-chats::before {
|
||||||
content: "\f247";
|
content: "\f247";
|
||||||
}
|
}
|
||||||
.icon-folder-tabs-bot::before {
|
.icon-folder-tabs-chat::before {
|
||||||
content: "\f248";
|
content: "\f248";
|
||||||
}
|
}
|
||||||
|
.icon-folder-tabs-channel::before {
|
||||||
|
content: "\f249";
|
||||||
|
}
|
||||||
|
.icon-folder-tabs-bot::before {
|
||||||
|
content: "\f24a";
|
||||||
|
}
|
||||||
|
|||||||
@ -157,191 +157,193 @@ $icons-map: (
|
|||||||
"no-download": "\f18b",
|
"no-download": "\f18b",
|
||||||
"next": "\f18c",
|
"next": "\f18c",
|
||||||
"next-link": "\f18d",
|
"next-link": "\f18d",
|
||||||
"new-chat-filled": "\f18e",
|
"new-send": "\f18e",
|
||||||
"my-notes": "\f18f",
|
"new-chat-filled": "\f18f",
|
||||||
"muted": "\f190",
|
"my-notes": "\f190",
|
||||||
"mute": "\f191",
|
"muted": "\f191",
|
||||||
"move-caption-up": "\f192",
|
"mute": "\f192",
|
||||||
"move-caption-down": "\f193",
|
"move-caption-up": "\f193",
|
||||||
"more": "\f194",
|
"move-caption-down": "\f194",
|
||||||
"more-circle": "\f195",
|
"more": "\f195",
|
||||||
"monospace": "\f196",
|
"more-circle": "\f196",
|
||||||
"microphone": "\f197",
|
"monospace": "\f197",
|
||||||
"microphone-alt": "\f198",
|
"microphone": "\f198",
|
||||||
"message": "\f199",
|
"microphone-alt": "\f199",
|
||||||
"message-succeeded": "\f19a",
|
"message": "\f19a",
|
||||||
"message-read": "\f19b",
|
"message-succeeded": "\f19b",
|
||||||
"message-pending": "\f19c",
|
"message-read": "\f19c",
|
||||||
"message-failed": "\f19d",
|
"message-pending": "\f19d",
|
||||||
"menu": "\f19e",
|
"message-failed": "\f19e",
|
||||||
"mention": "\f19f",
|
"menu": "\f19f",
|
||||||
"loop": "\f1a0",
|
"mention": "\f1a0",
|
||||||
"logout": "\f1a1",
|
"loop": "\f1a1",
|
||||||
"lock": "\f1a2",
|
"logout": "\f1a2",
|
||||||
"lock-badge": "\f1a3",
|
"lock": "\f1a3",
|
||||||
"location": "\f1a4",
|
"lock-badge": "\f1a4",
|
||||||
"link": "\f1a5",
|
"location": "\f1a5",
|
||||||
"link-broken": "\f1a6",
|
"link": "\f1a6",
|
||||||
"link-badge": "\f1a7",
|
"link-broken": "\f1a7",
|
||||||
"large-play": "\f1a8",
|
"link-badge": "\f1a8",
|
||||||
"large-pause": "\f1a9",
|
"large-play": "\f1a9",
|
||||||
"language": "\f1aa",
|
"large-pause": "\f1aa",
|
||||||
"lamp": "\f1ab",
|
"language": "\f1ab",
|
||||||
"keyboard": "\f1ac",
|
"lamp": "\f1ac",
|
||||||
"key": "\f1ad",
|
"keyboard": "\f1ad",
|
||||||
"italic": "\f1ae",
|
"key": "\f1ae",
|
||||||
"install": "\f1af",
|
"italic": "\f1af",
|
||||||
"info": "\f1b0",
|
"install": "\f1b0",
|
||||||
"info-filled": "\f1b1",
|
"info": "\f1b1",
|
||||||
"help": "\f1b2",
|
"info-filled": "\f1b2",
|
||||||
"heart": "\f1b3",
|
"help": "\f1b3",
|
||||||
"heart-outline": "\f1b4",
|
"heart": "\f1b4",
|
||||||
"hd-photo": "\f1b5",
|
"heart-outline": "\f1b5",
|
||||||
"hashtag": "\f1b6",
|
"hd-photo": "\f1b6",
|
||||||
"hand-stop": "\f1b7",
|
"hashtag": "\f1b7",
|
||||||
"hand-stop-filled": "\f1b8",
|
"hand-stop": "\f1b8",
|
||||||
"grouped": "\f1b9",
|
"hand-stop-filled": "\f1b9",
|
||||||
"grouped-disable": "\f1ba",
|
"grouped": "\f1ba",
|
||||||
"group": "\f1bb",
|
"grouped-disable": "\f1bb",
|
||||||
"group-filled": "\f1bc",
|
"group": "\f1bc",
|
||||||
"gift": "\f1bd",
|
"group-filled": "\f1bd",
|
||||||
"gift-transfer-inline": "\f1be",
|
"gift": "\f1be",
|
||||||
"gifs": "\f1bf",
|
"gift-transfer-inline": "\f1bf",
|
||||||
"fullscreen": "\f1c0",
|
"gifs": "\f1c0",
|
||||||
"frozen-time": "\f1c1",
|
"fullscreen": "\f1c1",
|
||||||
"fragment": "\f1c2",
|
"frozen-time": "\f1c2",
|
||||||
"forward": "\f1c3",
|
"fragment": "\f1c3",
|
||||||
"forums": "\f1c4",
|
"forward": "\f1c4",
|
||||||
"fontsize": "\f1c5",
|
"forums": "\f1c5",
|
||||||
"folder": "\f1c6",
|
"fontsize": "\f1c6",
|
||||||
"folder-badge": "\f1c7",
|
"folder": "\f1c7",
|
||||||
"flip": "\f1c8",
|
"folder-badge": "\f1c8",
|
||||||
"flag": "\f1c9",
|
"flip": "\f1c9",
|
||||||
"file-badge": "\f1ca",
|
"flag": "\f1ca",
|
||||||
"favorite": "\f1cb",
|
"file-badge": "\f1cb",
|
||||||
"favorite-filled": "\f1cc",
|
"favorite": "\f1cc",
|
||||||
"eye": "\f1cd",
|
"favorite-filled": "\f1cd",
|
||||||
"eye-outline": "\f1ce",
|
"eye": "\f1ce",
|
||||||
"eye-crossed": "\f1cf",
|
"eye-outline": "\f1cf",
|
||||||
"eye-crossed-outline": "\f1d0",
|
"eye-crossed": "\f1d0",
|
||||||
"expand": "\f1d1",
|
"eye-crossed-outline": "\f1d1",
|
||||||
"expand-modal": "\f1d2",
|
"expand": "\f1d2",
|
||||||
"enter": "\f1d3",
|
"expand-modal": "\f1d3",
|
||||||
"email": "\f1d4",
|
"enter": "\f1d4",
|
||||||
"edit": "\f1d5",
|
"email": "\f1d5",
|
||||||
"eats": "\f1d6",
|
"edit": "\f1d6",
|
||||||
"dropdown-arrows": "\f1d7",
|
"eats": "\f1d7",
|
||||||
"download": "\f1d8",
|
"dropdown-arrows": "\f1d8",
|
||||||
"down": "\f1d9",
|
"download": "\f1d9",
|
||||||
"double-badge": "\f1da",
|
"down": "\f1da",
|
||||||
"document": "\f1db",
|
"double-badge": "\f1db",
|
||||||
"diamond": "\f1dc",
|
"document": "\f1dc",
|
||||||
"delete": "\f1dd",
|
"diamond": "\f1dd",
|
||||||
"delete-user": "\f1de",
|
"delete": "\f1de",
|
||||||
"delete-left": "\f1df",
|
"delete-user": "\f1df",
|
||||||
"delete-filled": "\f1e0",
|
"delete-left": "\f1e0",
|
||||||
"data": "\f1e1",
|
"delete-filled": "\f1e1",
|
||||||
"darkmode": "\f1e2",
|
"data": "\f1e2",
|
||||||
"crown-wear": "\f1e3",
|
"darkmode": "\f1e3",
|
||||||
"crown-wear-outline": "\f1e4",
|
"crown-wear": "\f1e4",
|
||||||
"crown-take-off": "\f1e5",
|
"crown-wear-outline": "\f1e5",
|
||||||
"crown-take-off-outline": "\f1e6",
|
"crown-take-off": "\f1e6",
|
||||||
"crop": "\f1e7",
|
"crown-take-off-outline": "\f1e7",
|
||||||
"craft": "\f1e8",
|
"crop": "\f1e8",
|
||||||
"copy": "\f1e9",
|
"craft": "\f1e9",
|
||||||
"copy-media": "\f1ea",
|
"copy": "\f1ea",
|
||||||
"comments": "\f1eb",
|
"copy-media": "\f1eb",
|
||||||
"comments-sticker": "\f1ec",
|
"comments": "\f1ec",
|
||||||
"combine-craft": "\f1ed",
|
"comments-sticker": "\f1ed",
|
||||||
"colorize": "\f1ee",
|
"combine-craft": "\f1ee",
|
||||||
"collapse": "\f1ef",
|
"colorize": "\f1ef",
|
||||||
"collapse-modal": "\f1f0",
|
"collapse": "\f1f0",
|
||||||
"cloud-download": "\f1f1",
|
"collapse-modal": "\f1f1",
|
||||||
"closed-gift": "\f1f2",
|
"cloud-download": "\f1f2",
|
||||||
"close": "\f1f3",
|
"closed-gift": "\f1f3",
|
||||||
"close-topic": "\f1f4",
|
"close": "\f1f4",
|
||||||
"close-circle": "\f1f5",
|
"close-topic": "\f1f5",
|
||||||
"clock": "\f1f6",
|
"close-circle": "\f1f6",
|
||||||
"clock-edit": "\f1f7",
|
"clock": "\f1f7",
|
||||||
"check": "\f1f8",
|
"clock-edit": "\f1f8",
|
||||||
"chats-badge": "\f1f9",
|
"check": "\f1f9",
|
||||||
"chat-badge": "\f1fa",
|
"check-bold": "\f1fa",
|
||||||
"channelviews": "\f1fb",
|
"chats-badge": "\f1fb",
|
||||||
"channel": "\f1fc",
|
"chat-badge": "\f1fc",
|
||||||
"channel-filled": "\f1fd",
|
"channelviews": "\f1fd",
|
||||||
"cash-circle": "\f1fe",
|
"channel": "\f1fe",
|
||||||
"card": "\f1ff",
|
"channel-filled": "\f1ff",
|
||||||
"car": "\f200",
|
"cash-circle": "\f200",
|
||||||
"camera": "\f201",
|
"card": "\f201",
|
||||||
"camera-add": "\f202",
|
"car": "\f202",
|
||||||
"calendar": "\f203",
|
"camera": "\f203",
|
||||||
"calendar-filter": "\f204",
|
"camera-add": "\f204",
|
||||||
"bug": "\f205",
|
"calendar": "\f205",
|
||||||
"brush": "\f206",
|
"calendar-filter": "\f206",
|
||||||
"bots": "\f207",
|
"bug": "\f207",
|
||||||
"bot-commands-filled": "\f208",
|
"brush": "\f208",
|
||||||
"bot-command": "\f209",
|
"bots": "\f209",
|
||||||
"boosts": "\f20a",
|
"bot-commands-filled": "\f20a",
|
||||||
"boostcircle": "\f20b",
|
"bot-command": "\f20b",
|
||||||
"boost": "\f20c",
|
"boosts": "\f20c",
|
||||||
"boost-outline": "\f20d",
|
"boostcircle": "\f20d",
|
||||||
"boost-craft-chance": "\f20e",
|
"boost": "\f20e",
|
||||||
"bold": "\f20f",
|
"boost-outline": "\f20f",
|
||||||
"avatar-saved-messages": "\f210",
|
"boost-craft-chance": "\f210",
|
||||||
"avatar-deleted-account": "\f211",
|
"bold": "\f211",
|
||||||
"avatar-archived-chats": "\f212",
|
"avatar-saved-messages": "\f212",
|
||||||
"author-hidden": "\f213",
|
"avatar-deleted-account": "\f213",
|
||||||
"auction": "\f214",
|
"avatar-archived-chats": "\f214",
|
||||||
"auction-next-round": "\f215",
|
"author-hidden": "\f215",
|
||||||
"auction-filled": "\f216",
|
"auction": "\f216",
|
||||||
"auction-drop": "\f217",
|
"auction-next-round": "\f217",
|
||||||
"attach": "\f218",
|
"auction-filled": "\f218",
|
||||||
"ask-support": "\f219",
|
"auction-drop": "\f219",
|
||||||
"arrow-right": "\f21a",
|
"attach": "\f21a",
|
||||||
"arrow-left": "\f21b",
|
"ask-support": "\f21b",
|
||||||
"arrow-down": "\f21c",
|
"arrow-right": "\f21c",
|
||||||
"arrow-down-circle": "\f21d",
|
"arrow-left": "\f21d",
|
||||||
"archive": "\f21e",
|
"arrow-down": "\f21e",
|
||||||
"archive-to-main": "\f21f",
|
"arrow-down-circle": "\f21f",
|
||||||
"archive-from-main": "\f220",
|
"archive": "\f220",
|
||||||
"archive-filled": "\f221",
|
"archive-to-main": "\f221",
|
||||||
"animations": "\f222",
|
"archive-from-main": "\f222",
|
||||||
"animals": "\f223",
|
"archive-filled": "\f223",
|
||||||
"allow-speak": "\f224",
|
"animations": "\f224",
|
||||||
"allow-share": "\f225",
|
"animals": "\f225",
|
||||||
"admin": "\f226",
|
"allow-speak": "\f226",
|
||||||
"add": "\f227",
|
"allow-share": "\f227",
|
||||||
"add-user": "\f228",
|
"admin": "\f228",
|
||||||
"add-user-filled": "\f229",
|
"add": "\f229",
|
||||||
"add-one-badge": "\f22a",
|
"add-user": "\f22a",
|
||||||
"add-filled": "\f22b",
|
"add-user-filled": "\f22b",
|
||||||
"add-caption": "\f22c",
|
"add-one-badge": "\f22c",
|
||||||
"active-sessions": "\f22d",
|
"add-filled": "\f22d",
|
||||||
"rating-icons-negative": "\f22e",
|
"add-caption": "\f22e",
|
||||||
"rating-icons-level90": "\f22f",
|
"active-sessions": "\f22f",
|
||||||
"rating-icons-level9": "\f230",
|
"rating-icons-negative": "\f230",
|
||||||
"rating-icons-level80": "\f231",
|
"rating-icons-level90": "\f231",
|
||||||
"rating-icons-level8": "\f232",
|
"rating-icons-level9": "\f232",
|
||||||
"rating-icons-level70": "\f233",
|
"rating-icons-level80": "\f233",
|
||||||
"rating-icons-level7": "\f234",
|
"rating-icons-level8": "\f234",
|
||||||
"rating-icons-level60": "\f235",
|
"rating-icons-level70": "\f235",
|
||||||
"rating-icons-level6": "\f236",
|
"rating-icons-level7": "\f236",
|
||||||
"rating-icons-level50": "\f237",
|
"rating-icons-level60": "\f237",
|
||||||
"rating-icons-level5": "\f238",
|
"rating-icons-level6": "\f238",
|
||||||
"rating-icons-level40": "\f239",
|
"rating-icons-level50": "\f239",
|
||||||
"rating-icons-level4": "\f23a",
|
"rating-icons-level5": "\f23a",
|
||||||
"rating-icons-level30": "\f23b",
|
"rating-icons-level40": "\f23b",
|
||||||
"rating-icons-level3": "\f23c",
|
"rating-icons-level4": "\f23c",
|
||||||
"rating-icons-level20": "\f23d",
|
"rating-icons-level30": "\f23d",
|
||||||
"rating-icons-level2": "\f23e",
|
"rating-icons-level3": "\f23e",
|
||||||
"rating-icons-level10": "\f23f",
|
"rating-icons-level20": "\f23f",
|
||||||
"rating-icons-level1": "\f240",
|
"rating-icons-level2": "\f240",
|
||||||
"folder-tabs-user": "\f241",
|
"rating-icons-level10": "\f241",
|
||||||
"folder-tabs-star": "\f242",
|
"rating-icons-level1": "\f242",
|
||||||
"folder-tabs-group": "\f243",
|
"folder-tabs-user": "\f243",
|
||||||
"folder-tabs-folder": "\f244",
|
"folder-tabs-star": "\f244",
|
||||||
"folder-tabs-chats": "\f245",
|
"folder-tabs-group": "\f245",
|
||||||
"folder-tabs-chat": "\f246",
|
"folder-tabs-folder": "\f246",
|
||||||
"folder-tabs-channel": "\f247",
|
"folder-tabs-chats": "\f247",
|
||||||
"folder-tabs-bot": "\f248",
|
"folder-tabs-chat": "\f248",
|
||||||
|
"folder-tabs-channel": "\f249",
|
||||||
|
"folder-tabs-bot": "\f24a",
|
||||||
);
|
);
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -140,6 +140,7 @@ export type FontIconName =
|
|||||||
| 'no-download'
|
| 'no-download'
|
||||||
| 'next'
|
| 'next'
|
||||||
| 'next-link'
|
| 'next-link'
|
||||||
|
| 'new-send'
|
||||||
| 'new-chat-filled'
|
| 'new-chat-filled'
|
||||||
| 'my-notes'
|
| 'my-notes'
|
||||||
| 'muted'
|
| 'muted'
|
||||||
@ -247,6 +248,7 @@ export type FontIconName =
|
|||||||
| 'clock'
|
| 'clock'
|
||||||
| 'clock-edit'
|
| 'clock-edit'
|
||||||
| 'check'
|
| 'check'
|
||||||
|
| 'check-bold'
|
||||||
| 'chats-badge'
|
| 'chats-badge'
|
||||||
| 'chat-badge'
|
| 'chat-badge'
|
||||||
| 'channelviews'
|
| 'channelviews'
|
||||||
|
|||||||
22
src/types/language.d.ts
vendored
22
src/types/language.d.ts
vendored
@ -262,6 +262,7 @@ export interface LangPair {
|
|||||||
'DialogPin': undefined;
|
'DialogPin': undefined;
|
||||||
'ConversationPinMessageAlertPinAndNotifyMembers': undefined;
|
'ConversationPinMessageAlertPinAndNotifyMembers': undefined;
|
||||||
'SavedMessages': undefined;
|
'SavedMessages': undefined;
|
||||||
|
'SavedMessagesShort': undefined;
|
||||||
'AccDescrPrevious': undefined;
|
'AccDescrPrevious': undefined;
|
||||||
'ReportReasonSpam': undefined;
|
'ReportReasonSpam': undefined;
|
||||||
'ReportReasonViolence': undefined;
|
'ReportReasonViolence': undefined;
|
||||||
@ -526,6 +527,8 @@ export interface LangPair {
|
|||||||
'ContactShare': undefined;
|
'ContactShare': undefined;
|
||||||
'OK': undefined;
|
'OK': undefined;
|
||||||
'ForwardTo': undefined;
|
'ForwardTo': undefined;
|
||||||
|
'ShareWith': undefined;
|
||||||
|
'SelectChats': undefined;
|
||||||
'AttachGame': undefined;
|
'AttachGame': undefined;
|
||||||
'JumpToDate': undefined;
|
'JumpToDate': undefined;
|
||||||
'FloodWait': undefined;
|
'FloodWait': undefined;
|
||||||
@ -1978,8 +1981,6 @@ export interface LangPair {
|
|||||||
'AttachmentMenuUngroupAllMedia': undefined;
|
'AttachmentMenuUngroupAllMedia': undefined;
|
||||||
'AttachmentMenuEnableSpoiler': undefined;
|
'AttachmentMenuEnableSpoiler': undefined;
|
||||||
'AttachmentMenuDisableSpoiler': undefined;
|
'AttachmentMenuDisableSpoiler': undefined;
|
||||||
'AttachmentSendGif': undefined;
|
|
||||||
'AttachmentReplaceGif': undefined;
|
|
||||||
'AttachmentDragAddItems': undefined;
|
'AttachmentDragAddItems': undefined;
|
||||||
'AttachmentCaptionPlaceholder': undefined;
|
'AttachmentCaptionPlaceholder': undefined;
|
||||||
'MessageSummaryTitle': undefined;
|
'MessageSummaryTitle': undefined;
|
||||||
@ -2187,6 +2188,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'ConversationOpenBotLinkAllowMessages': {
|
'ConversationOpenBotLinkAllowMessages': {
|
||||||
'bot': V;
|
'bot': V;
|
||||||
};
|
};
|
||||||
|
'ForwardForStars': {
|
||||||
|
'price': V;
|
||||||
|
};
|
||||||
'BlockUserTitle': {
|
'BlockUserTitle': {
|
||||||
'user': V;
|
'user': V;
|
||||||
};
|
};
|
||||||
@ -3085,6 +3089,10 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'ComposerPlaceholderPaidReply': {
|
'ComposerPlaceholderPaidReply': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
};
|
};
|
||||||
|
'ForwardPaidChatsConfirmation': {
|
||||||
|
'chatsSelected': V;
|
||||||
|
'payConfirmation': V;
|
||||||
|
};
|
||||||
'MessageSentPaidToastText': {
|
'MessageSentPaidToastText': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
};
|
};
|
||||||
@ -3910,6 +3918,13 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
|||||||
'PayForMessage': {
|
'PayForMessage': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
'ForwardPaidChatsSelected': {
|
||||||
|
'paidChatsCount': V;
|
||||||
|
};
|
||||||
|
'ForwardPaidChatsPayConfirmation': {
|
||||||
|
'totalAmount': V;
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
'MessageSentPaidToastTitle': {
|
'MessageSentPaidToastTitle': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
@ -4045,6 +4060,9 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
|||||||
'GiftPreviewCountBackdrops': {
|
'GiftPreviewCountBackdrops': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
'FwdMessagesToChats': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type RegularLangKey = keyof LangPair;
|
export type RegularLangKey = keyof LangPair;
|
||||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user