import type { FC } from '../../lib/teact/teact'; import { memo, useEffect, useMemo, useState, } from '../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../global'; import type { ApiChat, ApiChatMember } from '../../api/types'; import type { IRadioOption } from '../ui/CheckboxGroup'; import { getHasAdminRight, getUserFirstOrLastName, isChatBasicGroup, isChatChannel, isChatSuperGroup, isSystemBot, } from '../../global/helpers'; import { getPeerTitle } from '../../global/helpers/peers'; import { getSendersFromSelectedMessages, selectBot, selectCanDeleteSelectedMessages, selectChat, selectChatFullInfo, selectIsChatWithBot, selectSenderFromMessage, selectTabState, selectUser, } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import { isUserId } from '../../util/entities/ids'; import { buildCollectionByCallback, unique } from '../../util/iteratees'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import renderText from './helpers/renderText'; import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import useOldLang from '../../hooks/useOldLang'; import usePreviousDeprecated from '../../hooks/usePreviousDeprecated'; import useManagePermissions from '../right/hooks/useManagePermissions'; import PermissionCheckboxList from '../main/PermissionCheckboxList'; import Button from '../ui/Button'; import Checkbox from '../ui/Checkbox'; import CheckboxGroup from '../ui/CheckboxGroup'; import ListItem from '../ui/ListItem'; import Modal from '../ui/Modal'; import Avatar from './Avatar'; import AvatarList from './AvatarList'; import Icon from './icons/Icon'; import styles from './DeleteMessageModal.module.scss'; export type OwnProps = { isOpen?: boolean; }; type StateProps = { chat?: ApiChat; isChannel?: boolean; isSuperGroup?: boolean; messageIds?: number[]; canDeleteForAll?: boolean; contactName?: string; currentUserId?: string; willDeleteForCurrentUserOnly?: boolean; willDeleteForAll?: boolean; adminMembersById?: Record; chatBot?: boolean; isSchedule?: boolean; onConfirm?: NoneToVoidFunction; canBanUsers?: boolean; isCreator?: boolean; linkedChatId?: string; }; const DeleteMessageModal: FC = ({ isOpen, chat, isChannel, isSuperGroup, isSchedule, currentUserId, messageIds, isCreator, canDeleteForAll, contactName, willDeleteForCurrentUserOnly, willDeleteForAll, chatBot, adminMembersById, canBanUsers, linkedChatId, onConfirm, }) => { const { closeDeleteMessageModal, deleteMessages, reportChannelSpam, deleteChatMember, deleteScheduledMessages, exitMessageSelectMode, updateChatMemberBannedRights, deleteParticipantHistory, } = getActions(); const prevIsOpen = usePreviousDeprecated(isOpen); const oldLang = useOldLang(); const lang = useLang(); const { permissions, havePermissionChanged, handlePermissionChange, resetPermissions, } = useManagePermissions(chat?.defaultBannedRights); const [peerIdsToDeleteAll, setPeerIdsToDeleteAll] = useState([]); const [peerIdsToBan, setPeerIdsToBan] = useState([]); const [peerIdsToReportSpam, setPeerIdsToReportSpam] = useState([]); const [isMediaDropdownOpen, setIsMediaDropdownOpen] = useState(false); const [isAdditionalOptionsVisible, setIsAdditionalOptionsVisible] = useState(false); const [shouldDeleteForAll, setShouldDeleteForAll] = useState(true); const peerList = useMemo(() => { if (isChannel || !messageIds || !chat) { return MEMO_EMPTY_ARRAY; } const global = getGlobal(); const senderArray = getSendersFromSelectedMessages(global, chat.id, messageIds); return senderArray ? unique(senderArray) .filter((peer) => ( peer?.id !== chat?.id && peer?.id !== linkedChatId && peer?.id !== chat?.linkedMonoforumId )) : MEMO_EMPTY_ARRAY; }, [chat, isChannel, linkedChatId, messageIds]); const buildNestedOptionListWithAvatars = useLastCallback(() => { return peerList.map((member) => { return { value: member.id, label: getPeerTitle(lang, member) || '', leftElement: , }; }); }); const peerListToDeleteAll = useMemo(() => { return peerList.filter((peer) => ( peer.id !== linkedChatId && peer.id !== chat?.linkedMonoforumId && peer.id !== currentUserId )); }, [peerList, currentUserId, linkedChatId, chat?.linkedMonoforumId]); const peerListToReportSpam = useMemo(() => { return peerList.filter((peer) => ( peer.id !== currentUserId && peer.id !== linkedChatId && peer.id !== chat?.linkedMonoforumId )); }, [peerList, currentUserId, linkedChatId, chat?.linkedMonoforumId]); const peerListToBan = useMemo(() => { const isCurrentUserInList = peerList.some((peer) => peer.id === currentUserId); const shouldReturnEmpty = !canBanUsers || isCurrentUserInList; if (shouldReturnEmpty) { return MEMO_EMPTY_ARRAY; } return peerList.filter((peer) => { const isAdmin = adminMembersById?.[peer.id]; return isCreator || !isAdmin; }); }, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById]); const shouldShowAdditionalOptions = useMemo(() => { return Boolean(peerListToDeleteAll.length || peerListToReportSpam.length || peerListToBan.length); }, [peerListToDeleteAll, peerListToReportSpam, peerListToBan]); const shouldShowOption = shouldShowAdditionalOptions && !canDeleteForAll && !isSchedule && isSuperGroup; const peerNames = useMemo(() => { if (!peerList || isSchedule) return {}; return buildCollectionByCallback(peerList, (peer) => [peer.id, getPeerTitle(lang, peer)]); }, [isSchedule, lang, peerList]); const ACTION_SPAM_OPTION: IRadioOption[] = useMemo(() => { return [ { value: messageIds && peerList.length >= 2 ? 'spam' : peerList?.[0]?.id, label: oldLang('ReportSpamTitle'), nestedOptions: messageIds && peerList.length >= 2 ? [ ...buildNestedOptionListWithAvatars().filter((opt) => opt.value !== linkedChatId && opt.value !== chat?.linkedMonoforumId && opt.value !== currentUserId), ] : undefined, }, ]; }, [messageIds, peerList, oldLang, linkedChatId, chat?.linkedMonoforumId, currentUserId]); const ACTION_DELETE_OPTION: IRadioOption[] = useMemo(() => { return [ { value: messageIds && peerList.length >= 2 ? 'delete_all' : peerList?.[0]?.id, label: messageIds && peerList.length >= 2 ? oldLang('DeleteAllFromUsers') : oldLang('DeleteAllFrom', Object.values(peerNames)[0]), nestedOptions: messageIds && peerList.length >= 2 ? [ ...buildNestedOptionListWithAvatars().filter((opt) => opt.value !== linkedChatId && opt.value !== chat?.linkedMonoforumId && opt.value !== currentUserId), ] : undefined, }, ]; }, [messageIds, peerList, oldLang, peerNames, linkedChatId, chat?.linkedMonoforumId, currentUserId]); const ACTION_BAN_OPTION: IRadioOption[] = useMemo(() => { return [ { value: messageIds && peerList.length >= 2 ? 'ban' : peerList?.[0]?.id, label: messageIds && peerList.length >= 2 ? (isAdditionalOptionsVisible ? oldLang('DeleteRestrictUsers') : oldLang('DeleteBanUsers')) : (isAdditionalOptionsVisible ? oldLang('KickFromSupergroup') : oldLang('DeleteBan', Object.values(peerNames)[0])), nestedOptions: messageIds && peerList.length >= 2 ? [ ...buildNestedOptionListWithAvatars(), ] : undefined, }, ]; }, [isAdditionalOptionsVisible, oldLang, messageIds, peerList, peerNames]); const toggleAdditionalOptions = useLastCallback(() => { setIsAdditionalOptionsVisible((prev) => !prev); }); const filterMessageIdByPeerId = useLastCallback((peerIds: string[], selectedMessageIdList: number[]) => { if (!chat) return MEMO_EMPTY_ARRAY; const global = getGlobal(); return selectedMessageIdList.filter((msgId) => { const sender = selectSenderFromMessage(global, chat.id, msgId); return sender && peerIds.includes(sender.id); }); }); const handleReportSpam = useLastCallback((userMessagesMap: Record) => { Object.entries(userMessagesMap).forEach(([userId, messageIdList]) => { if (messageIdList.length) { reportChannelSpam({ participantId: userId, chatId: chat!.id, messageIds: messageIdList, }); } }); }); const handleDeleteMessages = useLastCallback((filteredMessageIdList: number[]) => { deleteMessages({ messageIds: filteredMessageIdList, shouldDeleteForAll: true }); }); const handleDeleteAllPeerMessages = useLastCallback((peerIdList: string[]) => { if (!chat) return; peerIdList.forEach((peerId) => { deleteParticipantHistory({ peerId, chatId: chat.id }); }); }); const handleDeleteMember = useLastCallback((filteredUserIdList: string[]) => { filteredUserIdList.forEach((userId) => { deleteChatMember({ chatId: chat!.id, userId }); }); }); const handleUpdateChatMemberBannedRights = useLastCallback((filteredUserIdList: string[]) => { filteredUserIdList.forEach((userId) => { updateChatMemberBannedRights({ chatId: chat!.id, userId, bannedRights: permissions, }); }); }); const handleDeleteMessageList = useLastCallback(() => { if (!chat || !messageIds) return; onConfirm?.(); if (isSchedule) { deleteScheduledMessages({ messageIds }); } else if (shouldShowOption) { if (peerIdsToReportSpam?.length) { const global = getGlobal(); const peerIdList = peerIdsToReportSpam.filter((option) => !Number.isNaN(Number(option))); const messageList = messageIds.reduce>((acc, msgId) => { const peer = selectSenderFromMessage(global, chat.id, msgId); if (peer && peerIdList.includes(peer.id)) { if (!acc[peer.id]) { acc[peer.id] = []; } acc[peer.id].push(Number(msgId)); } return acc; }, {}); handleReportSpam(messageList); } if (peerIdsToDeleteAll?.length) { const peerIdList = peerIdsToDeleteAll.filter((option) => !Number.isNaN(Number(option))); handleDeleteAllPeerMessages(peerIdList); } if (peerIdsToBan?.length && !havePermissionChanged) { const peerIdList = peerIdsToBan.filter((option) => !Number.isNaN(Number(option))); handleDeleteMember(peerIdList); const filteredMessageIdList = filterMessageIdByPeerId(peerIdList, messageIds); handleDeleteMessages(filteredMessageIdList); } if (peerIdsToBan?.length && havePermissionChanged) { const peerIdList = peerIdsToBan.filter((option) => !Number.isNaN(Number(option))); handleUpdateChatMemberBannedRights(peerIdList); } if (!peerIdsToReportSpam?.length || !peerIdsToDeleteAll?.length || !peerIdsToBan?.length) { deleteMessages({ messageIds, shouldDeleteForAll }); } } else { deleteMessages({ messageIds, shouldDeleteForAll }); } closeDeleteMessageModal(); exitMessageSelectMode(); }); const onCloseHandler = useLastCallback(() => { closeDeleteMessageModal(); }); useEffect(() => { if (!isOpen && prevIsOpen) { setPeerIdsToReportSpam([]); setPeerIdsToDeleteAll([]); setPeerIdsToBan([]); setShouldDeleteForAll(true); setIsMediaDropdownOpen(false); setIsAdditionalOptionsVisible(false); resetPermissions(); } }, [isOpen, prevIsOpen, resetPermissions]); function renderHeader() { return (
{shouldShowOption && ( )}

{oldLang('Chat.DeleteMessagesConfirmation', messageIds?.length)}

); } function renderAdditionalActionOptions() { return (
= 2} /> {peerListToDeleteAll?.length > 0 && ( = 2} /> )} {peerListToBan?.length > 0 && ( = 2} /> )}
); } function renderPartiallyRestrictedUser() { return (

{oldLang('UserRestrictionsCanDoUsers', peerList.length)}

); } return (
{renderHeader()} {shouldShowOption && ( <>

{oldLang('DeleteAdditionalActions')}

{renderAdditionalActionOptions()} {renderPartiallyRestrictedUser()} {peerIdsToBan?.length && canBanUsers ? ( {oldLang(isAdditionalOptionsVisible ? 'DeleteToggleBanUsers' : 'DeleteToggleRestrictUsers')} ) : setIsAdditionalOptionsVisible(false)} )} {(canDeleteForAll || chatBot || !shouldShowOption) && ( <>

{messageIds && messageIds.length > 1 ? lang('AreYouSureDeleteFewMessages') : lang('AreYouSureDeleteSingleMessage')}

{willDeleteForCurrentUserOnly && (

{oldLang('lng_delete_for_me_chat_hint', 1, 'i')}

)} {willDeleteForAll && (

{oldLang('lng_delete_for_everyone_hint', 1, 'i')}

)} )} {canDeleteForAll && ( )}
); }; export default memo(withGlobal( (global): Complete => { const { deleteMessageModal, } = selectTabState(global); const messageIds = deleteMessageModal?.messageIds; const chatId = deleteMessageModal?.chatId; const { canDeleteForAll } = selectCanDeleteSelectedMessages(global, messageIds); const chat = chatId ? selectChat(global, chatId) : undefined; const chatFullInfo = chat && selectChatFullInfo(global, chat.id); const linkedChatId = chatFullInfo?.linkedChatId; const isChannel = Boolean(chat) && isChatChannel(chat); const isSuperGroup = Boolean(chat) && isChatSuperGroup(chat); const isSchedule = deleteMessageModal?.isSchedule; const onConfirm = deleteMessageModal?.onConfirm; const contactName = chatId && isUserId(chatId) ? getUserFirstOrLastName(selectUser(global, chatId)) : undefined; const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id)); const adminMembersById = chatFullInfo?.adminMembersById; const canBanUsers = chat && getHasAdminRight(chat, 'banUsers') && !chat.isMonoforum; // TODO: Ban in channel in case of monoforum const isCreator = chat?.isCreator; const isChatWithBot = chatId ? selectIsChatWithBot(global, chatId) : undefined; const willDeleteForCurrentUserOnly = (chat && isChatBasicGroup(chat) && !canDeleteForAll) || isChatWithBot; const willDeleteForAll = chat && (isChatSuperGroup(chat) || isChannel); return { chat, isChannel, isSuperGroup, messageIds, currentUserId: global.currentUserId, canDeleteForAll: !isSchedule && canDeleteForAll, contactName, willDeleteForCurrentUserOnly, willDeleteForAll, adminMembersById, chatBot, canBanUsers, linkedChatId, isSchedule, isCreator, onConfirm, }; }, )(DeleteMessageModal));