Sender Group Container: Support context menu for avatars (#5900)
This commit is contained in:
parent
f21ca52337
commit
7874fbbb03
@ -1953,3 +1953,4 @@
|
|||||||
"ApiMessageActionPaidMessagesRefundedIncoming" = "{user} refunded **{stars}** to you";
|
"ApiMessageActionPaidMessagesRefundedIncoming" = "{user} refunded **{stars}** to you";
|
||||||
"NotificationTitleNotSupportedInFrozenAccount" = "Your account is frozen";
|
"NotificationTitleNotSupportedInFrozenAccount" = "Your account is frozen";
|
||||||
"NotificationMessageNotSupportedInFrozenAccount" = "This action is not available";
|
"NotificationMessageNotSupportedInFrozenAccount" = "This action is not available";
|
||||||
|
"ContextMenuItemMention" = "Mention";
|
||||||
|
|||||||
@ -86,6 +86,7 @@ type OwnProps = {
|
|||||||
noPersonalPhoto?: boolean;
|
noPersonalPhoto?: boolean;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void;
|
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void;
|
||||||
|
onContextMenu?: (e: React.MouseEvent) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Avatar: FC<OwnProps> = ({
|
const Avatar: FC<OwnProps> = ({
|
||||||
@ -110,6 +111,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
loopIndefinitely,
|
loopIndefinitely,
|
||||||
noPersonalPhoto,
|
noPersonalPhoto,
|
||||||
onClick,
|
onClick,
|
||||||
|
onContextMenu,
|
||||||
}) => {
|
}) => {
|
||||||
const { openStoryViewer } = getActions();
|
const { openStoryViewer } = getActions();
|
||||||
|
|
||||||
@ -297,6 +299,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
aria-label={typeof content === 'string' ? author : undefined}
|
aria-label={typeof content === 'string' ? author : undefined}
|
||||||
style={buildStyle(`--_size: ${pxSize}px;`, customColor && `--color-user: ${customColor}`)}
|
style={buildStyle(`--_size: ${pxSize}px;`, customColor && `--color-user: ${customColor}`)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
onContextMenu={onContextMenu}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
>
|
>
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
|
|||||||
@ -93,6 +93,7 @@ import {
|
|||||||
selectNotifyDefaults,
|
selectNotifyDefaults,
|
||||||
selectNotifyException,
|
selectNotifyException,
|
||||||
selectNoWebPage,
|
selectNoWebPage,
|
||||||
|
selectPeer,
|
||||||
selectPeerPaidMessagesStars,
|
selectPeerPaidMessagesStars,
|
||||||
selectPeerStory,
|
selectPeerStory,
|
||||||
selectPerformanceSettingsValue,
|
selectPerformanceSettingsValue,
|
||||||
@ -297,6 +298,7 @@ type StateProps =
|
|||||||
disallowedGifts?: ApiDisallowedGifts;
|
disallowedGifts?: ApiDisallowedGifts;
|
||||||
isAccountFrozen?: boolean;
|
isAccountFrozen?: boolean;
|
||||||
isAppConfigLoaded?: boolean;
|
isAppConfigLoaded?: boolean;
|
||||||
|
insertingPeerIdMention?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MainButtonState {
|
enum MainButtonState {
|
||||||
@ -421,6 +423,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
disallowedGifts,
|
disallowedGifts,
|
||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
isAppConfigLoaded,
|
isAppConfigLoaded,
|
||||||
|
insertingPeerIdMention,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
sendMessage,
|
sendMessage,
|
||||||
@ -448,6 +451,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
setReactionEffect,
|
setReactionEffect,
|
||||||
hideEffectInComposer,
|
hideEffectInComposer,
|
||||||
updateChatSilentPosting,
|
updateChatSilentPosting,
|
||||||
|
updateInsertingPeerIdMention,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
@ -751,6 +755,15 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
currentUserId,
|
currentUserId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!insertingPeerIdMention) return;
|
||||||
|
const peer = selectPeer(getGlobal(), insertingPeerIdMention);
|
||||||
|
if (peer) {
|
||||||
|
insertMention(peer, true, true);
|
||||||
|
}
|
||||||
|
updateInsertingPeerIdMention({ peerId: undefined });
|
||||||
|
}, [insertingPeerIdMention, insertMention]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isInlineBotTooltipOpen,
|
isOpen: isInlineBotTooltipOpen,
|
||||||
botId: inlineBotId,
|
botId: inlineBotId,
|
||||||
@ -2410,6 +2423,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isStarsBalanceModalOpen = Boolean(tabState.starsBalanceModal);
|
const isStarsBalanceModalOpen = Boolean(tabState.starsBalanceModal);
|
||||||
const isAccountFrozen = selectIsCurrentUserFrozen(global);
|
const isAccountFrozen = selectIsCurrentUserFrozen(global);
|
||||||
const isAppConfigLoaded = global.isAppConfigLoaded;
|
const isAppConfigLoaded = global.isAppConfigLoaded;
|
||||||
|
const insertingPeerIdMention = tabState.insertingPeerIdMention;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
availableReactions: global.reactions.availableReactions,
|
availableReactions: global.reactions.availableReactions,
|
||||||
@ -2498,6 +2512,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
disallowedGifts: userFullInfo?.disallowedGifts,
|
disallowedGifts: userFullInfo?.disallowedGifts,
|
||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
isAppConfigLoaded,
|
isAppConfigLoaded,
|
||||||
|
insertingPeerIdMention,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Composer));
|
)(Composer));
|
||||||
|
|||||||
@ -750,6 +750,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
onScrollDownToggle={onScrollDownToggle}
|
onScrollDownToggle={onScrollDownToggle}
|
||||||
onNotchToggle={onNotchToggle}
|
onNotchToggle={onNotchToggle}
|
||||||
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
||||||
|
canPost={canPost}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Loading color="white" backgroundColor="dark" />
|
<Loading color="white" backgroundColor="dark" />
|
||||||
|
|||||||
@ -70,6 +70,7 @@ interface OwnProps {
|
|||||||
onScrollDownToggle: BooleanToVoidFunction;
|
onScrollDownToggle: BooleanToVoidFunction;
|
||||||
onNotchToggle: AnyToVoidFunction;
|
onNotchToggle: AnyToVoidFunction;
|
||||||
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||||
|
canPost?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
||||||
@ -104,6 +105,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
onScrollDownToggle,
|
onScrollDownToggle,
|
||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
onIntersectPinnedMessage,
|
onIntersectPinnedMessage,
|
||||||
|
canPost,
|
||||||
}) => {
|
}) => {
|
||||||
const { openHistoryCalendar } = getActions();
|
const { openHistoryCalendar } = getActions();
|
||||||
|
|
||||||
@ -325,6 +327,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
message={lastMessage}
|
message={lastMessage}
|
||||||
withAvatar={withAvatar}
|
withAvatar={withAvatar}
|
||||||
appearanceOrder={lastAppearanceOrder}
|
appearanceOrder={lastAppearanceOrder}
|
||||||
|
canPost={canPost}
|
||||||
>
|
>
|
||||||
{senderGroupElements}
|
{senderGroupElements}
|
||||||
</SenderGroupContainer>
|
</SenderGroupContainer>
|
||||||
|
|||||||
@ -2,13 +2,13 @@ import type { RefObject } from 'react';
|
|||||||
import { useEffect, useState } from '../../../../lib/teact/teact';
|
import { useEffect, useState } from '../../../../lib/teact/teact';
|
||||||
import { getGlobal } from '../../../../global';
|
import { getGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type { ApiChatMember, ApiUser } from '../../../../api/types';
|
import type { ApiChatMember, ApiPeer, ApiUser } from '../../../../api/types';
|
||||||
import type { Signal } from '../../../../util/signals';
|
import type { Signal } from '../../../../util/signals';
|
||||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||||
|
|
||||||
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||||
import { getMainUsername, getUserFirstOrLastName } from '../../../../global/helpers';
|
import { getMainUsername } from '../../../../global/helpers';
|
||||||
import { filterPeersByQuery } from '../../../../global/helpers/peers';
|
import { filterPeersByQuery, getPeerTitle } from '../../../../global/helpers/peers';
|
||||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||||
import { pickTruthy, unique } from '../../../../util/iteratees';
|
import { pickTruthy, unique } from '../../../../util/iteratees';
|
||||||
import { getCaretPosition, getHtmlBeforeSelection, setCaretPosition } from '../../../../util/selection';
|
import { getCaretPosition, getHtmlBeforeSelection, setCaretPosition } from '../../../../util/selection';
|
||||||
@ -17,6 +17,7 @@ import { prepareForRegExp } from '../helpers/prepareForRegExp';
|
|||||||
import { useThrottledResolver } from '../../../../hooks/useAsyncResolvers';
|
import { useThrottledResolver } from '../../../../hooks/useAsyncResolvers';
|
||||||
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
const THROTTLE = 300;
|
const THROTTLE = 300;
|
||||||
@ -39,6 +40,7 @@ export default function useMentionTooltip(
|
|||||||
topInlineBotIds?: string[],
|
topInlineBotIds?: string[],
|
||||||
currentUserId?: string,
|
currentUserId?: string,
|
||||||
) {
|
) {
|
||||||
|
const lang = useLang();
|
||||||
const [filteredUsers, setFilteredUsers] = useState<ApiUser[] | undefined>();
|
const [filteredUsers, setFilteredUsers] = useState<ApiUser[] | undefined>();
|
||||||
const [isManuallyClosed, markManuallyClosed, unmarkManuallyClosed] = useFlag(false);
|
const [isManuallyClosed, markManuallyClosed, unmarkManuallyClosed] = useFlag(false);
|
||||||
|
|
||||||
@ -95,19 +97,23 @@ export default function useMentionTooltip(
|
|||||||
setFilteredUsers(Object.values(pickTruthy(usersById, filteredIds)));
|
setFilteredUsers(Object.values(pickTruthy(usersById, filteredIds)));
|
||||||
}, [currentUserId, groupChatMembers, topInlineBotIds, getUsernameTag, getWithInlineBots]);
|
}, [currentUserId, groupChatMembers, topInlineBotIds, getUsernameTag, getWithInlineBots]);
|
||||||
|
|
||||||
const insertMention = useLastCallback((user: ApiUser, forceFocus = false) => {
|
const insertMention = useLastCallback((
|
||||||
if (!user.usernames && !getUserFirstOrLastName(user)) {
|
peer: ApiPeer,
|
||||||
|
forceFocus = false,
|
||||||
|
insertAtEnd = false,
|
||||||
|
) => {
|
||||||
|
if (!peer.usernames && !getPeerTitle(lang, peer)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainUsername = getMainUsername(user);
|
const mainUsername = getMainUsername(peer);
|
||||||
const userFirstOrLastName = getUserFirstOrLastName(user) || '';
|
const userFirstOrLastName = getPeerTitle(lang, peer) || '';
|
||||||
const htmlToInsert = mainUsername
|
const htmlToInsert = mainUsername
|
||||||
? `@${mainUsername}`
|
? `@${mainUsername}`
|
||||||
: `<a
|
: `<a
|
||||||
class="text-entity-link"
|
class="text-entity-link"
|
||||||
data-entity-type="${ApiMessageEntityTypes.MentionName}"
|
data-entity-type="${ApiMessageEntityTypes.MentionName}"
|
||||||
data-user-id="${user.id}"
|
data-user-id="${peer.id}"
|
||||||
contenteditable="false"
|
contenteditable="false"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>${userFirstOrLastName}</a>`;
|
>${userFirstOrLastName}</a>`;
|
||||||
@ -115,7 +121,8 @@ export default function useMentionTooltip(
|
|||||||
const inputEl = inputRef.current!;
|
const inputEl = inputRef.current!;
|
||||||
const htmlBeforeSelection = getHtmlBeforeSelection(inputEl);
|
const htmlBeforeSelection = getHtmlBeforeSelection(inputEl);
|
||||||
const fixedHtmlBeforeSelection = cleanWebkitNewLines(htmlBeforeSelection);
|
const fixedHtmlBeforeSelection = cleanWebkitNewLines(htmlBeforeSelection);
|
||||||
const atIndex = fixedHtmlBeforeSelection.lastIndexOf('@');
|
const atIndex = insertAtEnd ? fixedHtmlBeforeSelection.length
|
||||||
|
: fixedHtmlBeforeSelection.lastIndexOf('@');
|
||||||
const shiftCaretPosition = (mainUsername ? mainUsername.length + 1 : userFirstOrLastName.length)
|
const shiftCaretPosition = (mainUsername ? mainUsername.length + 1 : userFirstOrLastName.length)
|
||||||
- (fixedHtmlBeforeSelection.length - atIndex);
|
- (fixedHtmlBeforeSelection.length - atIndex);
|
||||||
|
|
||||||
|
|||||||
@ -32,3 +32,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contextMenu {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
:global(.bubble) {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import React, {
|
import React, {
|
||||||
memo,
|
memo,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -10,12 +11,17 @@ import type {
|
|||||||
ApiPeer,
|
ApiPeer,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
import { MESSAGE_APPEARANCE_DELAY } from '../../../config';
|
|
||||||
import {
|
import {
|
||||||
|
EDITABLE_INPUT_CSS_SELECTOR,
|
||||||
|
MESSAGE_APPEARANCE_DELAY,
|
||||||
|
} from '../../../config';
|
||||||
|
import {
|
||||||
|
getMainUsername,
|
||||||
isAnonymousForwardsChat,
|
isAnonymousForwardsChat,
|
||||||
isAnonymousOwnMessage,
|
isAnonymousOwnMessage,
|
||||||
isSystemBot,
|
isSystemBot,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
|
import { isApiPeerUser } from '../../../global/helpers/peers';
|
||||||
import {
|
import {
|
||||||
selectForwardedSender,
|
selectForwardedSender,
|
||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
@ -23,11 +29,15 @@ import {
|
|||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
|
import Menu from '../../ui/Menu';
|
||||||
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
|
||||||
import styles from './SenderGroupContainer.module.scss';
|
import styles from './SenderGroupContainer.module.scss';
|
||||||
|
|
||||||
@ -38,6 +48,7 @@ type OwnProps =
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
id: string;
|
id: string;
|
||||||
appearanceOrder: number;
|
appearanceOrder: number;
|
||||||
|
canPost?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -61,12 +72,16 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isRepliesChat,
|
isRepliesChat,
|
||||||
isAnonymousForwards,
|
isAnonymousForwards,
|
||||||
|
canPost,
|
||||||
}) => {
|
}) => {
|
||||||
const { openChat } = getActions();
|
const { openChat, updateInsertingPeerIdMention } = getActions();
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { forwardInfo } = message;
|
const { forwardInfo } = message;
|
||||||
|
|
||||||
const messageSender = canShowSender ? sender : undefined;
|
const messageSender = canShowSender ? sender : undefined;
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const noAppearanceAnimation = appearanceOrder <= 0;
|
const noAppearanceAnimation = appearanceOrder <= 0;
|
||||||
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
||||||
@ -81,8 +96,9 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
const shouldPreferOriginSender = forwardInfo
|
const shouldPreferOriginSender = forwardInfo
|
||||||
&& (isChatWithSelf || isRepliesChat || isAnonymousForwards || !messageSender);
|
&& (isChatWithSelf || isRepliesChat || isAnonymousForwards || !messageSender);
|
||||||
const avatarPeer = shouldPreferOriginSender ? originSender : messageSender;
|
const avatarPeer = shouldPreferOriginSender ? originSender : messageSender;
|
||||||
|
const isAvatarPeerUser = avatarPeer && isApiPeerUser(avatarPeer);
|
||||||
|
|
||||||
const handleAvatarClick = useLastCallback(() => {
|
const handleOpenChat = useLastCallback(() => {
|
||||||
if (!avatarPeer) {
|
if (!avatarPeer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -90,6 +106,21 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
openChat({ id: avatarPeer.id });
|
openChat({ id: avatarPeer.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleMention = useLastCallback(() => {
|
||||||
|
if (!avatarPeer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||||
|
if (messageInput) {
|
||||||
|
updateInsertingPeerIdMention({ peerId: avatarPeer.id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAvatarClick = useLastCallback(() => {
|
||||||
|
handleOpenChat();
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ref: avatarRef,
|
ref: avatarRef,
|
||||||
shouldRender,
|
shouldRender,
|
||||||
@ -98,6 +129,59 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
withShouldRender: true,
|
withShouldRender: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
|
handleContextMenu, handleContextMenuClose,
|
||||||
|
handleContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(ref);
|
||||||
|
|
||||||
|
const getTriggerElement = useLastCallback(() => avatarRef.current);
|
||||||
|
const getRootElement = useLastCallback(() => document.querySelector('.Transition_slide-active > .MessageList'));
|
||||||
|
const getMenuElement = useLastCallback(
|
||||||
|
() => ref?.current?.querySelector(`.${styles.contextMenu} .bubble`),
|
||||||
|
);
|
||||||
|
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||||
|
|
||||||
|
const canMention = canPost && avatarPeer && (isAvatarPeerUser || Boolean(getMainUsername(avatarPeer)));
|
||||||
|
const shouldRenderContextMenu = Boolean(contextMenuAnchor) && (isAvatarPeerUser || canMention);
|
||||||
|
|
||||||
|
function renderContextMenu() {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
isOpen={isContextMenuOpen}
|
||||||
|
anchor={contextMenuAnchor}
|
||||||
|
getTriggerElement={getTriggerElement}
|
||||||
|
getRootElement={getRootElement}
|
||||||
|
getLayout={getLayout}
|
||||||
|
getMenuElement={getMenuElement}
|
||||||
|
className={styles.contextMenu}
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
withPortal
|
||||||
|
autoClose
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{isAvatarPeerUser && (
|
||||||
|
<MenuItem
|
||||||
|
icon="comments"
|
||||||
|
onClick={handleOpenChat}
|
||||||
|
>
|
||||||
|
{lang('SendMessage')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canMention && (
|
||||||
|
<MenuItem
|
||||||
|
icon="mention"
|
||||||
|
onClick={handleMention}
|
||||||
|
>
|
||||||
|
{lang('ContextMenuItemMention')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function renderAvatar() {
|
function renderAvatar() {
|
||||||
const hiddenName = (!avatarPeer && forwardInfo) ? forwardInfo.hiddenUserName : undefined;
|
const hiddenName = (!avatarPeer && forwardInfo) ? forwardInfo.hiddenUserName : undefined;
|
||||||
|
|
||||||
@ -108,6 +192,7 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
peer={avatarPeer}
|
peer={avatarPeer}
|
||||||
text={hiddenName}
|
text={hiddenName}
|
||||||
onClick={avatarPeer ? handleAvatarClick : undefined}
|
onClick={avatarPeer ? handleAvatarClick : undefined}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -118,13 +203,14 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={id} className={className}>
|
<div id={id} className={className} ref={ref}>
|
||||||
{shouldRender && (
|
{shouldRender && (
|
||||||
<div ref={avatarRef} className={styles.avatarContainer}>
|
<div ref={avatarRef} className={styles.avatarContainer}>
|
||||||
{renderAvatar()}
|
{renderAvatar()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
|
{shouldRenderContextMenu && renderContextMenu()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -678,6 +678,13 @@ addActionHandler('saveEffectInDraft', (global, actions, payload): ActionReturnTy
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('updateInsertingPeerIdMention', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { peerId, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
return updateTabState(global, {
|
||||||
|
insertingPeerIdMention: peerId,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
async function saveDraft<T extends GlobalState>({
|
async function saveDraft<T extends GlobalState>({
|
||||||
global, chatId, threadId, draft, isLocalOnly, noLocalTimeUpdate,
|
global, chatId, threadId, draft, isLocalOnly, noLocalTimeUpdate,
|
||||||
} : {
|
} : {
|
||||||
|
|||||||
@ -969,6 +969,9 @@ export interface ActionPayloads {
|
|||||||
focusLastMessage: WithTabId | undefined;
|
focusLastMessage: WithTabId | undefined;
|
||||||
updateDraftReplyInfo: Partial<ApiInputMessageReplyInfo> & WithTabId;
|
updateDraftReplyInfo: Partial<ApiInputMessageReplyInfo> & WithTabId;
|
||||||
resetDraftReplyInfo: WithTabId | undefined;
|
resetDraftReplyInfo: WithTabId | undefined;
|
||||||
|
updateInsertingPeerIdMention: {
|
||||||
|
peerId?: string;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
// Multitab
|
// Multitab
|
||||||
destroyConnection: undefined;
|
destroyConnection: undefined;
|
||||||
|
|||||||
@ -799,4 +799,5 @@ export type TabState = {
|
|||||||
|
|
||||||
isWaitingForStarGiftUpgrade?: true;
|
isWaitingForStarGiftUpgrade?: true;
|
||||||
isWaitingForStarGiftTransfer?: true;
|
isWaitingForStarGiftTransfer?: true;
|
||||||
|
insertingPeerIdMention?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
1
src/types/language.d.ts
vendored
1
src/types/language.d.ts
vendored
@ -1502,6 +1502,7 @@ export interface LangPair {
|
|||||||
'ActionPaidMessageGroupPriceFree': undefined;
|
'ActionPaidMessageGroupPriceFree': undefined;
|
||||||
'NotificationTitleNotSupportedInFrozenAccount': undefined;
|
'NotificationTitleNotSupportedInFrozenAccount': undefined;
|
||||||
'NotificationMessageNotSupportedInFrozenAccount': undefined;
|
'NotificationMessageNotSupportedInFrozenAccount': undefined;
|
||||||
|
'ContextMenuItemMention': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user