ClosableEmbeddedMessage Menu: Reply in Another Chat (#4504)
This commit is contained in:
parent
6eaaa5dce5
commit
6e37c5b163
@ -409,6 +409,7 @@ export interface ApiInputMessageReplyInfo {
|
||||
replyToTopId?: number;
|
||||
replyToPeerId?: string;
|
||||
quoteText?: ApiFormattedText;
|
||||
isShowingDelayNeeded?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiInputStoryReplyInfo {
|
||||
@ -532,6 +533,9 @@ export type MediaContent = {
|
||||
isExpiredRoundVideo?: boolean;
|
||||
ttlSeconds?: number;
|
||||
};
|
||||
export type MediaContainer = {
|
||||
content: MediaContent;
|
||||
};
|
||||
|
||||
export interface ApiMessage {
|
||||
id: number;
|
||||
|
||||
1
src/assets/font-icons/remove-quote.svg
Normal file
1
src/assets/font-icons/remove-quote.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M20 2.667a2.667 2.667 0 0 0-.306 5.316 8.3 8.3 0 0 1-.562 1.45l-.32.64a1.328 1.328 0 0 0 2.376 1.188l.32-.64a10.9 10.9 0 0 0 1.153-4.887v-.23A2.667 2.667 0 0 0 20 2.666zm6.667 0a2.667 2.667 0 0 0-.307 5.316q-.21.749-.561 1.45l-.32.64a1.328 1.328 0 0 0 2.375 1.188l.32-.64a10.9 10.9 0 0 0 1.154-4.887v-.23a2.667 2.667 0 0 0-2.661-2.837M5.333 14.672h4.634l2.656 2.656h-7.29a1.328 1.328 0 0 1 0-2.656m0 8h12.634l2.656 2.656H5.333a1.328 1.328 0 0 1 0-2.656m21.792 2.575 1.814 1.814a1.328 1.328 0 0 1-1.878 1.878l-24-24a1.328 1.328 0 1 1 1.878-1.878L8.55 6.672H14a1.328 1.328 0 0 1 0 2.656h-2.794l5.344 5.344h10.117a1.328 1.328 0 1 1 0 2.656h-7.461l5.344 5.344h2.117a1.328 1.328 0 0 1 .458 2.575"/></svg>
|
||||
|
After Width: | Height: | Size: 771 B |
1
src/assets/font-icons/show-message.svg
Normal file
1
src/assets/font-icons/show-message.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M3.4 30.7c.2.1.4.1.6.1.4 0 .8-.1 1.1-.5l4.1-4.1c.4-.3.6-.5.7-.6.05 0 .1-.025.15-.05s.1-.05.15-.05h11.4c2.4 0 3.7 0 4.9-.6 1-.5 1.9-1.4 2.4-2.4.6-1.2.6-2.5.6-4.9v-7.2c0-2.4 0-3.7-.6-4.9-.5-1-1.4-1.9-2.4-2.4-1.2-.6-2.5-.6-4.9-.6H10.4c-2.4 0-3.7 0-4.9.6-1 .5-1.9 1.4-2.4 2.4s-.6 2.2-.6 3.8c0 .8.7 1.5 1.5 1.5s1.5-.7 1.5-1.5q0-1.8.3-2.4c.2-.5.6-.9 1.1-1.1.5-.3 1.6-.3 3.5-.3h11.2c1.9 0 3 0 3.5.3.5.2.9.6 1 1.1.3.5.3 1.6.3 3.5v7.2c0 1.9 0 3-.3 3.5s-.6.9-1.1 1.1c-.5.3-1.6.3-3.5.3H11c-.7 0-1.1 0-1.6.1-.4.1-.8.3-1.2.5-.4.3-.7.6-1.2 1.1l-1.5 1.5v-7c0-.8-.7-1.5-1.5-1.5s-1.5.7-1.5 1.5v10.6c0 .6.3 1.2.9 1.4m9.7-11.1c-.5-.5-.5-1.4 0-1.9l2.3-2.4H1.3C.6 15.3 0 14.7 0 14s.6-1.3 1.3-1.3h14.2l-2.4-2.4c-.5-.5-.5-1.4 0-1.9s1.4-.5 1.9 0l4.7 4.7c.2.2.4.5.4.9 0 .3-.1.6-.3.8L15 19.6c-.5.5-1.4.5-1.9 0"/></svg>
|
||||
|
After Width: | Height: | Size: 864 B |
@ -110,12 +110,12 @@
|
||||
.message-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
flex: 1;
|
||||
column-gap: 0.25rem;
|
||||
}
|
||||
|
||||
.message-title, .embedded-sender {
|
||||
.message-title, .embedded-sender, .embedded-sender-chat {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@ -3,13 +3,15 @@ import React, { useMemo, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import type {
|
||||
ApiChat,
|
||||
ApiMessage, ApiPeer, ApiReplyInfo,
|
||||
ApiMessage, ApiPeer, ApiReplyInfo, MediaContainer,
|
||||
} from '../../../api/types';
|
||||
import type { ChatTranslatedMessages } from '../../../global/types';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import type { IconName } from '../../../types/icons';
|
||||
|
||||
import { CONTENT_NOT_SUPPORTED } from '../../../config';
|
||||
import {
|
||||
getMediaContentTypeDescription,
|
||||
getMessageIsSpoiler,
|
||||
getMessageMediaHash,
|
||||
getMessageRoundVideo,
|
||||
@ -18,6 +20,7 @@ import {
|
||||
isChatChannel,
|
||||
isChatGroup,
|
||||
isMessageTranslatable,
|
||||
isUserId,
|
||||
} from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed';
|
||||
@ -58,7 +61,7 @@ type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
onClick: NoneToVoidFunction;
|
||||
onClick: ((e: React.MouseEvent) => void);
|
||||
};
|
||||
|
||||
const NBSP = '\u00A0';
|
||||
@ -128,7 +131,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return customText || NBSP;
|
||||
return customText || renderMediaContentType(wrappedMedia) || NBSP;
|
||||
}
|
||||
|
||||
if (isActionMessage(message)) {
|
||||
@ -155,6 +158,23 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
function renderMediaContentType(media?: MediaContainer) {
|
||||
if (!media || media.content.text) return NBSP;
|
||||
const description = getMediaContentTypeDescription(lang, media.content);
|
||||
if (!description || description === CONTENT_NOT_SUPPORTED) return NBSP;
|
||||
return (
|
||||
<span>
|
||||
{renderText(description)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function checkShouldRenderSenderTitle() {
|
||||
if (!senderChat) return true;
|
||||
if (isUserId(senderChat?.id)) return true;
|
||||
if (senderChat.id === sender?.id) return false;
|
||||
return true;
|
||||
}
|
||||
function renderSender() {
|
||||
if (title) {
|
||||
return renderText(title);
|
||||
@ -175,18 +195,21 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const isChatSender = senderChat && senderChat.id === sender?.id;
|
||||
const isReplyToQuote = isInComposer && Boolean(replyInfo && 'quoteText' in replyInfo && replyInfo?.quoteText);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isChatSender && (
|
||||
{checkShouldRenderSenderTitle() && (
|
||||
<span className="embedded-sender">
|
||||
{renderText(isReplyToQuote ? lang('ReplyToQuote', senderTitle) : senderTitle)}
|
||||
</span>
|
||||
)}
|
||||
{icon && <Icon name={icon} className="embedded-chat-icon" />}
|
||||
{icon && senderChatTitle && renderText(senderChatTitle)}
|
||||
{icon && senderChatTitle && (
|
||||
<span className="embedded-sender-chat">
|
||||
{renderText(senderChatTitle)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,9 +5,16 @@ import React, {
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type { ThreadId } from '../../types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import { getChatTitle, getUserFirstOrLastName, isUserId } from '../../global/helpers';
|
||||
import { selectChat, selectTabState, selectUser } from '../../global/selectors';
|
||||
import {
|
||||
selectChat,
|
||||
selectCurrentChat,
|
||||
selectDraft,
|
||||
selectTabState,
|
||||
selectUser,
|
||||
} from '../../global/selectors';
|
||||
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useLang from '../../hooks/useLang';
|
||||
@ -23,6 +30,7 @@ interface StateProps {
|
||||
currentUserId?: string;
|
||||
isManyMessages?: boolean;
|
||||
isStory?: boolean;
|
||||
isReplying?: boolean;
|
||||
}
|
||||
|
||||
const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
@ -30,8 +38,10 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
currentUserId,
|
||||
isManyMessages,
|
||||
isStory,
|
||||
isReplying,
|
||||
}) => {
|
||||
const {
|
||||
openChatOrTopicWithReplyInDraft,
|
||||
setForwardChatOrTopic,
|
||||
exitForwardMode,
|
||||
forwardToSavedMessages,
|
||||
@ -84,9 +94,15 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
forwardToSavedMessages();
|
||||
showNotification({ message });
|
||||
} else {
|
||||
setForwardChatOrTopic({ chatId: recipientId, topicId: Number(threadId) });
|
||||
const chatId = recipientId;
|
||||
const topicId = threadId ? Number(threadId) : undefined;
|
||||
if (isReplying) {
|
||||
openChatOrTopicWithReplyInDraft({ chatId, topicId });
|
||||
} else {
|
||||
setForwardChatOrTopic({ chatId, topicId });
|
||||
}
|
||||
}
|
||||
}, [currentUserId, isManyMessages, isStory, lang]);
|
||||
}, [currentUserId, isManyMessages, isStory, lang, isReplying]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
exitForwardMode();
|
||||
@ -110,9 +126,12 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const { messageIds, storyId } = selectTabState(global).forwardMessages;
|
||||
const currentChatId = selectCurrentChat(global)?.id;
|
||||
const isReplying = currentChatId && selectDraft(global, currentChatId, MAIN_THREAD_ID)?.replyInfo;
|
||||
return {
|
||||
currentUserId: global.currentUserId,
|
||||
isManyMessages: (messageIds?.length || 0) > 1,
|
||||
isStory: Boolean(storyId),
|
||||
isReplying: Boolean(isReplying),
|
||||
};
|
||||
})(ForwardRecipientPicker));
|
||||
|
||||
@ -4,11 +4,14 @@ import React, {
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiInputMessageReplyInfo, ApiMessage, ApiPeer } from '../../../api/types';
|
||||
import type {
|
||||
ApiChat, ApiInputMessageReplyInfo, ApiMessage, ApiPeer,
|
||||
} from '../../../api/types';
|
||||
|
||||
import { stripCustomEmoji } from '../../../global/helpers';
|
||||
import {
|
||||
selectCanAnimateInterface,
|
||||
selectChat,
|
||||
selectChatMessage,
|
||||
selectCurrentMessageList,
|
||||
selectDraft,
|
||||
@ -56,6 +59,9 @@ type StateProps = {
|
||||
isCurrentUserPremium?: boolean;
|
||||
isContextMenuDisabled?: boolean;
|
||||
isReplyToDiscussion?: boolean;
|
||||
isInChangingRecipientMode?: boolean;
|
||||
isChangingChats?: boolean;
|
||||
senderChat?: ApiChat;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
@ -80,12 +86,16 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
isContextMenuDisabled,
|
||||
isReplyToDiscussion,
|
||||
onClear,
|
||||
isInChangingRecipientMode,
|
||||
isChangingChats,
|
||||
senderChat,
|
||||
}) => {
|
||||
const {
|
||||
resetDraftReplyInfo,
|
||||
updateDraftReplyInfo,
|
||||
setEditingId,
|
||||
focusMessage,
|
||||
changeForwardRecipient,
|
||||
changeRecipient,
|
||||
setForwardNoAuthors,
|
||||
setForwardNoCaptions,
|
||||
exitForwardMode,
|
||||
@ -95,15 +105,17 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
const lang = useLang();
|
||||
|
||||
const isReplyToTopicStart = message?.content.action?.type === 'topicCreate';
|
||||
const isShowingReply = replyInfo && !shouldForceShowEditing;
|
||||
const isReplyWithQuote = Boolean(replyInfo?.quoteText);
|
||||
|
||||
const isForwarding = Boolean(forwardedMessagesCount);
|
||||
const isShown = Boolean(
|
||||
((replyInfo || editingId) && message)
|
||||
((replyInfo || editingId) && message && !isInChangingRecipientMode)
|
||||
|| (sender && forwardedMessagesCount),
|
||||
);
|
||||
const canAnimate = useAsyncRendering(
|
||||
[isShown, isForwarding],
|
||||
isShown && isForwarding ? FORWARD_RENDERING_DELAY : undefined,
|
||||
isShown && isChangingChats ? FORWARD_RENDERING_DELAY : undefined,
|
||||
);
|
||||
|
||||
const {
|
||||
@ -115,6 +127,11 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
undefined,
|
||||
!shouldAnimate,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (canAnimate && replyInfo?.isShowingDelayNeeded) {
|
||||
updateDraftReplyInfo({ isShowingDelayNeeded: false });
|
||||
}
|
||||
});
|
||||
|
||||
const clearEmbedded = useLastCallback(() => {
|
||||
if (replyInfo && !shouldForceShowEditing) {
|
||||
@ -129,24 +146,36 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
|
||||
useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]);
|
||||
|
||||
const handleMessageClick = useLastCallback((): void => {
|
||||
if (isForwarding) return;
|
||||
const {
|
||||
isContextMenuOpen, contextMenuPosition, handleContextMenu,
|
||||
handleContextMenuClose, handleContextMenuHide,
|
||||
} = useContextMenuHandlers(ref);
|
||||
|
||||
const focusMessageFromDraft = () => {
|
||||
focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true });
|
||||
};
|
||||
const handleMessageClick = useLastCallback((e: React.MouseEvent): void => {
|
||||
handleContextMenu(e);
|
||||
});
|
||||
|
||||
const handleClearClick = useLastCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||
e.stopPropagation();
|
||||
clearEmbedded();
|
||||
handleContextMenuHide();
|
||||
});
|
||||
|
||||
const handleChangeRecipientClick = useLastCallback(() => {
|
||||
changeForwardRecipient();
|
||||
});
|
||||
|
||||
const {
|
||||
isContextMenuOpen, contextMenuPosition, handleContextMenu,
|
||||
handleContextMenuClose, handleContextMenuHide,
|
||||
} = useContextMenuHandlers(ref);
|
||||
const buildAutoCloseMenuItemHandler = (action: NoneToVoidFunction) => {
|
||||
return () => {
|
||||
handleContextMenuClose();
|
||||
action();
|
||||
};
|
||||
};
|
||||
const handleForwardToAnotherChatClick = useLastCallback(buildAutoCloseMenuItemHandler(changeRecipient));
|
||||
const handleShowMessageClick = useLastCallback(buildAutoCloseMenuItemHandler(focusMessageFromDraft));
|
||||
const handleRemoveQuoteClick = useLastCallback(buildAutoCloseMenuItemHandler(
|
||||
() => updateDraftReplyInfo({ quoteText: undefined }),
|
||||
));
|
||||
const handleChangeReplyRecipientClick = useLastCallback(buildAutoCloseMenuItemHandler(changeRecipient));
|
||||
const handleDoNotReplyClick = useLastCallback(buildAutoCloseMenuItemHandler(clearEmbedded));
|
||||
|
||||
const getTriggerElement = useLastCallback(() => ref.current);
|
||||
const getRootElement = useLastCallback(() => ref.current!);
|
||||
@ -162,8 +191,11 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldRender) handleContextMenuClose();
|
||||
}, [handleContextMenuClose, shouldRender]);
|
||||
if (!shouldRender) {
|
||||
handleContextMenuClose();
|
||||
handleContextMenuHide();
|
||||
}
|
||||
}, [handleContextMenuClose, handleContextMenuHide, shouldRender]);
|
||||
|
||||
const className = buildClassName('ComposerEmbeddedMessage', transitionClassNames);
|
||||
const renderingSender = useCurrentOrPrev(sender, true);
|
||||
@ -172,8 +204,6 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
getPeerColorClass(renderingSender),
|
||||
);
|
||||
|
||||
const isShowingReply = replyInfo && !shouldForceShowEditing;
|
||||
|
||||
const leftIcon = useMemo(() => {
|
||||
if (isShowingReply) {
|
||||
return 'reply';
|
||||
@ -212,9 +242,9 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} ref={ref} onContextMenu={handleContextMenu} onClick={handleContextMenu}>
|
||||
<div className={className} ref={ref} onContextMenu={handleContextMenu}>
|
||||
<div className={innerClassName}>
|
||||
<div className="embedded-left-icon">
|
||||
<div className="embedded-left-icon" onClick={handleContextMenu}>
|
||||
{renderingLeftIcon && <Icon name={renderingLeftIcon} />}
|
||||
{Boolean(replyInfo?.quoteText) && (
|
||||
<Icon name="quote" className="quote-reply" />
|
||||
@ -231,6 +261,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
title={(editingId && !isShowingReply) ? lang('EditMessage')
|
||||
: noAuthors ? lang('HiddenSendersNameDescription') : undefined}
|
||||
onClick={handleMessageClick}
|
||||
senderChat={senderChat}
|
||||
/>
|
||||
<Button
|
||||
className="embedded-cancel"
|
||||
@ -242,7 +273,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
{isForwarding && !isContextMenuDisabled && (
|
||||
{(isShowingReply || isForwarding) && !isContextMenuDisabled && (
|
||||
<Menu
|
||||
isOpen={isContextMenuOpen}
|
||||
transformOriginX={transformOriginX}
|
||||
@ -254,55 +285,83 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
onClose={handleContextMenuClose}
|
||||
onCloseAnimationEnd={handleContextMenuHide}
|
||||
>
|
||||
<MenuItem
|
||||
icon={!noAuthors ? 'message-succeeded' : undefined}
|
||||
customIcon={noAuthors ? <i className="icon icon-placeholder" /> : undefined}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => setForwardNoAuthors({
|
||||
noAuthors: false,
|
||||
})}
|
||||
>
|
||||
{lang(forwardedMessagesCount > 1 ? 'ShowSenderNames' : 'ShowSendersName')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={noAuthors ? 'message-succeeded' : undefined}
|
||||
customIcon={!noAuthors ? <i className="icon icon-placeholder" /> : undefined}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => setForwardNoAuthors({
|
||||
noAuthors: true,
|
||||
})}
|
||||
>
|
||||
{lang(forwardedMessagesCount > 1 ? 'HideSenderNames' : 'HideSendersName')}
|
||||
</MenuItem>
|
||||
{forwardsHaveCaptions && (
|
||||
{isForwarding && (
|
||||
<>
|
||||
<MenuSeparator />
|
||||
<MenuItem
|
||||
icon={!noCaptions ? 'message-succeeded' : undefined}
|
||||
customIcon={noCaptions ? <i className="icon icon-placeholder" /> : undefined}
|
||||
icon={!noAuthors ? 'message-succeeded' : undefined}
|
||||
customIcon={noAuthors ? <i className="icon icon-placeholder" /> : undefined}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => setForwardNoCaptions({
|
||||
noCaptions: false,
|
||||
onClick={() => setForwardNoAuthors({
|
||||
noAuthors: false,
|
||||
})}
|
||||
>
|
||||
{lang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.ShowCaption' : 'ShowCaption')}
|
||||
{lang(forwardedMessagesCount > 1 ? 'ShowSenderNames' : 'ShowSendersName')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={noCaptions ? 'message-succeeded' : undefined}
|
||||
customIcon={!noCaptions ? <i className="icon icon-placeholder" /> : undefined}
|
||||
icon={noAuthors ? 'message-succeeded' : undefined}
|
||||
customIcon={!noAuthors ? <i className="icon icon-placeholder" /> : undefined}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => setForwardNoCaptions({
|
||||
noCaptions: true,
|
||||
onClick={() => setForwardNoAuthors({
|
||||
noAuthors: true,
|
||||
})}
|
||||
>
|
||||
{lang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.HideCaption' : 'HideCaption')}
|
||||
{lang(forwardedMessagesCount > 1 ? 'HideSenderNames' : 'HideSendersName')}
|
||||
</MenuItem>
|
||||
{forwardsHaveCaptions && (
|
||||
<>
|
||||
<MenuSeparator />
|
||||
<MenuItem
|
||||
icon={!noCaptions ? 'message-succeeded' : undefined}
|
||||
customIcon={noCaptions ? <i className="icon icon-placeholder" /> : undefined}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => setForwardNoCaptions({
|
||||
noCaptions: false,
|
||||
})}
|
||||
>
|
||||
{lang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.ShowCaption' : 'ShowCaption')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={noCaptions ? 'message-succeeded' : undefined}
|
||||
customIcon={!noCaptions ? <i className="icon icon-placeholder" /> : undefined}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => setForwardNoCaptions({
|
||||
noCaptions: true,
|
||||
})}
|
||||
>
|
||||
{lang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.HideCaption' : 'HideCaption')}
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
<MenuSeparator />
|
||||
<MenuItem icon="replace" onClick={handleForwardToAnotherChatClick}>
|
||||
{lang('ForwardAnotherChat')}
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
{isShowingReply && (
|
||||
<>
|
||||
<MenuItem
|
||||
icon="show-message"
|
||||
onClick={handleShowMessageClick}
|
||||
>
|
||||
{lang('Message.Context.Goto')}
|
||||
</MenuItem>
|
||||
{isReplyWithQuote && (
|
||||
<MenuItem
|
||||
icon="remove-quote"
|
||||
onClick={handleRemoveQuoteClick}
|
||||
>
|
||||
{lang('RemoveQuote')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem icon="replace" onClick={handleChangeReplyRecipientClick}>
|
||||
{lang('ReplyToAnotherChat')}
|
||||
</MenuItem>
|
||||
<MenuItem icon="delete" onClick={handleDoNotReplyClick}>
|
||||
{lang('DoNotReply')}
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
<MenuSeparator />
|
||||
<MenuItem icon="replace" onClick={handleChangeRecipientClick}>
|
||||
{lang('ChangeRecipient')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
@ -319,7 +378,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const {
|
||||
forwardMessages: {
|
||||
fromChatId, toChatId, messageIds: forwardMessageIds, noAuthors, noCaptions,
|
||||
fromChatId, toChatId, messageIds: forwardMessageIds, noAuthors, noCaptions, isModalShown,
|
||||
},
|
||||
} = selectTabState(global);
|
||||
|
||||
@ -332,7 +391,10 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const draft = selectDraft(global, chatId, threadId);
|
||||
const replyInfo = draft?.replyInfo;
|
||||
const replyToPeerId = replyInfo?.replyToPeerId;
|
||||
const senderChat = replyToPeerId ? selectChat(global, replyToPeerId) : undefined;
|
||||
|
||||
const isChangingChats = isForwarding || replyInfo?.isShowingDelayNeeded;
|
||||
let message: ApiMessage | undefined;
|
||||
if (replyInfo && !shouldForceShowEditing) {
|
||||
message = selectChatMessage(global, replyInfo.replyToPeerId || chatId, replyInfo.replyToMsgId);
|
||||
@ -389,6 +451,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
isContextMenuDisabled,
|
||||
isReplyToDiscussion,
|
||||
isInChangingRecipientMode: isModalShown,
|
||||
isChangingChats,
|
||||
senderChat,
|
||||
};
|
||||
},
|
||||
)(ComposerEmbeddedMessage));
|
||||
|
||||
@ -52,13 +52,11 @@ const MenuItem: FC<MenuItemProps> = (props) => {
|
||||
const lang = useLang();
|
||||
const { isTouchScreen } = useAppLayout();
|
||||
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
if (disabled || !onClick) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
onClick(e, clickArg);
|
||||
});
|
||||
|
||||
@ -67,13 +65,12 @@ const MenuItem: FC<MenuItemProps> = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
if (disabled || !onClick) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
onClick(e, clickArg);
|
||||
});
|
||||
const handleMouseDown = useLastCallback((e: React.SyntheticEvent<HTMLDivElement | HTMLAnchorElement>) => {
|
||||
|
||||
@ -13,6 +13,7 @@ import type {
|
||||
ApiSticker,
|
||||
ApiStory,
|
||||
ApiStorySkipped,
|
||||
ApiUser,
|
||||
ApiVideo,
|
||||
} from '../../../api/types';
|
||||
import type { MessageKey } from '../../../util/messageKey';
|
||||
@ -122,6 +123,7 @@ import {
|
||||
selectPeerStory,
|
||||
selectPinnedIds,
|
||||
selectRealLastReadId,
|
||||
selectReplyCanBeSentToChat,
|
||||
selectScheduledMessage,
|
||||
selectSendAs,
|
||||
selectSponsoredMessage,
|
||||
@ -1750,29 +1752,104 @@ addActionHandler('openUrl', (global, actions, payload): ActionReturnType => {
|
||||
}
|
||||
});
|
||||
|
||||
async function checkIfVoiceMessagesAllowed<T extends GlobalState>(
|
||||
global: T,
|
||||
user: ApiUser,
|
||||
chatId: string,
|
||||
): Promise<boolean> {
|
||||
let fullInfo = selectUserFullInfo(global, chatId);
|
||||
if (!fullInfo) {
|
||||
const { accessHash } = user;
|
||||
const result = await callApi('fetchFullUser', { id: chatId, accessHash });
|
||||
fullInfo = result?.fullInfo;
|
||||
}
|
||||
return Boolean(!fullInfo?.noVoiceMessages);
|
||||
}
|
||||
|
||||
function moveReplyToNewDraft<T extends GlobalState>(
|
||||
global: T,
|
||||
threadId: ThreadId,
|
||||
replyInfo: ApiInputMessageReplyInfo,
|
||||
toChatId: string,
|
||||
) {
|
||||
const currentDraft = selectDraft(global, toChatId, threadId);
|
||||
|
||||
if (!replyInfo.replyToMsgId) return;
|
||||
|
||||
const newDraft: ApiDraft = {
|
||||
...currentDraft,
|
||||
replyInfo,
|
||||
};
|
||||
|
||||
saveDraft({
|
||||
global, chatId: toChatId, threadId, draft: newDraft, isLocalOnly: true, noLocalTimeUpdate: true,
|
||||
});
|
||||
}
|
||||
addActionHandler('openChatOrTopicWithReplyInDraft', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId: toChatId, topicId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
if (!selectReplyCanBeSentToChat(global, toChatId, tabId)) {
|
||||
actions.showNotification({ message: translate('Chat.SendNotAllowedText'), tabId });
|
||||
return;
|
||||
}
|
||||
|
||||
global = updateTabState(global, {
|
||||
forwardMessages: {
|
||||
...selectTabState(global, tabId).forwardMessages,
|
||||
isModalShown: false,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
const currentChat = selectCurrentChat(global, tabId);
|
||||
if (!currentChat) return;
|
||||
|
||||
const threadId = topicId || MAIN_THREAD_ID;
|
||||
const currentChatId = currentChat.id;
|
||||
|
||||
const currentReplyInfo = selectDraft(global, currentChatId, threadId)?.replyInfo;
|
||||
if (!currentReplyInfo) return;
|
||||
if (!currentReplyInfo.replyToPeerId && toChatId === currentChat.id) return;
|
||||
|
||||
const getPeerId = () => {
|
||||
if (!currentReplyInfo?.replyToPeerId) return currentChatId;
|
||||
return currentReplyInfo.replyToPeerId === toChatId ? undefined : currentReplyInfo.replyToPeerId;
|
||||
};
|
||||
const currentThreadId = selectCurrentMessageList(global, tabId)?.threadId;
|
||||
if (!currentThreadId) {
|
||||
return;
|
||||
}
|
||||
const replyToPeerId = getPeerId();
|
||||
const newReply: ApiInputMessageReplyInfo = {
|
||||
...currentReplyInfo,
|
||||
replyToPeerId,
|
||||
type: 'message',
|
||||
isShowingDelayNeeded: true,
|
||||
};
|
||||
|
||||
moveReplyToNewDraft(global, threadId, newReply, toChatId);
|
||||
actions.openThread({ chatId: toChatId, threadId, tabId });
|
||||
actions.closeMediaViewer({ tabId });
|
||||
actions.exitMessageSelectMode({ tabId });
|
||||
actions.clearDraft({ chatId: currentChatId, threadId: currentThreadId });
|
||||
});
|
||||
|
||||
addActionHandler('setForwardChatOrTopic', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId, topicId, tabId = getCurrentTabId() } = payload;
|
||||
let user = selectUser(global, chatId);
|
||||
if (user && selectForwardsContainVoiceMessages(global, tabId)) {
|
||||
let fullInfo = selectUserFullInfo(global, chatId);
|
||||
if (!fullInfo) {
|
||||
const { accessHash } = user;
|
||||
const result = await callApi('fetchFullUser', { id: chatId, accessHash });
|
||||
global = getGlobal();
|
||||
user = result?.user;
|
||||
fullInfo = result?.fullInfo;
|
||||
}
|
||||
|
||||
if (fullInfo!.noVoiceMessages) {
|
||||
actions.showDialog({
|
||||
data: {
|
||||
message: translate('VoiceMessagesRestrictedByPrivacy', getUserFullName(user)),
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const user = selectUser(global, chatId);
|
||||
const isSelectForwardsContainVoiceMessages = selectForwardsContainVoiceMessages(global, tabId);
|
||||
if (isSelectForwardsContainVoiceMessages && user && !await checkIfVoiceMessagesAllowed(global, user, chatId)) {
|
||||
actions.showDialog({
|
||||
data: {
|
||||
message: translate('VoiceMessagesRestrictedByPrivacy', getUserFullName(user)),
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
global = getGlobal();
|
||||
|
||||
if (!selectForwardsCanBeSentToChat(global, chatId, tabId)) {
|
||||
actions.showAllowedMessageTypesNotification({ chatId, tabId });
|
||||
|
||||
@ -515,7 +515,7 @@ addActionHandler('openForwardMenu', (global, actions, payload): ActionReturnType
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('changeForwardRecipient', (global, actions, payload): ActionReturnType => {
|
||||
addActionHandler('changeRecipient', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
return updateTabState(global, {
|
||||
forwardMessages: {
|
||||
|
||||
@ -10,7 +10,7 @@ import type {
|
||||
ApiPhoto,
|
||||
ApiVideo,
|
||||
ApiWebDocument,
|
||||
MediaContent,
|
||||
MediaContainer,
|
||||
} from '../../api/types';
|
||||
import { ApiMediaFormat } from '../../api/types';
|
||||
|
||||
@ -25,10 +25,6 @@ import {
|
||||
import { getDocumentHasPreview } from '../../components/common/helpers/documentInfo';
|
||||
import { getAttachmentType, matchLinkInMessageText } from './messages';
|
||||
|
||||
type MediaContainer = {
|
||||
content: MediaContent;
|
||||
};
|
||||
|
||||
type Target =
|
||||
'micro'
|
||||
| 'pictogram'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { TeactNode } from '../../lib/teact/teact';
|
||||
|
||||
import type { ApiMessage } from '../../api/types';
|
||||
import type { ApiMessage, MediaContent } from '../../api/types';
|
||||
import type { LangFn } from '../../hooks/useLang';
|
||||
import { ApiMessageEntityTypes } from '../../api/types';
|
||||
|
||||
@ -8,7 +8,7 @@ import { CONTENT_NOT_SUPPORTED } from '../../config';
|
||||
import trimText from '../../util/trimText';
|
||||
import { getGlobal } from '../index';
|
||||
import {
|
||||
getExpiredMessageDescription, getMessageText, getMessageTranscription, isExpiredMessage,
|
||||
getExpiredMessageContentDescription, getMessageText, getMessageTranscription, isExpiredMessageContent,
|
||||
} from './messages';
|
||||
import { getUserFirstOrLastName } from './users';
|
||||
|
||||
@ -102,11 +102,23 @@ export function getMessageSummaryEmoji(message: ApiMessage) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getMediaContentTypeDescription(lang: LangFn, content: MediaContent) {
|
||||
return getSummaryDescription(lang, content);
|
||||
}
|
||||
export function getMessageSummaryDescription(
|
||||
lang: LangFn,
|
||||
message: ApiMessage,
|
||||
truncatedText?: string | TeactNode,
|
||||
isExtended = false,
|
||||
) {
|
||||
return getSummaryDescription(lang, message.content, message, truncatedText, isExtended);
|
||||
}
|
||||
function getSummaryDescription(
|
||||
lang: LangFn,
|
||||
mediaContent: MediaContent,
|
||||
message?: ApiMessage,
|
||||
truncatedText?: string | TeactNode,
|
||||
isExtended = false,
|
||||
) {
|
||||
const {
|
||||
text,
|
||||
@ -124,12 +136,12 @@ export function getMessageSummaryDescription(
|
||||
storyData,
|
||||
giveaway,
|
||||
giveawayResults,
|
||||
} = message.content;
|
||||
} = mediaContent;
|
||||
|
||||
let hasUsedTruncatedText = false;
|
||||
let summary: string | TeactNode | undefined;
|
||||
|
||||
if (message.groupedId) {
|
||||
if (message?.groupedId) {
|
||||
hasUsedTruncatedText = true;
|
||||
summary = truncatedText || lang('lng_in_dlg_album');
|
||||
}
|
||||
@ -149,7 +161,7 @@ export function getMessageSummaryDescription(
|
||||
}
|
||||
|
||||
if (audio) {
|
||||
summary = getMessageAudioCaption(message) || lang('AttachMusic');
|
||||
summary = getMessageAudioCaption(mediaContent) || lang('AttachMusic');
|
||||
}
|
||||
|
||||
if (voice) {
|
||||
@ -203,7 +215,7 @@ export function getMessageSummaryDescription(
|
||||
}
|
||||
|
||||
if (storyData) {
|
||||
if (storyData.isMention) {
|
||||
if (message && storyData.isMention) {
|
||||
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
||||
const global = getGlobal();
|
||||
const firstName = getUserFirstOrLastName(global.users.byId[message.chatId]);
|
||||
@ -211,12 +223,12 @@ export function getMessageSummaryDescription(
|
||||
? lang('Chat.Service.StoryMentioned.You', firstName)
|
||||
: lang('Chat.Service.StoryMentioned', firstName);
|
||||
} else {
|
||||
summary = lang('ForwardedStory');
|
||||
summary = message ? lang('ForwardedStory') : lang('Chat.ReplyStory');
|
||||
}
|
||||
}
|
||||
|
||||
if (isExpiredMessage(message)) {
|
||||
const expiredMessageText = getExpiredMessageDescription(lang, message);
|
||||
if (isExpiredMessageContent(mediaContent)) {
|
||||
const expiredMessageText = getExpiredMessageContentDescription(lang, mediaContent);
|
||||
if (expiredMessageText) {
|
||||
summary = expiredMessageText;
|
||||
}
|
||||
@ -232,11 +244,11 @@ export function generateBrailleSpoiler(length: number) {
|
||||
.join('');
|
||||
}
|
||||
|
||||
function getMessageAudioCaption(message: ApiMessage) {
|
||||
function getMessageAudioCaption(mediaContent: MediaContent) {
|
||||
const {
|
||||
audio,
|
||||
text,
|
||||
} = message.content;
|
||||
} = mediaContent;
|
||||
|
||||
return (audio && [audio.title, audio.performer].filter(Boolean)
|
||||
.join(' — ')) || (text?.text);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ApiAttachment, ApiChat, ApiMessage, ApiMessageEntityTextUrl, ApiPeer, ApiStory, ApiUser,
|
||||
} from '../../api/types';
|
||||
import type { MediaContent } from '../../api/types/messages';
|
||||
import type { LangFn } from '../../hooks/useLang';
|
||||
import { ApiMessageEntityTypes } from '../../api/types';
|
||||
|
||||
@ -320,7 +321,10 @@ export function extractMessageText(message: ApiMessage | ApiStory, inChatList =
|
||||
}
|
||||
|
||||
export function getExpiredMessageDescription(langFn: LangFn, message: ApiMessage): string | undefined {
|
||||
const { isExpiredVoice, isExpiredRoundVideo } = message.content;
|
||||
return getExpiredMessageContentDescription(langFn, message.content);
|
||||
}
|
||||
export function getExpiredMessageContentDescription(langFn: LangFn, mediaContent: MediaContent): string | undefined {
|
||||
const { isExpiredVoice, isExpiredRoundVideo } = mediaContent;
|
||||
if (isExpiredVoice) {
|
||||
return langFn('Message.VoiceMessageExpired');
|
||||
} else if (isExpiredRoundVideo) {
|
||||
@ -330,7 +334,11 @@ export function getExpiredMessageDescription(langFn: LangFn, message: ApiMessage
|
||||
}
|
||||
|
||||
export function isExpiredMessage(message: ApiMessage) {
|
||||
const { isExpiredVoice, isExpiredRoundVideo } = message.content ?? {};
|
||||
return isExpiredMessageContent(message.content);
|
||||
}
|
||||
|
||||
export function isExpiredMessageContent(content: MediaContent) {
|
||||
const { isExpiredVoice, isExpiredRoundVideo } = content ?? {};
|
||||
return Boolean(isExpiredVoice || isExpiredRoundVideo);
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import type {
|
||||
ApiStickerSetInfo,
|
||||
} from '../../api/types';
|
||||
import type { ThreadId } from '../../types';
|
||||
import type { IAllowedAttachmentOptions } from '../helpers';
|
||||
import type {
|
||||
ChatTranslatedMessages,
|
||||
GlobalState, MessageListType, TabArgs, TabThread, Thread,
|
||||
@ -47,6 +48,7 @@ import {
|
||||
isChatGroup,
|
||||
isChatSuperGroup,
|
||||
isCommonBoxChat,
|
||||
isExpiredMessage,
|
||||
isForwardedMessage,
|
||||
isMessageDocumentSticker,
|
||||
isMessageFailed,
|
||||
@ -1336,7 +1338,7 @@ export function selectForwardsContainVoiceMessages<T extends GlobalState>(
|
||||
const chatMessages = selectChatMessages(global, fromChatId!);
|
||||
return messageIds.some((messageId) => {
|
||||
const message = chatMessages[messageId];
|
||||
return Boolean(message.content.voice) || message.content.video?.isRound;
|
||||
return Boolean(message.content.voice) || Boolean(message.content.video?.isRound);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1358,7 +1360,22 @@ export function selectRequestedMessageTranslationLanguage<T extends GlobalState>
|
||||
const requestedInChat = selectTabState(global, tabId).requestedTranslations.byChatId[chatId];
|
||||
return requestedInChat?.toLanguage || requestedInChat?.manualMessages?.[messageId];
|
||||
}
|
||||
export function selectReplyCanBeSentToChat<T extends GlobalState>(
|
||||
global: T,
|
||||
toChatId: string,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
) {
|
||||
const currentChat = selectCurrentChat(global, tabId);
|
||||
if (!currentChat) return false;
|
||||
const replyInfo = selectDraft(global, currentChat.id, MAIN_THREAD_ID)?.replyInfo;
|
||||
if (!replyInfo || !replyInfo.replyToMsgId) return false;
|
||||
const fromChatId = replyInfo?.replyToPeerId ?? currentChat.id;
|
||||
if (toChatId === fromChatId) return true;
|
||||
const chatMessages = selectChatMessages(global, fromChatId!);
|
||||
const message = chatMessages[replyInfo.replyToMsgId];
|
||||
|
||||
return !isExpiredMessage(message);
|
||||
}
|
||||
export function selectForwardsCanBeSentToChat<T extends GlobalState>(
|
||||
global: T,
|
||||
toChatId: string,
|
||||
@ -1374,33 +1391,30 @@ export function selectForwardsCanBeSentToChat<T extends GlobalState>(
|
||||
|
||||
const chatFullInfo = selectChatFullInfo(global, toChatId);
|
||||
const chatMessages = selectChatMessages(global, fromChatId!);
|
||||
const {
|
||||
canSendVoices, canSendRoundVideos, canSendStickers, canSendDocuments, canSendAudios, canSendVideos,
|
||||
canSendPhotos, canSendGifs, canSendPlainText,
|
||||
} = getAllowedAttachmentOptions(chat, chatFullInfo);
|
||||
return !messageIds!.some((messageId) => {
|
||||
const message = chatMessages[messageId];
|
||||
const isVoice = message.content.voice;
|
||||
const isRoundVideo = message.content.video?.isRound;
|
||||
const isPhoto = message.content.photo;
|
||||
const isGif = message.content.video?.isGif;
|
||||
const isVideo = message.content.video && !isRoundVideo && !isGif;
|
||||
const isAudio = message.content.audio;
|
||||
const isDocument = message.content.document;
|
||||
const isSticker = message.content.sticker;
|
||||
const isPlainText = message.content.text
|
||||
&& !isVoice && !isRoundVideo && !isSticker && !isDocument && !isAudio && !isVideo && !isPhoto && !isGif;
|
||||
const options = getAllowedAttachmentOptions(chat, chatFullInfo);
|
||||
return !messageIds!.some((messageId) => сheckMessageSendingDenied(chatMessages[messageId], options));
|
||||
}
|
||||
function сheckMessageSendingDenied(message: ApiMessage, options: IAllowedAttachmentOptions) {
|
||||
const isVoice = message.content.voice;
|
||||
const isRoundVideo = message.content.video?.isRound;
|
||||
const isPhoto = message.content.photo;
|
||||
const isGif = message.content.video?.isGif;
|
||||
const isVideo = message.content.video && !isRoundVideo && !isGif;
|
||||
const isAudio = message.content.audio;
|
||||
const isDocument = message.content.document;
|
||||
const isSticker = message.content.sticker;
|
||||
const isPlainText = message.content.text
|
||||
&& !isVoice && !isRoundVideo && !isSticker && !isDocument && !isAudio && !isVideo && !isPhoto && !isGif;
|
||||
|
||||
return (isVoice && !canSendVoices)
|
||||
|| (isRoundVideo && !canSendRoundVideos)
|
||||
|| (isSticker && !canSendStickers)
|
||||
|| (isDocument && !canSendDocuments)
|
||||
|| (isAudio && !canSendAudios)
|
||||
|| (isVideo && !canSendVideos)
|
||||
|| (isPhoto && !canSendPhotos)
|
||||
|| (isGif && !canSendGifs)
|
||||
|| (isPlainText && !canSendPlainText);
|
||||
});
|
||||
return (isVoice && !options.canSendVoices)
|
||||
|| (isRoundVideo && !options.canSendRoundVideos)
|
||||
|| (isSticker && !options.canSendStickers)
|
||||
|| (isDocument && !options.canSendDocuments)
|
||||
|| (isAudio && !options.canSendAudios)
|
||||
|| (isVideo && !options.canSendVideos)
|
||||
|| (isPhoto && !options.canSendPhotos)
|
||||
|| (isGif && !options.canSendGifs)
|
||||
|| (isPlainText && !options.canSendPlainText);
|
||||
}
|
||||
|
||||
export function selectCanTranslateMessage<T extends GlobalState>(
|
||||
|
||||
@ -2540,6 +2540,10 @@ export interface ActionPayloads {
|
||||
chatId: string;
|
||||
topicId?: number;
|
||||
} & WithTabId;
|
||||
openChatOrTopicWithReplyInDraft: {
|
||||
chatId: string;
|
||||
topicId?: number;
|
||||
} & WithTabId;
|
||||
forwardMessages: {
|
||||
isSilent?: boolean;
|
||||
scheduledAt?: number;
|
||||
@ -2551,7 +2555,7 @@ export interface ActionPayloads {
|
||||
noCaptions: boolean;
|
||||
} & WithTabId;
|
||||
exitForwardMode: WithTabId | undefined;
|
||||
changeForwardRecipient: WithTabId | undefined;
|
||||
changeRecipient: WithTabId | undefined;
|
||||
forwardToSavedMessages: WithTabId | undefined;
|
||||
forwardStory: {
|
||||
toChatId: string;
|
||||
|
||||
@ -24,6 +24,7 @@ const useShowTransition = (
|
||||
|
||||
if (closeTimeoutRef.current) {
|
||||
window.clearTimeout(closeTimeoutRef.current);
|
||||
|
||||
closeTimeoutRef.current = undefined;
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -187,83 +187,85 @@ $icons-map: (
|
||||
"readchats": "\f19c",
|
||||
"recent": "\f19d",
|
||||
"reload": "\f19e",
|
||||
"remove": "\f19f",
|
||||
"reopen-topic": "\f1a0",
|
||||
"replace": "\f1a1",
|
||||
"replies": "\f1a2",
|
||||
"reply-filled": "\f1a3",
|
||||
"reply": "\f1a4",
|
||||
"revenue-split": "\f1a5",
|
||||
"revote": "\f1a6",
|
||||
"save-story": "\f1a7",
|
||||
"saved-messages": "\f1a8",
|
||||
"schedule": "\f1a9",
|
||||
"search": "\f1aa",
|
||||
"select": "\f1ab",
|
||||
"send-outline": "\f1ac",
|
||||
"send": "\f1ad",
|
||||
"settings-filled": "\f1ae",
|
||||
"settings": "\f1af",
|
||||
"share-filled": "\f1b0",
|
||||
"share-screen-outlined": "\f1b1",
|
||||
"share-screen-stop": "\f1b2",
|
||||
"share-screen": "\f1b3",
|
||||
"sidebar": "\f1b4",
|
||||
"skip-next": "\f1b5",
|
||||
"skip-previous": "\f1b6",
|
||||
"smallscreen": "\f1b7",
|
||||
"smile": "\f1b8",
|
||||
"sort": "\f1b9",
|
||||
"speaker-muted-story": "\f1ba",
|
||||
"speaker-outline": "\f1bb",
|
||||
"speaker-story": "\f1bc",
|
||||
"speaker": "\f1bd",
|
||||
"spoiler-disable": "\f1be",
|
||||
"spoiler": "\f1bf",
|
||||
"sport": "\f1c0",
|
||||
"stats": "\f1c1",
|
||||
"stealth-future": "\f1c2",
|
||||
"stealth-past": "\f1c3",
|
||||
"stickers": "\f1c4",
|
||||
"stop-raising-hand": "\f1c5",
|
||||
"stop": "\f1c6",
|
||||
"story-caption": "\f1c7",
|
||||
"story-expired": "\f1c8",
|
||||
"story-priority": "\f1c9",
|
||||
"story-reply": "\f1ca",
|
||||
"strikethrough": "\f1cb",
|
||||
"tag-add": "\f1cc",
|
||||
"tag-crossed": "\f1cd",
|
||||
"tag-filter": "\f1ce",
|
||||
"tag-name": "\f1cf",
|
||||
"tag": "\f1d0",
|
||||
"timer": "\f1d1",
|
||||
"transcribe": "\f1d2",
|
||||
"truck": "\f1d3",
|
||||
"unarchive": "\f1d4",
|
||||
"underlined": "\f1d5",
|
||||
"unlock-badge": "\f1d6",
|
||||
"unlock": "\f1d7",
|
||||
"unmute": "\f1d8",
|
||||
"unpin": "\f1d9",
|
||||
"unread": "\f1da",
|
||||
"up": "\f1db",
|
||||
"user-filled": "\f1dc",
|
||||
"user-online": "\f1dd",
|
||||
"user": "\f1de",
|
||||
"video-outlined": "\f1df",
|
||||
"video-stop": "\f1e0",
|
||||
"video": "\f1e1",
|
||||
"view-once": "\f1e2",
|
||||
"voice-chat": "\f1e3",
|
||||
"volume-1": "\f1e4",
|
||||
"volume-2": "\f1e5",
|
||||
"volume-3": "\f1e6",
|
||||
"web": "\f1e7",
|
||||
"webapp": "\f1e8",
|
||||
"word-wrap": "\f1e9",
|
||||
"zoom-in": "\f1ea",
|
||||
"zoom-out": "\f1eb",
|
||||
"remove-quote": "\f19f",
|
||||
"remove": "\f1a0",
|
||||
"reopen-topic": "\f1a1",
|
||||
"replace": "\f1a2",
|
||||
"replies": "\f1a3",
|
||||
"reply-filled": "\f1a4",
|
||||
"reply": "\f1a5",
|
||||
"revenue-split": "\f1a6",
|
||||
"revote": "\f1a7",
|
||||
"save-story": "\f1a8",
|
||||
"saved-messages": "\f1a9",
|
||||
"schedule": "\f1aa",
|
||||
"search": "\f1ab",
|
||||
"select": "\f1ac",
|
||||
"send-outline": "\f1ad",
|
||||
"send": "\f1ae",
|
||||
"settings-filled": "\f1af",
|
||||
"settings": "\f1b0",
|
||||
"share-filled": "\f1b1",
|
||||
"share-screen-outlined": "\f1b2",
|
||||
"share-screen-stop": "\f1b3",
|
||||
"share-screen": "\f1b4",
|
||||
"show-message": "\f1b5",
|
||||
"sidebar": "\f1b6",
|
||||
"skip-next": "\f1b7",
|
||||
"skip-previous": "\f1b8",
|
||||
"smallscreen": "\f1b9",
|
||||
"smile": "\f1ba",
|
||||
"sort": "\f1bb",
|
||||
"speaker-muted-story": "\f1bc",
|
||||
"speaker-outline": "\f1bd",
|
||||
"speaker-story": "\f1be",
|
||||
"speaker": "\f1bf",
|
||||
"spoiler-disable": "\f1c0",
|
||||
"spoiler": "\f1c1",
|
||||
"sport": "\f1c2",
|
||||
"stats": "\f1c3",
|
||||
"stealth-future": "\f1c4",
|
||||
"stealth-past": "\f1c5",
|
||||
"stickers": "\f1c6",
|
||||
"stop-raising-hand": "\f1c7",
|
||||
"stop": "\f1c8",
|
||||
"story-caption": "\f1c9",
|
||||
"story-expired": "\f1ca",
|
||||
"story-priority": "\f1cb",
|
||||
"story-reply": "\f1cc",
|
||||
"strikethrough": "\f1cd",
|
||||
"tag-add": "\f1ce",
|
||||
"tag-crossed": "\f1cf",
|
||||
"tag-filter": "\f1d0",
|
||||
"tag-name": "\f1d1",
|
||||
"tag": "\f1d2",
|
||||
"timer": "\f1d3",
|
||||
"transcribe": "\f1d4",
|
||||
"truck": "\f1d5",
|
||||
"unarchive": "\f1d6",
|
||||
"underlined": "\f1d7",
|
||||
"unlock-badge": "\f1d8",
|
||||
"unlock": "\f1d9",
|
||||
"unmute": "\f1da",
|
||||
"unpin": "\f1db",
|
||||
"unread": "\f1dc",
|
||||
"up": "\f1dd",
|
||||
"user-filled": "\f1de",
|
||||
"user-online": "\f1df",
|
||||
"user": "\f1e0",
|
||||
"video-outlined": "\f1e1",
|
||||
"video-stop": "\f1e2",
|
||||
"video": "\f1e3",
|
||||
"view-once": "\f1e4",
|
||||
"voice-chat": "\f1e5",
|
||||
"volume-1": "\f1e6",
|
||||
"volume-2": "\f1e7",
|
||||
"volume-3": "\f1e8",
|
||||
"web": "\f1e9",
|
||||
"webapp": "\f1ea",
|
||||
"word-wrap": "\f1eb",
|
||||
"zoom-in": "\f1ec",
|
||||
"zoom-out": "\f1ed",
|
||||
);
|
||||
|
||||
.icon-active-sessions::before {
|
||||
@ -740,6 +742,9 @@ $icons-map: (
|
||||
.icon-reload::before {
|
||||
content: map.get($icons-map, "reload");
|
||||
}
|
||||
.icon-remove-quote::before {
|
||||
content: map.get($icons-map, "remove-quote");
|
||||
}
|
||||
.icon-remove::before {
|
||||
content: map.get($icons-map, "remove");
|
||||
}
|
||||
@ -803,6 +808,9 @@ $icons-map: (
|
||||
.icon-share-screen::before {
|
||||
content: map.get($icons-map, "share-screen");
|
||||
}
|
||||
.icon-show-message::before {
|
||||
content: map.get($icons-map, "show-message");
|
||||
}
|
||||
.icon-sidebar::before {
|
||||
content: map.get($icons-map, "sidebar");
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -157,6 +157,7 @@ export type FontIconName =
|
||||
| 'readchats'
|
||||
| 'recent'
|
||||
| 'reload'
|
||||
| 'remove-quote'
|
||||
| 'remove'
|
||||
| 'reopen-topic'
|
||||
| 'replace'
|
||||
@ -178,6 +179,7 @@ export type FontIconName =
|
||||
| 'share-screen-outlined'
|
||||
| 'share-screen-stop'
|
||||
| 'share-screen'
|
||||
| 'show-message'
|
||||
| 'sidebar'
|
||||
| 'skip-next'
|
||||
| 'skip-previous'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user