Message / Contact: Support copying number, support non-registered (#1860)
This commit is contained in:
parent
1f99bd3d49
commit
39cdd8e8e8
@ -60,7 +60,9 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
openForwardMenuForSelectedMessages,
|
openForwardMenuForSelectedMessages,
|
||||||
downloadSelectedMessages,
|
downloadSelectedMessages,
|
||||||
copySelectedMessages,
|
copySelectedMessages,
|
||||||
|
showNotification,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||||
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
|
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
|
||||||
@ -78,8 +80,11 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback(() => {
|
||||||
copySelectedMessages();
|
copySelectedMessages();
|
||||||
|
showNotification({
|
||||||
|
message: lang('Share.Link.Copied'),
|
||||||
|
});
|
||||||
exitMessageSelectMode();
|
exitMessageSelectMode();
|
||||||
}, [copySelectedMessages, exitMessageSelectMode]);
|
}, [copySelectedMessages, exitMessageSelectMode, lang, showNotification]);
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
downloadSelectedMessages();
|
downloadSelectedMessages();
|
||||||
@ -89,8 +94,6 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
const prevSelectedMessagesCount = usePrevious(selectedMessagesCount || undefined, true);
|
const prevSelectedMessagesCount = usePrevious(selectedMessagesCount || undefined, true);
|
||||||
const renderingSelectedMessagesCount = isActive ? selectedMessagesCount : prevSelectedMessagesCount;
|
const renderingSelectedMessagesCount = isActive ? selectedMessagesCount : prevSelectedMessagesCount;
|
||||||
|
|
||||||
const lang = useLang();
|
|
||||||
|
|
||||||
const formattedMessagesCount = lang('VoiceOver.Chat.MessagesSelected', renderingSelectedMessagesCount, 'i');
|
const formattedMessagesCount = lang('VoiceOver.Chat.MessagesSelected', renderingSelectedMessagesCount, 'i');
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
|
|||||||
@ -20,6 +20,8 @@ type StateProps = {
|
|||||||
phoneCodeList: ApiCountryCode[];
|
phoneCodeList: ApiCountryCode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const UNREGISTERED_CONTACT_ID = '0';
|
||||||
|
|
||||||
const Contact: FC<OwnProps & StateProps> = ({
|
const Contact: FC<OwnProps & StateProps> = ({
|
||||||
contact, user, phoneCodeList,
|
contact, user, phoneCodeList,
|
||||||
}) => {
|
}) => {
|
||||||
@ -31,6 +33,7 @@ const Contact: FC<OwnProps & StateProps> = ({
|
|||||||
phoneNumber,
|
phoneNumber,
|
||||||
userId,
|
userId,
|
||||||
} = contact;
|
} = contact;
|
||||||
|
const isRegistered = userId !== UNREGISTERED_CONTACT_ID;
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
openChat({ id: userId });
|
openChat({ id: userId });
|
||||||
@ -38,8 +41,8 @@ const Contact: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName('Contact', Boolean(userId) && 'interactive')}
|
className={buildClassName('Contact', isRegistered && 'interactive')}
|
||||||
onClick={userId ? handleClick : undefined}
|
onClick={isRegistered ? handleClick : undefined}
|
||||||
>
|
>
|
||||||
<Avatar size="large" user={user} text={firstName || lastName} />
|
<Avatar size="large" user={user} text={firstName || lastName} />
|
||||||
<div className="contact-info">
|
<div className="contact-info">
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
|||||||
import { MessageListType } from '../../../global/types';
|
import { MessageListType } from '../../../global/types';
|
||||||
import { ApiAvailableReaction, ApiMessage } from '../../../api/types';
|
import { ApiAvailableReaction, ApiMessage } from '../../../api/types';
|
||||||
import { IAlbum, IAnchorPosition } from '../../../types';
|
import { IAlbum, IAnchorPosition } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectActiveDownloadIds,
|
selectActiveDownloadIds,
|
||||||
selectAllowedMessageActions,
|
selectAllowedMessageActions,
|
||||||
@ -19,17 +20,18 @@ import {
|
|||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||||
import { getDayStartAt } from '../../../util/dateFormat';
|
import { getDayStartAt } from '../../../util/dateFormat';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { REM } from '../../common/helpers/mediaDimensions';
|
|
||||||
|
|
||||||
import DeleteMessageModal from '../../common/DeleteMessageModal';
|
import DeleteMessageModal from '../../common/DeleteMessageModal';
|
||||||
import ReportModal from '../../common/ReportModal';
|
import ReportModal from '../../common/ReportModal';
|
||||||
import PinMessageModal from '../../common/PinMessageModal';
|
import PinMessageModal from '../../common/PinMessageModal';
|
||||||
import MessageContextMenu from './MessageContextMenu';
|
import MessageContextMenu from './MessageContextMenu';
|
||||||
import CalendarModal from '../../common/CalendarModal';
|
import CalendarModal from '../../common/CalendarModal';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
|
||||||
|
|
||||||
const START_SIZE = 2 * REM;
|
const START_SIZE = 2 * REM;
|
||||||
|
|
||||||
@ -300,7 +302,12 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
const handleCopyLink = useCallback(() => {
|
const handleCopyLink = useCallback(() => {
|
||||||
copyTextToClipboard(`https://t.me/${chatUsername || `c/${message.chatId.replace('-', '')}`}/${message.id}`);
|
copyTextToClipboard(`https://t.me/${chatUsername || `c/${message.chatId.replace('-', '')}`}/${message.id}`);
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [chatUsername, closeMenu, message.chatId, message.id]);
|
}, [chatUsername, closeMenu, message]);
|
||||||
|
|
||||||
|
const handleCopyNumber = useCallback(() => {
|
||||||
|
copyTextToClipboard(message.content.contact!.phoneNumber);
|
||||||
|
closeMenu();
|
||||||
|
}, [closeMenu, message]);
|
||||||
|
|
||||||
const handleDownloadClick = useCallback(() => {
|
const handleDownloadClick = useCallback(() => {
|
||||||
(album?.messages || [message]).forEach((msg) => {
|
(album?.messages || [message]).forEach((msg) => {
|
||||||
@ -383,6 +390,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={closeMenu}
|
onClose={closeMenu}
|
||||||
onCopyLink={handleCopyLink}
|
onCopyLink={handleCopyLink}
|
||||||
onCopyMessages={handleCopyMessages}
|
onCopyMessages={handleCopyMessages}
|
||||||
|
onCopyNumber={handleCopyNumber}
|
||||||
onDownload={handleDownloadClick}
|
onDownload={handleDownloadClick}
|
||||||
onSaveGif={handleSaveGif}
|
onSaveGif={handleSaveGif}
|
||||||
onShowSeenBy={handleOpenSeenByModal}
|
onShowSeenBy={handleOpenSeenByModal}
|
||||||
@ -464,6 +472,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
&& !areReactionsEmpty(message.reactions) && message.reactions.canSeeList;
|
&& !areReactionsEmpty(message.reactions) && message.reactions.canSeeList;
|
||||||
const canRemoveReaction = isPrivate && message.reactions?.results?.some((l) => l.isChosen);
|
const canRemoveReaction = isPrivate && message.reactions?.results?.some((l) => l.isChosen);
|
||||||
const isProtected = selectIsMessageProtected(global, message);
|
const isProtected = selectIsMessageProtected(global, message);
|
||||||
|
const canCopyNumber = Boolean(message.content.contact);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
availableReactions: global.availableReactions,
|
availableReactions: global.availableReactions,
|
||||||
@ -479,7 +488,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canForward: !isProtected && !isScheduled && canForward,
|
canForward: !isProtected && !isScheduled && canForward,
|
||||||
canFaveSticker: !isScheduled && canFaveSticker,
|
canFaveSticker: !isScheduled && canFaveSticker,
|
||||||
canUnfaveSticker: !isScheduled && canUnfaveSticker,
|
canUnfaveSticker: !isScheduled && canUnfaveSticker,
|
||||||
canCopy: !isProtected && canCopy,
|
canCopy: canCopyNumber || (!isProtected && canCopy),
|
||||||
canCopyLink: !isProtected && !isScheduled && canCopyLink,
|
canCopyLink: !isProtected && !isScheduled && canCopyLink,
|
||||||
canSelect,
|
canSelect,
|
||||||
canDownload: !isProtected && canDownload,
|
canDownload: !isProtected && canDownload,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useEffect, useRef,
|
FC, memo, useCallback, useEffect, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import { ApiAvailableReaction, ApiMessage, ApiUser } from '../../../api/types';
|
import { ApiAvailableReaction, ApiMessage, ApiUser } from '../../../api/types';
|
||||||
import { IAnchorPosition } from '../../../types';
|
import { IAnchorPosition } from '../../../types';
|
||||||
@ -8,11 +9,12 @@ import { IAnchorPosition } from '../../../types';
|
|||||||
import { getMessageCopyOptions } from './helpers/copyOptions';
|
import { getMessageCopyOptions } from './helpers/copyOptions';
|
||||||
import { disableScrolling, enableScrolling } from '../../../util/scrollLock';
|
import { disableScrolling, enableScrolling } from '../../../util/scrollLock';
|
||||||
import { getUserFullName } from '../../../global/helpers';
|
import { getUserFullName } from '../../../global/helpers';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
|
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useContextMenuPosition from '../../../hooks/useContextMenuPosition';
|
import useContextMenuPosition from '../../../hooks/useContextMenuPosition';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
|
||||||
|
|
||||||
import Menu from '../../ui/Menu';
|
import Menu from '../../ui/Menu';
|
||||||
import MenuItem from '../../ui/MenuItem';
|
import MenuItem from '../../ui/MenuItem';
|
||||||
@ -66,6 +68,7 @@ type OwnProps = {
|
|||||||
onCloseAnimationEnd?: () => void;
|
onCloseAnimationEnd?: () => void;
|
||||||
onCopyLink?: () => void;
|
onCopyLink?: () => void;
|
||||||
onCopyMessages?: (messageIds: number[]) => void;
|
onCopyMessages?: (messageIds: number[]) => void;
|
||||||
|
onCopyNumber?: () => void;
|
||||||
onDownload?: () => void;
|
onDownload?: () => void;
|
||||||
onSaveGif?: () => void;
|
onSaveGif?: () => void;
|
||||||
onShowSeenBy?: () => void;
|
onShowSeenBy?: () => void;
|
||||||
@ -121,6 +124,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
onCopyLink,
|
onCopyLink,
|
||||||
|
onCopyNumber,
|
||||||
onDownload,
|
onDownload,
|
||||||
onSaveGif,
|
onSaveGif,
|
||||||
onShowSeenBy,
|
onShowSeenBy,
|
||||||
@ -128,16 +132,28 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
onSendReaction,
|
onSendReaction,
|
||||||
onCopyMessages,
|
onCopyMessages,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { showNotification } = getActions();
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||||
const copyOptions = getMessageCopyOptions(message, onClose, canCopyLink ? onCopyLink : undefined, onCopyMessages);
|
const lang = useLang();
|
||||||
const noReactions = !isPrivate && !enabledReactions?.length;
|
const noReactions = !isPrivate && !enabledReactions?.length;
|
||||||
const withReactions = canShowReactionList && !noReactions;
|
const withReactions = canShowReactionList && !noReactions;
|
||||||
|
|
||||||
const [isReady, markIsReady, unmarkIsReady] = useFlag();
|
const [isReady, markIsReady, unmarkIsReady] = useFlag();
|
||||||
|
|
||||||
|
const handleAfterCopy = useCallback(() => {
|
||||||
|
showNotification({
|
||||||
|
message: lang('Share.Link.Copied'),
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
}, [lang, onClose, showNotification]);
|
||||||
|
|
||||||
|
const copyOptions = getMessageCopyOptions(
|
||||||
|
message, handleAfterCopy, canCopyLink ? onCopyLink : undefined, onCopyMessages, onCopyNumber,
|
||||||
|
);
|
||||||
|
|
||||||
const getTriggerElement = useCallback(() => {
|
const getTriggerElement = useCallback(() => {
|
||||||
return document.querySelector(`.Transition__slide--active > .MessageList div[data-message-id="${message.id}"]`);
|
return document.querySelector(`.Transition__slide--active > .MessageList div[data-message-id="${message.id}"]`);
|
||||||
}, [message.id]);
|
}, [message.id]);
|
||||||
@ -193,8 +209,6 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
return enableScrolling;
|
return enableScrolling;
|
||||||
}, [withScroll]);
|
}, [withScroll]);
|
||||||
|
|
||||||
const lang = useLang();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { ApiMediaFormat, ApiMessage } from '../../../../api/types';
|
|||||||
|
|
||||||
import * as mediaLoader from '../../../../util/mediaLoader';
|
import * as mediaLoader from '../../../../util/mediaLoader';
|
||||||
import {
|
import {
|
||||||
|
getMessageContact,
|
||||||
getMessageMediaHash,
|
getMessageMediaHash,
|
||||||
getMessagePhoto,
|
getMessagePhoto,
|
||||||
getMessageText,
|
getMessageText,
|
||||||
@ -24,11 +25,13 @@ export function getMessageCopyOptions(
|
|||||||
afterEffect?: () => void,
|
afterEffect?: () => void,
|
||||||
onCopyLink?: () => void,
|
onCopyLink?: () => void,
|
||||||
onCopyMessages?: (messageIds: number[]) => void,
|
onCopyMessages?: (messageIds: number[]) => void,
|
||||||
|
onCopyNumber?: () => void,
|
||||||
): ICopyOptions {
|
): ICopyOptions {
|
||||||
const options: ICopyOptions = [];
|
const options: ICopyOptions = [];
|
||||||
const text = getMessageText(message);
|
const text = getMessageText(message);
|
||||||
const photo = getMessagePhoto(message)
|
const photo = getMessagePhoto(message)
|
||||||
|| (!getMessageWebPageVideo(message) ? getMessageWebPagePhoto(message) : undefined);
|
|| (!getMessageWebPageVideo(message) ? getMessageWebPagePhoto(message) : undefined);
|
||||||
|
const contact = getMessageContact(message);
|
||||||
const mediaHash = getMessageMediaHash(message, 'inline');
|
const mediaHash = getMessageMediaHash(message, 'inline');
|
||||||
const canImageBeCopied = photo && (mediaHash || hasMessageLocalBlobUrl(message)) && CLIPBOARD_ITEM_SUPPORTED;
|
const canImageBeCopied = photo && (mediaHash || hasMessageLocalBlobUrl(message)) && CLIPBOARD_ITEM_SUPPORTED;
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
@ -41,9 +44,7 @@ export function getMessageCopyOptions(
|
|||||||
Promise.resolve(mediaHash ? mediaLoader.fetch(mediaHash, ApiMediaFormat.BlobUrl) : photo!.blobUrl)
|
Promise.resolve(mediaHash ? mediaLoader.fetch(mediaHash, ApiMediaFormat.BlobUrl) : photo!.blobUrl)
|
||||||
.then(copyImageToClipboard);
|
.then(copyImageToClipboard);
|
||||||
|
|
||||||
if (afterEffect) {
|
afterEffect?.();
|
||||||
afterEffect();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -68,9 +69,7 @@ export function getMessageCopyOptions(
|
|||||||
copyTextToClipboard(clipboardText);
|
copyTextToClipboard(clipboardText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (afterEffect) {
|
afterEffect?.();
|
||||||
afterEffect();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -82,9 +81,19 @@ export function getMessageCopyOptions(
|
|||||||
handler: () => {
|
handler: () => {
|
||||||
onCopyLink();
|
onCopyLink();
|
||||||
|
|
||||||
if (afterEffect) {
|
afterEffect?.();
|
||||||
afterEffect();
|
},
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contact && onCopyNumber) {
|
||||||
|
options.push({
|
||||||
|
label: 'lng_profile_copy_phone',
|
||||||
|
icon: 'copy',
|
||||||
|
handler: () => {
|
||||||
|
onCopyNumber();
|
||||||
|
|
||||||
|
afterEffect?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user