ClosableEmbeddedMessage Menu: Reply in Another Chat (#4504)

This commit is contained in:
Alexander Zinchuk 2024-05-17 15:45:21 +02:00
parent 6eaaa5dce5
commit 6e37c5b163
20 changed files with 451 additions and 219 deletions

View File

@ -409,6 +409,7 @@ export interface ApiInputMessageReplyInfo {
replyToTopId?: number; replyToTopId?: number;
replyToPeerId?: string; replyToPeerId?: string;
quoteText?: ApiFormattedText; quoteText?: ApiFormattedText;
isShowingDelayNeeded?: boolean;
} }
export interface ApiInputStoryReplyInfo { export interface ApiInputStoryReplyInfo {
@ -532,6 +533,9 @@ export type MediaContent = {
isExpiredRoundVideo?: boolean; isExpiredRoundVideo?: boolean;
ttlSeconds?: number; ttlSeconds?: number;
}; };
export type MediaContainer = {
content: MediaContent;
};
export interface ApiMessage { export interface ApiMessage {
id: number; id: number;

View 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

View 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

View File

@ -110,12 +110,12 @@
.message-title { .message-title {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: nowrap;
flex: 1; flex: 1;
column-gap: 0.25rem; column-gap: 0.25rem;
} }
.message-title, .embedded-sender { .message-title, .embedded-sender, .embedded-sender-chat {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -3,13 +3,15 @@ import React, { useMemo, useRef } from '../../../lib/teact/teact';
import type { import type {
ApiChat, ApiChat,
ApiMessage, ApiPeer, ApiReplyInfo, ApiMessage, ApiPeer, ApiReplyInfo, MediaContainer,
} from '../../../api/types'; } from '../../../api/types';
import type { ChatTranslatedMessages } from '../../../global/types'; import type { ChatTranslatedMessages } from '../../../global/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { IconName } from '../../../types/icons'; import type { IconName } from '../../../types/icons';
import { CONTENT_NOT_SUPPORTED } from '../../../config';
import { import {
getMediaContentTypeDescription,
getMessageIsSpoiler, getMessageIsSpoiler,
getMessageMediaHash, getMessageMediaHash,
getMessageRoundVideo, getMessageRoundVideo,
@ -18,6 +20,7 @@ import {
isChatChannel, isChatChannel,
isChatGroup, isChatGroup,
isMessageTranslatable, isMessageTranslatable,
isUserId,
} from '../../../global/helpers'; } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed'; import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed';
@ -58,7 +61,7 @@ type OwnProps = {
isOpen?: boolean; isOpen?: boolean;
observeIntersectionForLoading?: ObserveFn; observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn; observeIntersectionForPlaying?: ObserveFn;
onClick: NoneToVoidFunction; onClick: ((e: React.MouseEvent) => void);
}; };
const NBSP = '\u00A0'; const NBSP = '\u00A0';
@ -128,7 +131,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
} }
if (!message) { if (!message) {
return customText || NBSP; return customText || renderMediaContentType(wrappedMedia) || NBSP;
} }
if (isActionMessage(message)) { 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() { function renderSender() {
if (title) { if (title) {
return renderText(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); const isReplyToQuote = isInComposer && Boolean(replyInfo && 'quoteText' in replyInfo && replyInfo?.quoteText);
return ( return (
<> <>
{!isChatSender && ( {checkShouldRenderSenderTitle() && (
<span className="embedded-sender"> <span className="embedded-sender">
{renderText(isReplyToQuote ? lang('ReplyToQuote', senderTitle) : senderTitle)} {renderText(isReplyToQuote ? lang('ReplyToQuote', senderTitle) : senderTitle)}
</span> </span>
)} )}
{icon && <Icon name={icon} className="embedded-chat-icon" />} {icon && <Icon name={icon} className="embedded-chat-icon" />}
{icon && senderChatTitle && renderText(senderChatTitle)} {icon && senderChatTitle && (
<span className="embedded-sender-chat">
{renderText(senderChatTitle)}
</span>
)}
</> </>
); );
} }

View File

@ -5,9 +5,16 @@ import React, {
import { getActions, getGlobal, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
import type { ThreadId } from '../../types'; import type { ThreadId } from '../../types';
import { MAIN_THREAD_ID } from '../../api/types';
import { getChatTitle, getUserFirstOrLastName, isUserId } from '../../global/helpers'; 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 useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -23,6 +30,7 @@ interface StateProps {
currentUserId?: string; currentUserId?: string;
isManyMessages?: boolean; isManyMessages?: boolean;
isStory?: boolean; isStory?: boolean;
isReplying?: boolean;
} }
const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
@ -30,8 +38,10 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
currentUserId, currentUserId,
isManyMessages, isManyMessages,
isStory, isStory,
isReplying,
}) => { }) => {
const { const {
openChatOrTopicWithReplyInDraft,
setForwardChatOrTopic, setForwardChatOrTopic,
exitForwardMode, exitForwardMode,
forwardToSavedMessages, forwardToSavedMessages,
@ -84,9 +94,15 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
forwardToSavedMessages(); forwardToSavedMessages();
showNotification({ message }); showNotification({ message });
} else { } 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(() => { const handleClose = useCallback(() => {
exitForwardMode(); exitForwardMode();
@ -110,9 +126,12 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>((global): StateProps => { export default memo(withGlobal<OwnProps>((global): StateProps => {
const { messageIds, storyId } = selectTabState(global).forwardMessages; const { messageIds, storyId } = selectTabState(global).forwardMessages;
const currentChatId = selectCurrentChat(global)?.id;
const isReplying = currentChatId && selectDraft(global, currentChatId, MAIN_THREAD_ID)?.replyInfo;
return { return {
currentUserId: global.currentUserId, currentUserId: global.currentUserId,
isManyMessages: (messageIds?.length || 0) > 1, isManyMessages: (messageIds?.length || 0) > 1,
isStory: Boolean(storyId), isStory: Boolean(storyId),
isReplying: Boolean(isReplying),
}; };
})(ForwardRecipientPicker)); })(ForwardRecipientPicker));

View File

@ -4,11 +4,14 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; 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 { stripCustomEmoji } from '../../../global/helpers';
import { import {
selectCanAnimateInterface, selectCanAnimateInterface,
selectChat,
selectChatMessage, selectChatMessage,
selectCurrentMessageList, selectCurrentMessageList,
selectDraft, selectDraft,
@ -56,6 +59,9 @@ type StateProps = {
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
isContextMenuDisabled?: boolean; isContextMenuDisabled?: boolean;
isReplyToDiscussion?: boolean; isReplyToDiscussion?: boolean;
isInChangingRecipientMode?: boolean;
isChangingChats?: boolean;
senderChat?: ApiChat;
}; };
type OwnProps = { type OwnProps = {
@ -80,12 +86,16 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
isContextMenuDisabled, isContextMenuDisabled,
isReplyToDiscussion, isReplyToDiscussion,
onClear, onClear,
isInChangingRecipientMode,
isChangingChats,
senderChat,
}) => { }) => {
const { const {
resetDraftReplyInfo, resetDraftReplyInfo,
updateDraftReplyInfo,
setEditingId, setEditingId,
focusMessage, focusMessage,
changeForwardRecipient, changeRecipient,
setForwardNoAuthors, setForwardNoAuthors,
setForwardNoCaptions, setForwardNoCaptions,
exitForwardMode, exitForwardMode,
@ -95,15 +105,17 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
const lang = useLang(); const lang = useLang();
const isReplyToTopicStart = message?.content.action?.type === 'topicCreate'; const isReplyToTopicStart = message?.content.action?.type === 'topicCreate';
const isShowingReply = replyInfo && !shouldForceShowEditing;
const isReplyWithQuote = Boolean(replyInfo?.quoteText);
const isForwarding = Boolean(forwardedMessagesCount); const isForwarding = Boolean(forwardedMessagesCount);
const isShown = Boolean( const isShown = Boolean(
((replyInfo || editingId) && message) ((replyInfo || editingId) && message && !isInChangingRecipientMode)
|| (sender && forwardedMessagesCount), || (sender && forwardedMessagesCount),
); );
const canAnimate = useAsyncRendering( const canAnimate = useAsyncRendering(
[isShown, isForwarding], [isShown, isForwarding],
isShown && isForwarding ? FORWARD_RENDERING_DELAY : undefined, isShown && isChangingChats ? FORWARD_RENDERING_DELAY : undefined,
); );
const { const {
@ -115,6 +127,11 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
undefined, undefined,
!shouldAnimate, !shouldAnimate,
); );
useEffect(() => {
if (canAnimate && replyInfo?.isShowingDelayNeeded) {
updateDraftReplyInfo({ isShowingDelayNeeded: false });
}
});
const clearEmbedded = useLastCallback(() => { const clearEmbedded = useLastCallback(() => {
if (replyInfo && !shouldForceShowEditing) { if (replyInfo && !shouldForceShowEditing) {
@ -129,24 +146,36 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]); useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]);
const handleMessageClick = useLastCallback((): void => { const {
if (isForwarding) return; isContextMenuOpen, contextMenuPosition, handleContextMenu,
handleContextMenuClose, handleContextMenuHide,
} = useContextMenuHandlers(ref);
const focusMessageFromDraft = () => {
focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true }); 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 => { const handleClearClick = useLastCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
e.stopPropagation(); e.stopPropagation();
clearEmbedded(); clearEmbedded();
handleContextMenuHide();
}); });
const buildAutoCloseMenuItemHandler = (action: NoneToVoidFunction) => {
const handleChangeRecipientClick = useLastCallback(() => { return () => {
changeForwardRecipient(); handleContextMenuClose();
}); action();
};
const { };
isContextMenuOpen, contextMenuPosition, handleContextMenu, const handleForwardToAnotherChatClick = useLastCallback(buildAutoCloseMenuItemHandler(changeRecipient));
handleContextMenuClose, handleContextMenuHide, const handleShowMessageClick = useLastCallback(buildAutoCloseMenuItemHandler(focusMessageFromDraft));
} = useContextMenuHandlers(ref); const handleRemoveQuoteClick = useLastCallback(buildAutoCloseMenuItemHandler(
() => updateDraftReplyInfo({ quoteText: undefined }),
));
const handleChangeReplyRecipientClick = useLastCallback(buildAutoCloseMenuItemHandler(changeRecipient));
const handleDoNotReplyClick = useLastCallback(buildAutoCloseMenuItemHandler(clearEmbedded));
const getTriggerElement = useLastCallback(() => ref.current); const getTriggerElement = useLastCallback(() => ref.current);
const getRootElement = useLastCallback(() => ref.current!); const getRootElement = useLastCallback(() => ref.current!);
@ -162,8 +191,11 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
); );
useEffect(() => { useEffect(() => {
if (!shouldRender) handleContextMenuClose(); if (!shouldRender) {
}, [handleContextMenuClose, shouldRender]); handleContextMenuClose();
handleContextMenuHide();
}
}, [handleContextMenuClose, handleContextMenuHide, shouldRender]);
const className = buildClassName('ComposerEmbeddedMessage', transitionClassNames); const className = buildClassName('ComposerEmbeddedMessage', transitionClassNames);
const renderingSender = useCurrentOrPrev(sender, true); const renderingSender = useCurrentOrPrev(sender, true);
@ -172,8 +204,6 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
getPeerColorClass(renderingSender), getPeerColorClass(renderingSender),
); );
const isShowingReply = replyInfo && !shouldForceShowEditing;
const leftIcon = useMemo(() => { const leftIcon = useMemo(() => {
if (isShowingReply) { if (isShowingReply) {
return 'reply'; return 'reply';
@ -212,9 +242,9 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
} }
return ( return (
<div className={className} ref={ref} onContextMenu={handleContextMenu} onClick={handleContextMenu}> <div className={className} ref={ref} onContextMenu={handleContextMenu}>
<div className={innerClassName}> <div className={innerClassName}>
<div className="embedded-left-icon"> <div className="embedded-left-icon" onClick={handleContextMenu}>
{renderingLeftIcon && <Icon name={renderingLeftIcon} />} {renderingLeftIcon && <Icon name={renderingLeftIcon} />}
{Boolean(replyInfo?.quoteText) && ( {Boolean(replyInfo?.quoteText) && (
<Icon name="quote" className="quote-reply" /> <Icon name="quote" className="quote-reply" />
@ -231,6 +261,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
title={(editingId && !isShowingReply) ? lang('EditMessage') title={(editingId && !isShowingReply) ? lang('EditMessage')
: noAuthors ? lang('HiddenSendersNameDescription') : undefined} : noAuthors ? lang('HiddenSendersNameDescription') : undefined}
onClick={handleMessageClick} onClick={handleMessageClick}
senderChat={senderChat}
/> />
<Button <Button
className="embedded-cancel" className="embedded-cancel"
@ -242,7 +273,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
> >
<i className="icon icon-close" /> <i className="icon icon-close" />
</Button> </Button>
{isForwarding && !isContextMenuDisabled && ( {(isShowingReply || isForwarding) && !isContextMenuDisabled && (
<Menu <Menu
isOpen={isContextMenuOpen} isOpen={isContextMenuOpen}
transformOriginX={transformOriginX} transformOriginX={transformOriginX}
@ -254,55 +285,83 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
onClose={handleContextMenuClose} onClose={handleContextMenuClose}
onCloseAnimationEnd={handleContextMenuHide} onCloseAnimationEnd={handleContextMenuHide}
> >
<MenuItem {isForwarding && (
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 && (
<> <>
<MenuSeparator />
<MenuItem <MenuItem
icon={!noCaptions ? 'message-succeeded' : undefined} icon={!noAuthors ? 'message-succeeded' : undefined}
customIcon={noCaptions ? <i className="icon icon-placeholder" /> : undefined} customIcon={noAuthors ? <i className="icon icon-placeholder" /> : undefined}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => setForwardNoCaptions({ onClick={() => setForwardNoAuthors({
noCaptions: false, noAuthors: false,
})} })}
> >
{lang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.ShowCaption' : 'ShowCaption')} {lang(forwardedMessagesCount > 1 ? 'ShowSenderNames' : 'ShowSendersName')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon={noCaptions ? 'message-succeeded' : undefined} icon={noAuthors ? 'message-succeeded' : undefined}
customIcon={!noCaptions ? <i className="icon icon-placeholder" /> : undefined} customIcon={!noAuthors ? <i className="icon icon-placeholder" /> : undefined}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => setForwardNoCaptions({ onClick={() => setForwardNoAuthors({
noCaptions: true, 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> </MenuItem>
</> </>
)} )}
<MenuSeparator />
<MenuItem icon="replace" onClick={handleChangeRecipientClick}>
{lang('ChangeRecipient')}
</MenuItem>
</Menu> </Menu>
)} )}
</div> </div>
@ -319,7 +378,7 @@ export default memo(withGlobal<OwnProps>(
const { const {
forwardMessages: { forwardMessages: {
fromChatId, toChatId, messageIds: forwardMessageIds, noAuthors, noCaptions, fromChatId, toChatId, messageIds: forwardMessageIds, noAuthors, noCaptions, isModalShown,
}, },
} = selectTabState(global); } = selectTabState(global);
@ -332,7 +391,10 @@ export default memo(withGlobal<OwnProps>(
const draft = selectDraft(global, chatId, threadId); const draft = selectDraft(global, chatId, threadId);
const replyInfo = draft?.replyInfo; const replyInfo = draft?.replyInfo;
const replyToPeerId = replyInfo?.replyToPeerId;
const senderChat = replyToPeerId ? selectChat(global, replyToPeerId) : undefined;
const isChangingChats = isForwarding || replyInfo?.isShowingDelayNeeded;
let message: ApiMessage | undefined; let message: ApiMessage | undefined;
if (replyInfo && !shouldForceShowEditing) { if (replyInfo && !shouldForceShowEditing) {
message = selectChatMessage(global, replyInfo.replyToPeerId || chatId, replyInfo.replyToMsgId); message = selectChatMessage(global, replyInfo.replyToPeerId || chatId, replyInfo.replyToMsgId);
@ -389,6 +451,9 @@ export default memo(withGlobal<OwnProps>(
isCurrentUserPremium: selectIsCurrentUserPremium(global), isCurrentUserPremium: selectIsCurrentUserPremium(global),
isContextMenuDisabled, isContextMenuDisabled,
isReplyToDiscussion, isReplyToDiscussion,
isInChangingRecipientMode: isModalShown,
isChangingChats,
senderChat,
}; };
}, },
)(ComposerEmbeddedMessage)); )(ComposerEmbeddedMessage));

View File

@ -52,13 +52,11 @@ const MenuItem: FC<MenuItemProps> = (props) => {
const lang = useLang(); const lang = useLang();
const { isTouchScreen } = useAppLayout(); const { isTouchScreen } = useAppLayout();
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => { const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
if (disabled || !onClick) { if (disabled || !onClick) {
e.stopPropagation();
e.preventDefault(); e.preventDefault();
return; return;
} }
onClick(e, clickArg); onClick(e, clickArg);
}); });
@ -67,13 +65,12 @@ const MenuItem: FC<MenuItemProps> = (props) => {
return; return;
} }
e.stopPropagation();
if (disabled || !onClick) { if (disabled || !onClick) {
e.stopPropagation();
e.preventDefault(); e.preventDefault();
return; return;
} }
onClick(e, clickArg); onClick(e, clickArg);
}); });
const handleMouseDown = useLastCallback((e: React.SyntheticEvent<HTMLDivElement | HTMLAnchorElement>) => { const handleMouseDown = useLastCallback((e: React.SyntheticEvent<HTMLDivElement | HTMLAnchorElement>) => {

View File

@ -13,6 +13,7 @@ import type {
ApiSticker, ApiSticker,
ApiStory, ApiStory,
ApiStorySkipped, ApiStorySkipped,
ApiUser,
ApiVideo, ApiVideo,
} from '../../../api/types'; } from '../../../api/types';
import type { MessageKey } from '../../../util/messageKey'; import type { MessageKey } from '../../../util/messageKey';
@ -122,6 +123,7 @@ import {
selectPeerStory, selectPeerStory,
selectPinnedIds, selectPinnedIds,
selectRealLastReadId, selectRealLastReadId,
selectReplyCanBeSentToChat,
selectScheduledMessage, selectScheduledMessage,
selectSendAs, selectSendAs,
selectSponsoredMessage, 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> => { addActionHandler('setForwardChatOrTopic', async (global, actions, payload): Promise<void> => {
const { chatId, topicId, tabId = getCurrentTabId() } = payload; const { chatId, topicId, tabId = getCurrentTabId() } = payload;
let user = selectUser(global, chatId); const user = selectUser(global, chatId);
if (user && selectForwardsContainVoiceMessages(global, tabId)) { const isSelectForwardsContainVoiceMessages = selectForwardsContainVoiceMessages(global, tabId);
let fullInfo = selectUserFullInfo(global, chatId); if (isSelectForwardsContainVoiceMessages && user && !await checkIfVoiceMessagesAllowed(global, user, chatId)) {
if (!fullInfo) { actions.showDialog({
const { accessHash } = user; data: {
const result = await callApi('fetchFullUser', { id: chatId, accessHash }); message: translate('VoiceMessagesRestrictedByPrivacy', getUserFullName(user)),
global = getGlobal(); },
user = result?.user; tabId,
fullInfo = result?.fullInfo; });
} return;
if (fullInfo!.noVoiceMessages) {
actions.showDialog({
data: {
message: translate('VoiceMessagesRestrictedByPrivacy', getUserFullName(user)),
},
tabId,
});
return;
}
} }
global = getGlobal();
if (!selectForwardsCanBeSentToChat(global, chatId, tabId)) { if (!selectForwardsCanBeSentToChat(global, chatId, tabId)) {
actions.showAllowedMessageTypesNotification({ chatId, tabId }); actions.showAllowedMessageTypesNotification({ chatId, tabId });

View File

@ -515,7 +515,7 @@ addActionHandler('openForwardMenu', (global, actions, payload): ActionReturnType
}, tabId); }, tabId);
}); });
addActionHandler('changeForwardRecipient', (global, actions, payload): ActionReturnType => { addActionHandler('changeRecipient', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {}; const { tabId = getCurrentTabId() } = payload || {};
return updateTabState(global, { return updateTabState(global, {
forwardMessages: { forwardMessages: {

View File

@ -10,7 +10,7 @@ import type {
ApiPhoto, ApiPhoto,
ApiVideo, ApiVideo,
ApiWebDocument, ApiWebDocument,
MediaContent, MediaContainer,
} from '../../api/types'; } from '../../api/types';
import { ApiMediaFormat } from '../../api/types'; import { ApiMediaFormat } from '../../api/types';
@ -25,10 +25,6 @@ import {
import { getDocumentHasPreview } from '../../components/common/helpers/documentInfo'; import { getDocumentHasPreview } from '../../components/common/helpers/documentInfo';
import { getAttachmentType, matchLinkInMessageText } from './messages'; import { getAttachmentType, matchLinkInMessageText } from './messages';
type MediaContainer = {
content: MediaContent;
};
type Target = type Target =
'micro' 'micro'
| 'pictogram' | 'pictogram'

View File

@ -1,6 +1,6 @@
import type { TeactNode } from '../../lib/teact/teact'; 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 type { LangFn } from '../../hooks/useLang';
import { ApiMessageEntityTypes } from '../../api/types'; import { ApiMessageEntityTypes } from '../../api/types';
@ -8,7 +8,7 @@ import { CONTENT_NOT_SUPPORTED } from '../../config';
import trimText from '../../util/trimText'; import trimText from '../../util/trimText';
import { getGlobal } from '../index'; import { getGlobal } from '../index';
import { import {
getExpiredMessageDescription, getMessageText, getMessageTranscription, isExpiredMessage, getExpiredMessageContentDescription, getMessageText, getMessageTranscription, isExpiredMessageContent,
} from './messages'; } from './messages';
import { getUserFirstOrLastName } from './users'; import { getUserFirstOrLastName } from './users';
@ -102,11 +102,23 @@ export function getMessageSummaryEmoji(message: ApiMessage) {
return undefined; return undefined;
} }
export function getMediaContentTypeDescription(lang: LangFn, content: MediaContent) {
return getSummaryDescription(lang, content);
}
export function getMessageSummaryDescription( export function getMessageSummaryDescription(
lang: LangFn, lang: LangFn,
message: ApiMessage, message: ApiMessage,
truncatedText?: string | TeactNode, truncatedText?: string | TeactNode,
isExtended = false, isExtended = false,
) {
return getSummaryDescription(lang, message.content, message, truncatedText, isExtended);
}
function getSummaryDescription(
lang: LangFn,
mediaContent: MediaContent,
message?: ApiMessage,
truncatedText?: string | TeactNode,
isExtended = false,
) { ) {
const { const {
text, text,
@ -124,12 +136,12 @@ export function getMessageSummaryDescription(
storyData, storyData,
giveaway, giveaway,
giveawayResults, giveawayResults,
} = message.content; } = mediaContent;
let hasUsedTruncatedText = false; let hasUsedTruncatedText = false;
let summary: string | TeactNode | undefined; let summary: string | TeactNode | undefined;
if (message.groupedId) { if (message?.groupedId) {
hasUsedTruncatedText = true; hasUsedTruncatedText = true;
summary = truncatedText || lang('lng_in_dlg_album'); summary = truncatedText || lang('lng_in_dlg_album');
} }
@ -149,7 +161,7 @@ export function getMessageSummaryDescription(
} }
if (audio) { if (audio) {
summary = getMessageAudioCaption(message) || lang('AttachMusic'); summary = getMessageAudioCaption(mediaContent) || lang('AttachMusic');
} }
if (voice) { if (voice) {
@ -203,7 +215,7 @@ export function getMessageSummaryDescription(
} }
if (storyData) { if (storyData) {
if (storyData.isMention) { if (message && storyData.isMention) {
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global // eslint-disable-next-line eslint-multitab-tt/no-immediate-global
const global = getGlobal(); const global = getGlobal();
const firstName = getUserFirstOrLastName(global.users.byId[message.chatId]); 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.You', firstName)
: lang('Chat.Service.StoryMentioned', firstName); : lang('Chat.Service.StoryMentioned', firstName);
} else { } else {
summary = lang('ForwardedStory'); summary = message ? lang('ForwardedStory') : lang('Chat.ReplyStory');
} }
} }
if (isExpiredMessage(message)) { if (isExpiredMessageContent(mediaContent)) {
const expiredMessageText = getExpiredMessageDescription(lang, message); const expiredMessageText = getExpiredMessageContentDescription(lang, mediaContent);
if (expiredMessageText) { if (expiredMessageText) {
summary = expiredMessageText; summary = expiredMessageText;
} }
@ -232,11 +244,11 @@ export function generateBrailleSpoiler(length: number) {
.join(''); .join('');
} }
function getMessageAudioCaption(message: ApiMessage) { function getMessageAudioCaption(mediaContent: MediaContent) {
const { const {
audio, audio,
text, text,
} = message.content; } = mediaContent;
return (audio && [audio.title, audio.performer].filter(Boolean) return (audio && [audio.title, audio.performer].filter(Boolean)
.join(' — ')) || (text?.text); .join(' — ')) || (text?.text);

View File

@ -1,6 +1,7 @@
import type { import type {
ApiAttachment, ApiChat, ApiMessage, ApiMessageEntityTextUrl, ApiPeer, ApiStory, ApiUser, ApiAttachment, ApiChat, ApiMessage, ApiMessageEntityTextUrl, ApiPeer, ApiStory, ApiUser,
} from '../../api/types'; } from '../../api/types';
import type { MediaContent } from '../../api/types/messages';
import type { LangFn } from '../../hooks/useLang'; import type { LangFn } from '../../hooks/useLang';
import { ApiMessageEntityTypes } from '../../api/types'; 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 { 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) { if (isExpiredVoice) {
return langFn('Message.VoiceMessageExpired'); return langFn('Message.VoiceMessageExpired');
} else if (isExpiredRoundVideo) { } else if (isExpiredRoundVideo) {
@ -330,7 +334,11 @@ export function getExpiredMessageDescription(langFn: LangFn, message: ApiMessage
} }
export function isExpiredMessage(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); return Boolean(isExpiredVoice || isExpiredRoundVideo);
} }

View File

@ -9,6 +9,7 @@ import type {
ApiStickerSetInfo, ApiStickerSetInfo,
} from '../../api/types'; } from '../../api/types';
import type { ThreadId } from '../../types'; import type { ThreadId } from '../../types';
import type { IAllowedAttachmentOptions } from '../helpers';
import type { import type {
ChatTranslatedMessages, ChatTranslatedMessages,
GlobalState, MessageListType, TabArgs, TabThread, Thread, GlobalState, MessageListType, TabArgs, TabThread, Thread,
@ -47,6 +48,7 @@ import {
isChatGroup, isChatGroup,
isChatSuperGroup, isChatSuperGroup,
isCommonBoxChat, isCommonBoxChat,
isExpiredMessage,
isForwardedMessage, isForwardedMessage,
isMessageDocumentSticker, isMessageDocumentSticker,
isMessageFailed, isMessageFailed,
@ -1336,7 +1338,7 @@ export function selectForwardsContainVoiceMessages<T extends GlobalState>(
const chatMessages = selectChatMessages(global, fromChatId!); const chatMessages = selectChatMessages(global, fromChatId!);
return messageIds.some((messageId) => { return messageIds.some((messageId) => {
const message = chatMessages[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]; const requestedInChat = selectTabState(global, tabId).requestedTranslations.byChatId[chatId];
return requestedInChat?.toLanguage || requestedInChat?.manualMessages?.[messageId]; 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>( export function selectForwardsCanBeSentToChat<T extends GlobalState>(
global: T, global: T,
toChatId: string, toChatId: string,
@ -1374,33 +1391,30 @@ export function selectForwardsCanBeSentToChat<T extends GlobalState>(
const chatFullInfo = selectChatFullInfo(global, toChatId); const chatFullInfo = selectChatFullInfo(global, toChatId);
const chatMessages = selectChatMessages(global, fromChatId!); const chatMessages = selectChatMessages(global, fromChatId!);
const { const options = getAllowedAttachmentOptions(chat, chatFullInfo);
canSendVoices, canSendRoundVideos, canSendStickers, canSendDocuments, canSendAudios, canSendVideos, return !messageIds!.some((messageId) => сheckMessageSendingDenied(chatMessages[messageId], options));
canSendPhotos, canSendGifs, canSendPlainText, }
} = getAllowedAttachmentOptions(chat, chatFullInfo); function сheckMessageSendingDenied(message: ApiMessage, options: IAllowedAttachmentOptions) {
return !messageIds!.some((messageId) => { const isVoice = message.content.voice;
const message = chatMessages[messageId]; const isRoundVideo = message.content.video?.isRound;
const isVoice = message.content.voice; const isPhoto = message.content.photo;
const isRoundVideo = message.content.video?.isRound; const isGif = message.content.video?.isGif;
const isPhoto = message.content.photo; const isVideo = message.content.video && !isRoundVideo && !isGif;
const isGif = message.content.video?.isGif; const isAudio = message.content.audio;
const isVideo = message.content.video && !isRoundVideo && !isGif; const isDocument = message.content.document;
const isAudio = message.content.audio; const isSticker = message.content.sticker;
const isDocument = message.content.document; const isPlainText = message.content.text
const isSticker = message.content.sticker; && !isVoice && !isRoundVideo && !isSticker && !isDocument && !isAudio && !isVideo && !isPhoto && !isGif;
const isPlainText = message.content.text
&& !isVoice && !isRoundVideo && !isSticker && !isDocument && !isAudio && !isVideo && !isPhoto && !isGif;
return (isVoice && !canSendVoices) return (isVoice && !options.canSendVoices)
|| (isRoundVideo && !canSendRoundVideos) || (isRoundVideo && !options.canSendRoundVideos)
|| (isSticker && !canSendStickers) || (isSticker && !options.canSendStickers)
|| (isDocument && !canSendDocuments) || (isDocument && !options.canSendDocuments)
|| (isAudio && !canSendAudios) || (isAudio && !options.canSendAudios)
|| (isVideo && !canSendVideos) || (isVideo && !options.canSendVideos)
|| (isPhoto && !canSendPhotos) || (isPhoto && !options.canSendPhotos)
|| (isGif && !canSendGifs) || (isGif && !options.canSendGifs)
|| (isPlainText && !canSendPlainText); || (isPlainText && !options.canSendPlainText);
});
} }
export function selectCanTranslateMessage<T extends GlobalState>( export function selectCanTranslateMessage<T extends GlobalState>(

View File

@ -2540,6 +2540,10 @@ export interface ActionPayloads {
chatId: string; chatId: string;
topicId?: number; topicId?: number;
} & WithTabId; } & WithTabId;
openChatOrTopicWithReplyInDraft: {
chatId: string;
topicId?: number;
} & WithTabId;
forwardMessages: { forwardMessages: {
isSilent?: boolean; isSilent?: boolean;
scheduledAt?: number; scheduledAt?: number;
@ -2551,7 +2555,7 @@ export interface ActionPayloads {
noCaptions: boolean; noCaptions: boolean;
} & WithTabId; } & WithTabId;
exitForwardMode: WithTabId | undefined; exitForwardMode: WithTabId | undefined;
changeForwardRecipient: WithTabId | undefined; changeRecipient: WithTabId | undefined;
forwardToSavedMessages: WithTabId | undefined; forwardToSavedMessages: WithTabId | undefined;
forwardStory: { forwardStory: {
toChatId: string; toChatId: string;

View File

@ -24,6 +24,7 @@ const useShowTransition = (
if (closeTimeoutRef.current) { if (closeTimeoutRef.current) {
window.clearTimeout(closeTimeoutRef.current); window.clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = undefined; closeTimeoutRef.current = undefined;
} }
} else { } else {

View File

@ -187,83 +187,85 @@ $icons-map: (
"readchats": "\f19c", "readchats": "\f19c",
"recent": "\f19d", "recent": "\f19d",
"reload": "\f19e", "reload": "\f19e",
"remove": "\f19f", "remove-quote": "\f19f",
"reopen-topic": "\f1a0", "remove": "\f1a0",
"replace": "\f1a1", "reopen-topic": "\f1a1",
"replies": "\f1a2", "replace": "\f1a2",
"reply-filled": "\f1a3", "replies": "\f1a3",
"reply": "\f1a4", "reply-filled": "\f1a4",
"revenue-split": "\f1a5", "reply": "\f1a5",
"revote": "\f1a6", "revenue-split": "\f1a6",
"save-story": "\f1a7", "revote": "\f1a7",
"saved-messages": "\f1a8", "save-story": "\f1a8",
"schedule": "\f1a9", "saved-messages": "\f1a9",
"search": "\f1aa", "schedule": "\f1aa",
"select": "\f1ab", "search": "\f1ab",
"send-outline": "\f1ac", "select": "\f1ac",
"send": "\f1ad", "send-outline": "\f1ad",
"settings-filled": "\f1ae", "send": "\f1ae",
"settings": "\f1af", "settings-filled": "\f1af",
"share-filled": "\f1b0", "settings": "\f1b0",
"share-screen-outlined": "\f1b1", "share-filled": "\f1b1",
"share-screen-stop": "\f1b2", "share-screen-outlined": "\f1b2",
"share-screen": "\f1b3", "share-screen-stop": "\f1b3",
"sidebar": "\f1b4", "share-screen": "\f1b4",
"skip-next": "\f1b5", "show-message": "\f1b5",
"skip-previous": "\f1b6", "sidebar": "\f1b6",
"smallscreen": "\f1b7", "skip-next": "\f1b7",
"smile": "\f1b8", "skip-previous": "\f1b8",
"sort": "\f1b9", "smallscreen": "\f1b9",
"speaker-muted-story": "\f1ba", "smile": "\f1ba",
"speaker-outline": "\f1bb", "sort": "\f1bb",
"speaker-story": "\f1bc", "speaker-muted-story": "\f1bc",
"speaker": "\f1bd", "speaker-outline": "\f1bd",
"spoiler-disable": "\f1be", "speaker-story": "\f1be",
"spoiler": "\f1bf", "speaker": "\f1bf",
"sport": "\f1c0", "spoiler-disable": "\f1c0",
"stats": "\f1c1", "spoiler": "\f1c1",
"stealth-future": "\f1c2", "sport": "\f1c2",
"stealth-past": "\f1c3", "stats": "\f1c3",
"stickers": "\f1c4", "stealth-future": "\f1c4",
"stop-raising-hand": "\f1c5", "stealth-past": "\f1c5",
"stop": "\f1c6", "stickers": "\f1c6",
"story-caption": "\f1c7", "stop-raising-hand": "\f1c7",
"story-expired": "\f1c8", "stop": "\f1c8",
"story-priority": "\f1c9", "story-caption": "\f1c9",
"story-reply": "\f1ca", "story-expired": "\f1ca",
"strikethrough": "\f1cb", "story-priority": "\f1cb",
"tag-add": "\f1cc", "story-reply": "\f1cc",
"tag-crossed": "\f1cd", "strikethrough": "\f1cd",
"tag-filter": "\f1ce", "tag-add": "\f1ce",
"tag-name": "\f1cf", "tag-crossed": "\f1cf",
"tag": "\f1d0", "tag-filter": "\f1d0",
"timer": "\f1d1", "tag-name": "\f1d1",
"transcribe": "\f1d2", "tag": "\f1d2",
"truck": "\f1d3", "timer": "\f1d3",
"unarchive": "\f1d4", "transcribe": "\f1d4",
"underlined": "\f1d5", "truck": "\f1d5",
"unlock-badge": "\f1d6", "unarchive": "\f1d6",
"unlock": "\f1d7", "underlined": "\f1d7",
"unmute": "\f1d8", "unlock-badge": "\f1d8",
"unpin": "\f1d9", "unlock": "\f1d9",
"unread": "\f1da", "unmute": "\f1da",
"up": "\f1db", "unpin": "\f1db",
"user-filled": "\f1dc", "unread": "\f1dc",
"user-online": "\f1dd", "up": "\f1dd",
"user": "\f1de", "user-filled": "\f1de",
"video-outlined": "\f1df", "user-online": "\f1df",
"video-stop": "\f1e0", "user": "\f1e0",
"video": "\f1e1", "video-outlined": "\f1e1",
"view-once": "\f1e2", "video-stop": "\f1e2",
"voice-chat": "\f1e3", "video": "\f1e3",
"volume-1": "\f1e4", "view-once": "\f1e4",
"volume-2": "\f1e5", "voice-chat": "\f1e5",
"volume-3": "\f1e6", "volume-1": "\f1e6",
"web": "\f1e7", "volume-2": "\f1e7",
"webapp": "\f1e8", "volume-3": "\f1e8",
"word-wrap": "\f1e9", "web": "\f1e9",
"zoom-in": "\f1ea", "webapp": "\f1ea",
"zoom-out": "\f1eb", "word-wrap": "\f1eb",
"zoom-in": "\f1ec",
"zoom-out": "\f1ed",
); );
.icon-active-sessions::before { .icon-active-sessions::before {
@ -740,6 +742,9 @@ $icons-map: (
.icon-reload::before { .icon-reload::before {
content: map.get($icons-map, "reload"); content: map.get($icons-map, "reload");
} }
.icon-remove-quote::before {
content: map.get($icons-map, "remove-quote");
}
.icon-remove::before { .icon-remove::before {
content: map.get($icons-map, "remove"); content: map.get($icons-map, "remove");
} }
@ -803,6 +808,9 @@ $icons-map: (
.icon-share-screen::before { .icon-share-screen::before {
content: map.get($icons-map, "share-screen"); content: map.get($icons-map, "share-screen");
} }
.icon-show-message::before {
content: map.get($icons-map, "show-message");
}
.icon-sidebar::before { .icon-sidebar::before {
content: map.get($icons-map, "sidebar"); content: map.get($icons-map, "sidebar");
} }

Binary file not shown.

Binary file not shown.

View File

@ -157,6 +157,7 @@ export type FontIconName =
| 'readchats' | 'readchats'
| 'recent' | 'recent'
| 'reload' | 'reload'
| 'remove-quote'
| 'remove' | 'remove'
| 'reopen-topic' | 'reopen-topic'
| 'replace' | 'replace'
@ -178,6 +179,7 @@ export type FontIconName =
| 'share-screen-outlined' | 'share-screen-outlined'
| 'share-screen-stop' | 'share-screen-stop'
| 'share-screen' | 'share-screen'
| 'show-message'
| 'sidebar' | 'sidebar'
| 'skip-next' | 'skip-next'
| 'skip-previous' | 'skip-previous'