Delete Message Modal: Add linked chat moderation (#5519)
This commit is contained in:
parent
45fc7aac7c
commit
e90bd8ba9a
@ -840,14 +840,15 @@ export function joinChannel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function deleteChatUser({
|
export function deleteChatUser({
|
||||||
chat, user,
|
chat, user, shouldRevokeHistory,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat; user: ApiUser;
|
chat: ApiChat; user: ApiUser; shouldRevokeHistory?: boolean;
|
||||||
}) {
|
}) {
|
||||||
if (chat.type !== 'chatTypeBasicGroup') return undefined;
|
if (chat.type !== 'chatTypeBasicGroup') return undefined;
|
||||||
return invokeRequest(new GramJs.messages.DeleteChatUser({
|
return invokeRequest(new GramJs.messages.DeleteChatUser({
|
||||||
chatId: buildInputEntity(chat.id, chat.accessHash) as BigInt.BigInteger,
|
chatId: buildInputEntity(chat.id, chat.accessHash) as BigInt.BigInteger,
|
||||||
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
|
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
|
||||||
|
revokeHistory: shouldRevokeHistory || undefined,
|
||||||
}), {
|
}), {
|
||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -832,6 +832,37 @@ export async function deleteMessages({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteParticipantHistory({
|
||||||
|
chat, peer, isRepeat = false,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat; peer: ApiPeer; isRepeat?: boolean;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(
|
||||||
|
new GramJs.channels.DeleteParticipantHistory({
|
||||||
|
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||||
|
participant: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
|
||||||
|
if (!isRepeat) {
|
||||||
|
sendApiUpdate({
|
||||||
|
'@type': 'deleteParticipantHistory',
|
||||||
|
chatId: chat.id,
|
||||||
|
peerId: peer.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.offset) {
|
||||||
|
await deleteParticipantHistory({ chat, peer, isRepeat: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteScheduledMessages({
|
export function deleteScheduledMessages({
|
||||||
chat, messageIds,
|
chat, messageIds,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@ -351,6 +351,12 @@ export type ApiUpdateDeleteHistory = {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiDeleteParticipantHistory = {
|
||||||
|
'@type': 'deleteParticipantHistory';
|
||||||
|
chatId: string;
|
||||||
|
peerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdateDeleteSavedHistory = {
|
export type ApiUpdateDeleteSavedHistory = {
|
||||||
'@type': 'deleteSavedHistory';
|
'@type': 'deleteSavedHistory';
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -806,8 +812,9 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateChatListType | ApiUpdateChatFolder | ApiUpdateChatFoldersOrder | ApiUpdateRecommendedChatFolders |
|
ApiUpdateChatListType | ApiUpdateChatFolder | ApiUpdateChatFoldersOrder | ApiUpdateRecommendedChatFolders |
|
||||||
ApiUpdateNewMessage | ApiUpdateMessage | ApiUpdateThreadInfo | ApiUpdateCommonBoxMessages |
|
ApiUpdateNewMessage | ApiUpdateMessage | ApiUpdateThreadInfo | ApiUpdateCommonBoxMessages |
|
||||||
ApiUpdateDeleteMessages | ApiUpdateMessagePoll | ApiUpdateMessagePollVote | ApiUpdateDeleteHistory |
|
ApiUpdateDeleteMessages | ApiUpdateMessagePoll | ApiUpdateMessagePollVote | ApiUpdateDeleteHistory |
|
||||||
ApiUpdateMessageSendSucceeded | ApiUpdateMessageSendFailed | ApiUpdateServiceNotification |
|
ApiDeleteParticipantHistory | ApiUpdateMessageSendSucceeded | ApiUpdateMessageSendFailed |
|
||||||
ApiDeleteContact | ApiUpdateUser | ApiUpdateUserStatus | ApiUpdateUserFullInfo | ApiUpdateVideoProcessingPending |
|
ApiUpdateServiceNotification | ApiDeleteContact | ApiUpdateUser | ApiUpdateUserStatus |
|
||||||
|
ApiUpdateUserFullInfo | ApiUpdateVideoProcessingPending |
|
||||||
ApiUpdateAvatar | ApiUpdateMessageImage | ApiUpdateDraftMessage |
|
ApiUpdateAvatar | ApiUpdateMessageImage | ApiUpdateDraftMessage |
|
||||||
ApiUpdateError | ApiUpdateResetContacts | ApiUpdateStartEmojiInteraction |
|
ApiUpdateError | ApiUpdateResetContacts | ApiUpdateStartEmojiInteraction |
|
||||||
ApiUpdateFavoriteStickers | ApiUpdateStickerSet | ApiUpdateStickerSets | ApiUpdateStickerSetsOrder |
|
ApiUpdateFavoriteStickers | ApiUpdateStickerSet | ApiUpdateStickerSets | ApiUpdateStickerSetsOrder |
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
.mainContainer {
|
.main {
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
:global(.modal-dialog) {
|
||||||
|
max-width: 22.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin: 0 1rem;
|
margin-left: -0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@ -19,20 +25,13 @@
|
|||||||
|
|
||||||
.actionTitle {
|
.actionTitle {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
margin-left: 1rem;
|
|
||||||
color: var(--color-links);
|
color: var(--color-links);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
font-weight: var(--font-weight-semibold);
|
||||||
}
|
}
|
||||||
|
|
||||||
.restrictionTitle {
|
.restrictionTitle {
|
||||||
margin-bottom: 1.5rem;
|
margin: 1rem 0 0.5rem 1rem;
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listItemButton {
|
|
||||||
margin-top: 0.1875rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@ -43,17 +42,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
margin: 0 -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdownList {
|
.dropdownList {
|
||||||
padding-left: 3.1875rem;
|
padding: 0 1rem 0 4rem;
|
||||||
margin-block: -0.375rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialogButtons {
|
.dialogButtons {
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.proceedButtons {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.restrictionContainer,
|
.restrictionContainer,
|
||||||
.dropdownList {
|
.dropdownList {
|
||||||
|
margin: 0 -1rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
@ -62,6 +69,7 @@
|
|||||||
|
|
||||||
.restrictionContainerOpen,
|
.restrictionContainerOpen,
|
||||||
.dropdownListOpen {
|
.dropdownListOpen {
|
||||||
|
margin: 0 -1rem;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
transition: max-height 0.3s ease-in-out;
|
transition: max-height 0.3s ease-in-out;
|
||||||
|
|||||||
@ -1,37 +1,41 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useEffect,
|
memo,
|
||||||
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type { ApiChat, ApiChatMember } from '../../api/types';
|
||||||
ApiChat, ApiChatMember, ApiMessage, ApiPeer,
|
|
||||||
} from '../../api/types';
|
|
||||||
import type { IAlbum } from '../../types';
|
|
||||||
import type { IRadioOption } from '../ui/CheckboxGroup';
|
import type { IRadioOption } from '../ui/CheckboxGroup';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getHasAdminRight,
|
getHasAdminRight,
|
||||||
|
getPeerTitle,
|
||||||
getPrivateChatUserId,
|
getPrivateChatUserId,
|
||||||
getUserFirstOrLastName, getUserFullName,
|
getUserFirstOrLastName, isChatBasicGroup,
|
||||||
isChatBasicGroup,
|
isChatChannel,
|
||||||
isChatSuperGroup, isOwnMessage,
|
isChatSuperGroup,
|
||||||
isSystemBot,
|
isSystemBot,
|
||||||
isUserId,
|
isUserId,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectAllowedMessageActionsSlow,
|
getSendersFromSelectedMessages,
|
||||||
selectBot,
|
selectBot,
|
||||||
selectChat, selectChatFullInfo, selectCurrentMessageIds,
|
selectCanDeleteSelectedMessages,
|
||||||
selectCurrentMessageList, selectSender, selectSenderFromMessage, selectTabState,
|
selectChat,
|
||||||
|
selectChatFullInfo, selectIsChatWithBot,
|
||||||
|
selectSenderFromMessage,
|
||||||
|
selectTabState,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
import { buildCollectionByCallback, unique } from '../../util/iteratees';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||||
@ -39,10 +43,12 @@ import useManagePermissions from '../right/hooks/useManagePermissions';
|
|||||||
|
|
||||||
import PermissionCheckboxList from '../main/PermissionCheckboxList';
|
import PermissionCheckboxList from '../main/PermissionCheckboxList';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
|
import Checkbox from '../ui/Checkbox';
|
||||||
import CheckboxGroup from '../ui/CheckboxGroup';
|
import CheckboxGroup from '../ui/CheckboxGroup';
|
||||||
import ListItem from '../ui/ListItem';
|
import ListItem from '../ui/ListItem';
|
||||||
import Modal from '../ui/Modal';
|
import Modal from '../ui/Modal';
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
|
import AvatarList from './AvatarList';
|
||||||
import Icon from './icons/Icon';
|
import Icon from './icons/Icon';
|
||||||
|
|
||||||
import styles from './DeleteMessageModal.module.scss';
|
import styles from './DeleteMessageModal.module.scss';
|
||||||
@ -54,146 +60,199 @@ export type OwnProps = {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
isGroup?: boolean;
|
isGroup?: boolean;
|
||||||
|
isChannel?: boolean;
|
||||||
isSuperGroup?: boolean;
|
isSuperGroup?: boolean;
|
||||||
sender: ApiPeer | undefined;
|
messageIds?: number[];
|
||||||
currentUserId?: string;
|
|
||||||
canDeleteForAll?: boolean;
|
canDeleteForAll?: boolean;
|
||||||
contactName?: string;
|
contactName?: string;
|
||||||
|
currentUserId?: string;
|
||||||
willDeleteForCurrentUserOnly?: boolean;
|
willDeleteForCurrentUserOnly?: boolean;
|
||||||
willDeleteForAll?: boolean;
|
willDeleteForAll?: boolean;
|
||||||
messageIdList: number[] | undefined;
|
|
||||||
adminMembersById?: Record<string, ApiChatMember>;
|
adminMembersById?: Record<string, ApiChatMember>;
|
||||||
chatBot?: boolean;
|
chatBot?: boolean;
|
||||||
isSchedule?: boolean;
|
isSchedule?: boolean;
|
||||||
message?: ApiMessage;
|
|
||||||
album?: IAlbum;
|
|
||||||
onConfirm?: NoneToVoidFunction;
|
onConfirm?: NoneToVoidFunction;
|
||||||
isOwn?: boolean;
|
|
||||||
canBanUsers?: boolean;
|
canBanUsers?: boolean;
|
||||||
|
isCreator?: boolean;
|
||||||
|
linkedChatId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
chat,
|
chat,
|
||||||
isGroup,
|
isChannel,
|
||||||
isSuperGroup,
|
isSuperGroup,
|
||||||
sender,
|
|
||||||
currentUserId,
|
|
||||||
messageIdList,
|
|
||||||
isSchedule,
|
isSchedule,
|
||||||
message,
|
currentUserId,
|
||||||
album,
|
messageIds,
|
||||||
|
isCreator,
|
||||||
canDeleteForAll,
|
canDeleteForAll,
|
||||||
contactName,
|
contactName,
|
||||||
willDeleteForCurrentUserOnly,
|
willDeleteForCurrentUserOnly,
|
||||||
willDeleteForAll,
|
willDeleteForAll,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
adminMembersById,
|
|
||||||
chatBot,
|
chatBot,
|
||||||
isOwn,
|
adminMembersById,
|
||||||
canBanUsers,
|
canBanUsers,
|
||||||
|
linkedChatId,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
|
closeDeleteMessageModal,
|
||||||
deleteMessages,
|
deleteMessages,
|
||||||
deleteScheduledMessages,
|
|
||||||
reportChannelSpam,
|
reportChannelSpam,
|
||||||
deleteChatMember,
|
deleteChatMember,
|
||||||
|
deleteScheduledMessages,
|
||||||
|
exitMessageSelectMode,
|
||||||
updateChatMemberBannedRights,
|
updateChatMemberBannedRights,
|
||||||
closeDeleteMessageModal,
|
deleteParticipantHistory,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const prevIsOpen = usePreviousDeprecated(isOpen);
|
const prevIsOpen = usePreviousDeprecated(isOpen);
|
||||||
|
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
permissions, havePermissionChanged, handlePermissionChange, resetPermissions,
|
permissions, havePermissionChanged, handlePermissionChange, resetPermissions,
|
||||||
} = useManagePermissions(chat?.defaultBannedRights);
|
} = useManagePermissions(chat?.defaultBannedRights);
|
||||||
|
|
||||||
const [chosenDeleteOption, setChosenDeleteOption] = useState<string[] | undefined>(undefined);
|
const [peerIdsToDeleteAll, setPeerIdsToDeleteAll] = useState<string[] | undefined>(undefined);
|
||||||
const [chosenBanOption, setChosenBanOptions] = useState<string[] | undefined>(undefined);
|
const [peerIdsToBan, setPeerIdsToBan] = useState<string[] | undefined>(undefined);
|
||||||
const [chosenSpanOption, setChosenSpanOptions] = useState<string[] | undefined>(undefined);
|
const [peerIdsToReportSpam, setPeerIdsToReportSpam] = useState<string[] | undefined>(undefined);
|
||||||
const [isMediaDropdownOpen, setIsMediaDropdownOpen] = useState(false);
|
const [isMediaDropdownOpen, setIsMediaDropdownOpen] = useState(false);
|
||||||
const [isAdditionalOptionsVisible, setIsAdditionalOptionsVisible] = useState(false);
|
const [isAdditionalOptionsVisible, setIsAdditionalOptionsVisible] = useState(false);
|
||||||
const isSenderOwner = useMemo(() => {
|
const [shouldDeleteForAll, setShouldDeleteForAll] = useState(true);
|
||||||
return sender && adminMembersById && adminMembersById[sender.id] && adminMembersById[sender.id].isOwner;
|
|
||||||
}, [sender, adminMembersById]);
|
|
||||||
|
|
||||||
const user = useMemo(() => {
|
const peerList = useMemo(() => {
|
||||||
const usersById = getGlobal().users.byId;
|
if (isChannel || !messageIds || !chat) {
|
||||||
if (!sender || isSchedule) return undefined;
|
return MEMO_EMPTY_ARRAY;
|
||||||
|
|
||||||
return usersById[sender.id];
|
|
||||||
}, [isSchedule, sender]);
|
|
||||||
|
|
||||||
const shouldShowAdditionalOptions = useMemo(() => {
|
|
||||||
return user && user.id !== currentUserId;
|
|
||||||
}, [user, currentUserId]);
|
|
||||||
|
|
||||||
const shouldShowOptions = shouldShowAdditionalOptions && !canDeleteForAll && !isSchedule && (isGroup || isSuperGroup);
|
|
||||||
|
|
||||||
const userName = useMemo(() => {
|
|
||||||
const usersById = getGlobal().users.byId;
|
|
||||||
if (!sender || isSchedule) return '';
|
|
||||||
|
|
||||||
return getUserFullName(usersById[sender.id]);
|
|
||||||
}, [isSchedule, sender]);
|
|
||||||
|
|
||||||
const ACTION_SPAM_OPTION: IRadioOption[] = useMemo(() => {
|
|
||||||
if (!user) {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
const global = getGlobal();
|
||||||
|
const senderArray = getSendersFromSelectedMessages(global, chat.id, messageIds);
|
||||||
|
return senderArray ? unique(senderArray).filter((peer) => peer?.id !== chat?.id && peer?.id !== linkedChatId) : MEMO_EMPTY_ARRAY;
|
||||||
|
}, [chat, isChannel, messageIds]);
|
||||||
|
|
||||||
return [
|
const buildNestedOptionListWithAvatars = useLastCallback(() => {
|
||||||
{
|
return peerList.map((member) => {
|
||||||
value: user.id,
|
return {
|
||||||
label: lang('ReportSpamTitle'),
|
value: `${member.id}`,
|
||||||
},
|
label: getPeerTitle(lang, member) || '',
|
||||||
];
|
leftElement: <Avatar size="small" peer={member} />,
|
||||||
}, [lang, user]);
|
};
|
||||||
|
});
|
||||||
const ACTION_DELETE_OPTION: IRadioOption[] = useMemo(() => {
|
|
||||||
if (!user) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: user.id,
|
|
||||||
label: lang('DeleteAllFrom', userName),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [lang, user, userName]);
|
|
||||||
|
|
||||||
const ACTION_BAN_OPTION: IRadioOption[] = useMemo(() => {
|
|
||||||
if (!user) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: user.id,
|
|
||||||
label: (message && isAdditionalOptionsVisible ? lang('KickFromSupergroup') : lang('DeleteBan', userName)),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [isAdditionalOptionsVisible, lang, message, user, userName]);
|
|
||||||
|
|
||||||
const toggleAdditionalOptions = useLastCallback(() => {
|
|
||||||
setIsAdditionalOptionsVisible(!isAdditionalOptionsVisible);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterMessageIdByUserId = useLastCallback((userIds: string[], selectedMessageIdList: number[]) => {
|
const peerListToDeleteAll = useMemo(() => {
|
||||||
|
return peerList.filter((peer) => peer.id !== linkedChatId && peer.id !== currentUserId);
|
||||||
|
}, [peerList, currentUserId, linkedChatId]);
|
||||||
|
|
||||||
|
const peerListToReportSpam = useMemo(() => {
|
||||||
|
return peerList.filter((peer) => peer.id !== currentUserId && peer.id !== linkedChatId);
|
||||||
|
}, [peerList, currentUserId, linkedChatId]);
|
||||||
|
|
||||||
|
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 !== currentUserId),
|
||||||
|
] : undefined,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [messageIds, peerList, oldLang, linkedChatId, 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 !== currentUserId),
|
||||||
|
] : undefined,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [messageIds, peerList, oldLang, peerNames, linkedChatId, 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;
|
if (!chat) return MEMO_EMPTY_ARRAY;
|
||||||
|
const global = getGlobal();
|
||||||
return selectedMessageIdList.filter((msgId) => {
|
return selectedMessageIdList.filter((msgId) => {
|
||||||
const senderPeer = selectSenderFromMessage(getGlobal(), chat.id, msgId);
|
const sender = selectSenderFromMessage(global, chat.id, msgId);
|
||||||
return senderPeer && userIds.includes(senderPeer.id);
|
return sender && peerIds.includes(sender.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleReportSpam = useLastCallback((userMessagesMap: Record<string, number[]>) => {
|
||||||
|
Object.entries(userMessagesMap).forEach(([userId, messageIdList]) => {
|
||||||
|
if (messageIdList.length) {
|
||||||
|
reportChannelSpam({
|
||||||
|
participantId: userId,
|
||||||
|
chatId: chat!.id,
|
||||||
|
messageIds: messageIdList,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDeleteMessages = useLastCallback((filteredMessageIdList: number[]) => {
|
const handleDeleteMessages = useLastCallback((filteredMessageIdList: number[]) => {
|
||||||
if (filteredMessageIdList?.length) {
|
deleteMessages({ messageIds: filteredMessageIdList, shouldDeleteForAll: true });
|
||||||
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[]) => {
|
const handleDeleteMember = useLastCallback((filteredUserIdList: string[]) => {
|
||||||
@ -212,86 +271,68 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDeleteMessageForAll = useLastCallback(() => {
|
const handleDeleteMessageList = useLastCallback(() => {
|
||||||
onConfirm?.();
|
if (!chat || !messageIds) return;
|
||||||
const messageIds = album?.messages
|
|
||||||
? album.messages.map(({ id }) => id)
|
|
||||||
: [message!.id];
|
|
||||||
deleteMessages({ messageIds, shouldDeleteForAll: true });
|
|
||||||
closeDeleteMessageModal();
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDeleteMessageForSelf = useLastCallback(() => {
|
|
||||||
if (!chat) return;
|
|
||||||
|
|
||||||
onConfirm?.();
|
onConfirm?.();
|
||||||
const messageIds = album?.messages
|
|
||||||
? album.messages.map(({ id }) => id)
|
|
||||||
: [message!.id];
|
|
||||||
if (isSchedule) {
|
if (isSchedule) {
|
||||||
deleteScheduledMessages({ messageIds });
|
deleteScheduledMessages({ messageIds });
|
||||||
} else if (!isOwn && (chosenSpanOption || chosenDeleteOption || chosenBanOption) && (isGroup || isSuperGroup)) {
|
} else if (shouldShowOption) {
|
||||||
if (chosenSpanOption && sender) {
|
if (peerIdsToReportSpam) {
|
||||||
const filteredMessageIdList = filterMessageIdByUserId(chosenSpanOption, messageIdList!);
|
const global = getGlobal();
|
||||||
if (filteredMessageIdList && filteredMessageIdList.length) {
|
const peerIdList = peerIdsToReportSpam.filter((option) => !Number.isNaN(Number(option)));
|
||||||
reportChannelSpam({ participantId: sender.id, chatId: chat.id, messageIds: filteredMessageIdList });
|
const messageList = messageIds!.reduce<Record<string, number[]>>((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 (chosenDeleteOption) {
|
if (peerIdsToDeleteAll) {
|
||||||
const filteredMessageIdList = filterMessageIdByUserId(chosenDeleteOption, messageIdList!);
|
const peerIdList = peerIdsToDeleteAll.filter((option) => !Number.isNaN(Number(option)));
|
||||||
|
handleDeleteAllPeerMessages(peerIdList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peerIdsToBan && !havePermissionChanged) {
|
||||||
|
const peerIdList = peerIdsToBan.filter((option) => !Number.isNaN(Number(option)));
|
||||||
|
handleDeleteMember(peerIdList);
|
||||||
|
const filteredMessageIdList = filterMessageIdByPeerId(peerIdList, messageIds!);
|
||||||
handleDeleteMessages(filteredMessageIdList);
|
handleDeleteMessages(filteredMessageIdList);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chosenBanOption && !havePermissionChanged && message) {
|
if (peerIdsToBan && havePermissionChanged) {
|
||||||
const filteredUserIdList = chosenBanOption.filter((userId) => messageIds?.some((msgId) => {
|
const peerIdList = peerIdsToBan.filter((option) => !Number.isNaN(Number(option)));
|
||||||
const senderPeer = selectSenderFromMessage(getGlobal(), chat.id, msgId);
|
handleUpdateChatMemberBannedRights(peerIdList);
|
||||||
return senderPeer && senderPeer.id === userId;
|
|
||||||
}));
|
|
||||||
handleDeleteMember(filteredUserIdList);
|
|
||||||
deleteMessages({
|
|
||||||
messageIds: [message.id],
|
|
||||||
shouldDeleteForAll: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chosenBanOption && havePermissionChanged) {
|
if (!peerIdsToReportSpam || !peerIdsToDeleteAll || !peerIdsToBan) {
|
||||||
const filteredUserIdList = chosenBanOption.filter((userId) => messageIds?.some((msgId) => {
|
deleteMessages({ messageIds, shouldDeleteForAll });
|
||||||
const senderPeer = selectSenderFromMessage(getGlobal(), chat.id, msgId);
|
|
||||||
return senderPeer && senderPeer.id === userId;
|
|
||||||
}));
|
|
||||||
handleUpdateChatMemberBannedRights(filteredUserIdList);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deleteMessages({
|
deleteMessages({ messageIds, shouldDeleteForAll });
|
||||||
messageIds,
|
|
||||||
shouldDeleteForAll: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDeleteMessageModal();
|
closeDeleteMessageModal();
|
||||||
|
exitMessageSelectMode();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDeleteOptionChange = useLastCallback((options: string[]) => {
|
const onCloseHandler = useLastCallback(() => {
|
||||||
setChosenDeleteOption(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleBanOptionChange = useLastCallback((options: string[]) => {
|
|
||||||
setChosenBanOptions(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSpanOptionChange = useLastCallback((options: string[]) => {
|
|
||||||
setChosenSpanOptions(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleClose = useLastCallback(() => {
|
|
||||||
closeDeleteMessageModal();
|
closeDeleteMessageModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen && prevIsOpen) {
|
if (!isOpen && prevIsOpen) {
|
||||||
setChosenSpanOptions(undefined);
|
setPeerIdsToReportSpam(undefined);
|
||||||
setChosenDeleteOption(undefined);
|
setPeerIdsToDeleteAll(undefined);
|
||||||
setChosenBanOptions(undefined);
|
setPeerIdsToBan(undefined);
|
||||||
|
setShouldDeleteForAll(true);
|
||||||
setIsMediaDropdownOpen(false);
|
setIsMediaDropdownOpen(false);
|
||||||
setIsAdditionalOptionsVisible(false);
|
setIsAdditionalOptionsVisible(false);
|
||||||
resetPermissions();
|
resetPermissions();
|
||||||
@ -300,14 +341,19 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
function renderHeader() {
|
function renderHeader() {
|
||||||
return (
|
return (
|
||||||
<div className={shouldShowOptions && styles.container} dir={lang.isRtl ? 'rtl' : undefined}>
|
<div
|
||||||
{shouldShowOptions && (
|
className={shouldShowOption && styles.container}
|
||||||
<Avatar
|
dir={oldLang.isRtl ? 'rtl' : undefined}
|
||||||
|
>
|
||||||
|
{shouldShowOption && (
|
||||||
|
<AvatarList
|
||||||
size="small"
|
size="small"
|
||||||
peer={user!}
|
peers={peerList}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<h3 className={shouldShowOptions ? styles.title : styles.singleTitle}>{lang('DeleteSingleMessagesTitle')}</h3>
|
<h3 className={buildClassName(shouldShowOption ? styles.title : styles.singleTitle)}>
|
||||||
|
{oldLang('Chat.DeleteMessagesConfirmation', messageIds?.length)}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -317,19 +363,24 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
<div className={styles.options}>
|
<div className={styles.options}>
|
||||||
<CheckboxGroup
|
<CheckboxGroup
|
||||||
options={ACTION_SPAM_OPTION}
|
options={ACTION_SPAM_OPTION}
|
||||||
onChange={handleSpanOptionChange}
|
onChange={setPeerIdsToReportSpam}
|
||||||
selected={chosenSpanOption}
|
selected={peerIdsToReportSpam}
|
||||||
|
nestedCheckbox={messageIds && peerList.length >= 2}
|
||||||
/>
|
/>
|
||||||
<CheckboxGroup
|
{peerListToDeleteAll?.length > 0 && (
|
||||||
options={ACTION_DELETE_OPTION}
|
<CheckboxGroup
|
||||||
onChange={handleDeleteOptionChange}
|
options={ACTION_DELETE_OPTION}
|
||||||
selected={chosenDeleteOption}
|
onChange={setPeerIdsToDeleteAll}
|
||||||
/>
|
selected={peerIdsToDeleteAll}
|
||||||
{!isSenderOwner && canBanUsers && (
|
nestedCheckbox={messageIds && peerList.length >= 2}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{peerListToBan?.length > 0 && (
|
||||||
<CheckboxGroup
|
<CheckboxGroup
|
||||||
options={ACTION_BAN_OPTION}
|
options={ACTION_BAN_OPTION}
|
||||||
onChange={handleBanOptionChange}
|
onChange={setPeerIdsToBan}
|
||||||
selected={chosenBanOption}
|
selected={peerIdsToBan}
|
||||||
|
nestedCheckbox={messageIds && peerList.length >= 2}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -342,11 +393,10 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
isAdditionalOptionsVisible && styles.restrictionContainerOpen)}
|
isAdditionalOptionsVisible && styles.restrictionContainerOpen)}
|
||||||
>
|
>
|
||||||
<h3 className={buildClassName(styles.actionTitle, styles.restrictionTitle)}>
|
<h3 className={buildClassName(styles.actionTitle, styles.restrictionTitle)}>
|
||||||
{lang('UserRestrictionsCanDoUsers', 1)}
|
{oldLang('UserRestrictionsCanDoUsers', peerList.length)}
|
||||||
</h3>
|
</h3>
|
||||||
<PermissionCheckboxList
|
<PermissionCheckboxList
|
||||||
withCheckbox
|
withCheckbox
|
||||||
permissionGroup
|
|
||||||
chatId={chat?.id}
|
chatId={chat?.id}
|
||||||
isMediaDropdownOpen={isMediaDropdownOpen}
|
isMediaDropdownOpen={isMediaDropdownOpen}
|
||||||
setIsMediaDropdownOpen={setIsMediaDropdownOpen}
|
setIsMediaDropdownOpen={setIsMediaDropdownOpen}
|
||||||
@ -361,29 +411,32 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!messageIds) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={handleClose}
|
onClose={onCloseHandler}
|
||||||
onEnter={canDeleteForAll ? undefined : handleDeleteMessageForSelf}
|
onEnter={canDeleteForAll ? undefined : handleDeleteMessageList}
|
||||||
className="delete"
|
className={styles.root}
|
||||||
>
|
>
|
||||||
<div className={buildClassName(styles.mainContainer, 'custom-scroll')}>
|
<div className={styles.main}>
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
{shouldShowOptions && (
|
{shouldShowOption && (
|
||||||
<>
|
<>
|
||||||
<p className={styles.actionTitle}>{lang('DeleteAdditionalActions')}</p>
|
<p className={styles.actionTitle}>{oldLang('DeleteAdditionalActions')}</p>
|
||||||
{renderAdditionalActionOptions()}
|
{renderAdditionalActionOptions()}
|
||||||
{renderPartiallyRestrictedUser()}
|
{renderPartiallyRestrictedUser()}
|
||||||
{
|
{
|
||||||
chosenBanOption && canBanUsers && chosenBanOption?.length ? (
|
peerIdsToBan && canBanUsers ? (
|
||||||
<ListItem
|
<ListItem
|
||||||
narrow
|
narrow
|
||||||
className={styles.listItemButton}
|
|
||||||
buttonClassName={styles.button}
|
buttonClassName={styles.button}
|
||||||
onClick={toggleAdditionalOptions}
|
onClick={toggleAdditionalOptions}
|
||||||
>
|
>
|
||||||
{lang(isAdditionalOptionsVisible ? 'DeleteToggleBanUsers' : 'DeleteToggleRestrictUsers')}
|
{oldLang(isAdditionalOptionsVisible ? 'DeleteToggleBanUsers' : 'DeleteToggleRestrictUsers')}
|
||||||
<Icon
|
<Icon
|
||||||
name={isAdditionalOptionsVisible ? 'up' : 'down'}
|
name={isAdditionalOptionsVisible ? 'up' : 'down'}
|
||||||
className={buildClassName(styles.button, 'ml-2')}
|
className={buildClassName(styles.button, 'ml-2')}
|
||||||
@ -393,35 +446,35 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(chatBot || !shouldShowAdditionalOptions) && (
|
{(canDeleteForAll || chatBot || !shouldShowOption) && (
|
||||||
<>
|
<>
|
||||||
<p>{lang('AreYouSureDeleteSingleMessage')}</p>
|
<p>{messageIds.length > 1
|
||||||
|
? lang('AreYouSureDeleteFewMessages') : lang('AreYouSureDeleteSingleMessage')}
|
||||||
|
</p>
|
||||||
{willDeleteForCurrentUserOnly && (
|
{willDeleteForCurrentUserOnly && (
|
||||||
<p>{lang('lng_delete_for_me_chat_hint', 1, 'i')}</p>
|
<p>{oldLang('lng_delete_for_me_chat_hint', 1, 'i')}</p>
|
||||||
)}
|
)}
|
||||||
{willDeleteForAll && (
|
{willDeleteForAll && (
|
||||||
<p>{lang('lng_delete_for_everyone_hint', 1, 'i')}</p>
|
<p>{oldLang('lng_delete_for_everyone_hint', 1, 'i')}</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className={canDeleteForAll ? 'dialog-buttons-column'
|
{canDeleteForAll && (
|
||||||
: buildClassName('dialog-buttons', isAdditionalOptionsVisible && styles.dialogButtons)}
|
<Checkbox
|
||||||
|
className="dialog-checkbox"
|
||||||
|
label={contactName ? renderText(oldLang('DeleteMessagesOptionAlso', contactName))
|
||||||
|
: oldLang('Conversation.DeleteMessagesForEveryone')}
|
||||||
|
checked={shouldDeleteForAll}
|
||||||
|
onCheck={setShouldDeleteForAll}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={buildClassName('dialog-buttons',
|
||||||
|
isMediaDropdownOpen ? styles.dialogButtons : styles.proceedButtons)}
|
||||||
>
|
>
|
||||||
{canDeleteForAll && (
|
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteMessageList}>
|
||||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteMessageForAll}>
|
{shouldShowOption ? oldLang('DeleteProceedBtn') : lang('Delete')}
|
||||||
{contactName && renderText(lang('Conversation.DeleteMessagesFor', contactName))}
|
|
||||||
{!contactName && lang('Conversation.DeleteMessagesForEveryone')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteMessageForSelf}>
|
|
||||||
{lang(canDeleteForAll ? 'ChatList.DeleteForCurrentUser' : 'Delete')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className="confirm-dialog-button"
|
|
||||||
isText
|
|
||||||
onClick={handleClose}
|
|
||||||
>{lang('Cancel')}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button className="confirm-dialog-button" isText onClick={onCloseHandler}>{oldLang('Cancel')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -433,48 +486,44 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const {
|
const {
|
||||||
deleteMessageModal,
|
deleteMessageModal,
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
const chatId = deleteMessageModal && deleteMessageModal.message?.chatId;
|
const messageIds = deleteMessageModal?.messageIds;
|
||||||
|
const chatId = deleteMessageModal?.chatId;
|
||||||
|
const { canDeleteForAll } = selectCanDeleteSelectedMessages(global, messageIds);
|
||||||
const chat = chatId ? selectChat(global, chatId) : undefined;
|
const chat = chatId ? selectChat(global, chatId) : undefined;
|
||||||
const chatFullInfo = chat && selectChatFullInfo(global, chat.id);
|
const chatFullInfo = chat && selectChatFullInfo(global, chat.id);
|
||||||
const { threadId, type } = selectCurrentMessageList(global) || {};
|
const linkedChatId = chatFullInfo?.linkedChatId;
|
||||||
const { canDeleteForAll } = (deleteMessageModal && deleteMessageModal.message && threadId
|
const isChannel = Boolean(chat) && isChatChannel(chat);
|
||||||
&& selectAllowedMessageActionsSlow(global, deleteMessageModal.message, threadId)) || {};
|
|
||||||
const adminMembersById = chatFullInfo && chatFullInfo?.adminMembersById;
|
|
||||||
const messageIdList = chat && selectCurrentMessageIds(global, chat.id, threadId!, type!);
|
|
||||||
const isGroup = Boolean(chat) && isChatBasicGroup(chat);
|
|
||||||
const isSuperGroup = Boolean(chat) && isChatSuperGroup(chat);
|
const isSuperGroup = Boolean(chat) && isChatSuperGroup(chat);
|
||||||
const sender = deleteMessageModal?.message && selectSender(global, deleteMessageModal.message);
|
const isSchedule = deleteMessageModal?.isSchedule;
|
||||||
|
const onConfirm = deleteMessageModal?.onConfirm;
|
||||||
const contactName = chat && isUserId(chat.id)
|
const contactName = chat && isUserId(chat.id)
|
||||||
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!))
|
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!))
|
||||||
: undefined;
|
: undefined;
|
||||||
const isChatWithBot = Boolean(deleteMessageModal && deleteMessageModal.message
|
|
||||||
&& selectBot(global, deleteMessageModal.message.chatId));
|
|
||||||
const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id));
|
const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id));
|
||||||
const canBanUsers = chat && (chat.isCreator || getHasAdminRight(chat, 'banUsers'));
|
const adminMembersById = chatFullInfo?.adminMembersById;
|
||||||
const isOwn = deleteMessageModal && deleteMessageModal.message && isOwnMessage(deleteMessageModal.message);
|
const canBanUsers = chat && getHasAdminRight(chat, 'banUsers');
|
||||||
|
const isCreator = chat?.isCreator;
|
||||||
|
const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined;
|
||||||
const willDeleteForCurrentUserOnly = (chat && isChatBasicGroup(chat) && !canDeleteForAll) || isChatWithBot;
|
const willDeleteForCurrentUserOnly = (chat && isChatBasicGroup(chat) && !canDeleteForAll) || isChatWithBot;
|
||||||
const willDeleteForAll = chat && isChatSuperGroup(chat);
|
const willDeleteForAll = chat && (isChatSuperGroup(chat) || isChannel);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
isGroup,
|
isChannel,
|
||||||
isSuperGroup,
|
isSuperGroup,
|
||||||
|
messageIds,
|
||||||
currentUserId: global.currentUserId,
|
currentUserId: global.currentUserId,
|
||||||
sender,
|
canDeleteForAll: !isSchedule && canDeleteForAll,
|
||||||
messageIdList,
|
|
||||||
canDeleteForAll: deleteMessageModal && !deleteMessageModal.isSchedule && canDeleteForAll,
|
|
||||||
contactName,
|
contactName,
|
||||||
willDeleteForCurrentUserOnly,
|
willDeleteForCurrentUserOnly,
|
||||||
willDeleteForAll,
|
willDeleteForAll,
|
||||||
adminMembersById,
|
adminMembersById,
|
||||||
chatBot,
|
chatBot,
|
||||||
isSchedule: deleteMessageModal && deleteMessageModal.isSchedule,
|
|
||||||
message: deleteMessageModal && deleteMessageModal.message,
|
|
||||||
album: deleteMessageModal && deleteMessageModal.album,
|
|
||||||
onConfirm: deleteMessageModal && deleteMessageModal.onConfirm,
|
|
||||||
isOwn,
|
|
||||||
canBanUsers,
|
canBanUsers,
|
||||||
|
linkedChatId,
|
||||||
|
isSchedule,
|
||||||
|
isCreator,
|
||||||
|
onConfirm,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(DeleteMessageModal));
|
)(DeleteMessageModal));
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { FC } from '../../lib/teact/teact';
|
|||||||
import React, { memo, useMemo } from '../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
|
import type { ApiChat } from '../../api/types';
|
||||||
import type { ActiveDownloads, MediaViewerOrigin, MessageListType } from '../../types';
|
import type { ActiveDownloads, MediaViewerOrigin, MessageListType } from '../../types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import type { MenuItemProps } from '../ui/MenuItem';
|
import type { MenuItemProps } from '../ui/MenuItem';
|
||||||
@ -16,7 +17,7 @@ import {
|
|||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectActiveDownloads,
|
selectActiveDownloads,
|
||||||
selectAllowedMessageActionsSlow,
|
selectAllowedMessageActionsSlow, selectCurrentChat,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectIsChatProtected,
|
selectIsChatProtected,
|
||||||
selectIsMessageProtected,
|
selectIsMessageProtected,
|
||||||
@ -45,6 +46,7 @@ type StateProps = {
|
|||||||
isProtected?: boolean;
|
isProtected?: boolean;
|
||||||
isChatProtected?: boolean;
|
isChatProtected?: boolean;
|
||||||
canDelete?: boolean;
|
canDelete?: boolean;
|
||||||
|
chat?: ApiChat;
|
||||||
canUpdate?: boolean;
|
canUpdate?: boolean;
|
||||||
messageListType?: MessageListType;
|
messageListType?: MessageListType;
|
||||||
origin?: MediaViewerOrigin;
|
origin?: MediaViewerOrigin;
|
||||||
@ -67,6 +69,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
|||||||
item,
|
item,
|
||||||
mediaData,
|
mediaData,
|
||||||
isVideo,
|
isVideo,
|
||||||
|
chat,
|
||||||
isChatProtected,
|
isChatProtected,
|
||||||
isProtected,
|
isProtected,
|
||||||
canReportAvatar,
|
canReportAvatar,
|
||||||
@ -211,10 +214,11 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openDeleteModalHandler = useLastCallback(() => {
|
const openDeleteModalHandler = useLastCallback(() => {
|
||||||
if (item?.type === 'message') {
|
if (item?.type === 'message' && chat) {
|
||||||
openDeleteMessageModal({
|
openDeleteMessageModal({
|
||||||
|
chatId: chat?.id,
|
||||||
|
messageIds: [item.message.id],
|
||||||
isSchedule: messageListType === 'scheduled',
|
isSchedule: messageListType === 'scheduled',
|
||||||
message: item.message,
|
|
||||||
onConfirm: onBeforeDelete,
|
onConfirm: onBeforeDelete,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -394,6 +398,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const avatarOwner = item?.type === 'avatar' ? item.avatarOwner : undefined;
|
const avatarOwner = item?.type === 'avatar' ? item.avatarOwner : undefined;
|
||||||
const avatarPhoto = item?.type === 'avatar' && item.profilePhotos.photos[item.mediaIndex];
|
const avatarPhoto = item?.type === 'avatar' && item.profilePhotos.photos[item.mediaIndex];
|
||||||
|
|
||||||
|
const chat = selectCurrentChat(global);
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
const { threadId } = selectCurrentMessageList(global) || {};
|
const { threadId } = selectCurrentMessageList(global) || {};
|
||||||
const isProtected = selectIsMessageProtected(global, message);
|
const isProtected = selectIsMessageProtected(global, message);
|
||||||
@ -410,6 +415,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
activeDownloads,
|
activeDownloads,
|
||||||
isProtected,
|
isProtected,
|
||||||
|
chat,
|
||||||
isChatProtected,
|
isChatProtected,
|
||||||
canDelete,
|
canDelete,
|
||||||
canUpdate,
|
canUpdate,
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
.main {
|
|
||||||
max-height: 90vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
margin: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.singleTitle {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actionTitle {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
color: var(--color-links);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.restrictionTitle {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listItemButton {
|
|
||||||
margin-top: 0.1875rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
color: var(--color-links) !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
&:hover {
|
|
||||||
background: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdownList {
|
|
||||||
padding-left: 2.6875rem;
|
|
||||||
margin-block: -0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialogButtons {
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.restrictionContainer,
|
|
||||||
.dropdownList {
|
|
||||||
overflow: hidden;
|
|
||||||
max-height: 0;
|
|
||||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
|
||||||
transition: max-height 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.restrictionContainerOpen,
|
|
||||||
.dropdownListOpen {
|
|
||||||
max-height: 100vh;
|
|
||||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
|
||||||
transition: max-height 0.6s ease-in-out;
|
|
||||||
}
|
|
||||||
@ -1,508 +0,0 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
|
||||||
import React, {
|
|
||||||
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,
|
|
||||||
getPrivateChatUserId,
|
|
||||||
getUserFirstOrLastName,
|
|
||||||
getUserFullName,
|
|
||||||
isChatBasicGroup, isChatChannel,
|
|
||||||
isChatSuperGroup,
|
|
||||||
isUserId,
|
|
||||||
} from '../../global/helpers';
|
|
||||||
import {
|
|
||||||
getSendersFromSelectedMessages,
|
|
||||||
selectCanDeleteSelectedMessages,
|
|
||||||
selectChatFullInfo,
|
|
||||||
selectCurrentChat,
|
|
||||||
selectCurrentMessageIds,
|
|
||||||
selectCurrentMessageList,
|
|
||||||
selectSenderFromMessage,
|
|
||||||
selectTabState,
|
|
||||||
selectUser,
|
|
||||||
} from '../../global/selectors';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
|
||||||
import { buildCollectionByCallback } from '../../util/iteratees';
|
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
|
||||||
import renderText from '../common/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 Avatar from '../common/Avatar';
|
|
||||||
import AvatarList from '../common/AvatarList';
|
|
||||||
import Icon from '../common/icons/Icon';
|
|
||||||
import PermissionCheckboxList from '../main/PermissionCheckboxList';
|
|
||||||
import Button from '../ui/Button';
|
|
||||||
import CheckboxGroup from '../ui/CheckboxGroup';
|
|
||||||
import ListItem from '../ui/ListItem';
|
|
||||||
import Modal from '../ui/Modal';
|
|
||||||
|
|
||||||
import styles from './DeleteSelectedMessageModal.module.scss';
|
|
||||||
|
|
||||||
export type OwnProps = {
|
|
||||||
isOpen: boolean;
|
|
||||||
isSchedule: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StateProps = {
|
|
||||||
chat?: ApiChat;
|
|
||||||
isGroup?: boolean;
|
|
||||||
isChannel?: boolean;
|
|
||||||
isSuperGroup?: boolean;
|
|
||||||
selectedMessageIds?: number[];
|
|
||||||
canDeleteForAll?: boolean;
|
|
||||||
contactName?: string;
|
|
||||||
currentUserId?: string;
|
|
||||||
willDeleteForCurrentUserOnly?: boolean;
|
|
||||||
willDeleteForAll?: boolean;
|
|
||||||
messageIds: number[] | undefined;
|
|
||||||
adminMembersById?: Record<string, ApiChatMember>;
|
|
||||||
canBanUsers?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DeleteSelectedMessageModal: FC<OwnProps & StateProps> = ({
|
|
||||||
chat,
|
|
||||||
isChannel,
|
|
||||||
isGroup,
|
|
||||||
isSuperGroup,
|
|
||||||
isOpen,
|
|
||||||
isSchedule,
|
|
||||||
currentUserId,
|
|
||||||
selectedMessageIds,
|
|
||||||
canDeleteForAll,
|
|
||||||
contactName,
|
|
||||||
willDeleteForCurrentUserOnly,
|
|
||||||
willDeleteForAll,
|
|
||||||
messageIds,
|
|
||||||
onClose,
|
|
||||||
adminMembersById,
|
|
||||||
canBanUsers,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
deleteMessages,
|
|
||||||
reportChannelSpam,
|
|
||||||
deleteChatMember,
|
|
||||||
deleteScheduledMessages,
|
|
||||||
exitMessageSelectMode,
|
|
||||||
updateChatMemberBannedRights,
|
|
||||||
} = getActions();
|
|
||||||
|
|
||||||
const prevIsOpen = usePreviousDeprecated(isOpen);
|
|
||||||
|
|
||||||
const oldLang = useOldLang();
|
|
||||||
const lang = useLang();
|
|
||||||
|
|
||||||
const {
|
|
||||||
permissions, havePermissionChanged, handlePermissionChange, resetPermissions,
|
|
||||||
} = useManagePermissions(chat?.defaultBannedRights);
|
|
||||||
|
|
||||||
const [chosenDeleteOption, setChosenDeleteOption] = useState<string[] | undefined>(undefined);
|
|
||||||
const [chosenBanOption, setChosenBanOptions] = useState<string[] | undefined>(undefined);
|
|
||||||
const [chosenSpanOption, setChosenSpanOptions] = useState<string[] | undefined>(undefined);
|
|
||||||
const [isMediaDropdownOpen, setIsMediaDropdownOpen] = useState(false);
|
|
||||||
const [isAdditionalOptionsVisible, setIsAdditionalOptionsVisible] = useState(false);
|
|
||||||
|
|
||||||
const senderList = useMemo(() => {
|
|
||||||
if (isChannel) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const senderArray = getSendersFromSelectedMessages(getGlobal(), chat);
|
|
||||||
return senderArray?.filter(Boolean);
|
|
||||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
|
||||||
}, [chat, isChannel, isOpen]);
|
|
||||||
|
|
||||||
const isSenderOwner = useMemo(() => {
|
|
||||||
if (!senderList) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return senderList.some((sender) => sender
|
|
||||||
&& adminMembersById
|
|
||||||
&& adminMembersById[sender.id] && adminMembersById[sender.id].isOwner);
|
|
||||||
}, [senderList, adminMembersById]);
|
|
||||||
|
|
||||||
const userList = useMemo(() => {
|
|
||||||
const usersById = getGlobal().users.byId;
|
|
||||||
if (!senderList || isSchedule) return [];
|
|
||||||
const uniqueUserIds = new Set(senderList.map((user) => user!.id));
|
|
||||||
|
|
||||||
return Array.from(uniqueUserIds)
|
|
||||||
.map((id) => usersById[id])
|
|
||||||
.filter(Boolean);
|
|
||||||
}, [isSchedule, senderList]);
|
|
||||||
|
|
||||||
const nestedOptionsWithAvatarList = useLastCallback(() => {
|
|
||||||
return userList.map((user) => ({
|
|
||||||
value: `${user.id}`,
|
|
||||||
label: getUserFullName(user) || '',
|
|
||||||
leftElement: <Avatar size="small" peer={user} />,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const showAdditionalOptions = useMemo(() => {
|
|
||||||
return !userList.some((user) => user.id === currentUserId);
|
|
||||||
}, [userList, currentUserId]);
|
|
||||||
|
|
||||||
const shouldShowOptions = showAdditionalOptions && !canDeleteForAll && !isSchedule && (isGroup || isSuperGroup);
|
|
||||||
|
|
||||||
const userNames = useMemo(() => {
|
|
||||||
const usersById = getGlobal().users.byId;
|
|
||||||
if (!senderList || isSchedule) return {};
|
|
||||||
|
|
||||||
const uniqueUserIds = new Set(senderList.map((user) => user!.id));
|
|
||||||
const userIds = Array.from(uniqueUserIds).map((userId) => usersById[userId]);
|
|
||||||
|
|
||||||
return buildCollectionByCallback(userIds, (user) => [user?.id, getUserFullName(user)]);
|
|
||||||
}, [isSchedule, senderList]);
|
|
||||||
|
|
||||||
const ACTION_SPAM_OPTION: IRadioOption[] = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: selectedMessageIds && userList.length >= 2 ? 'spam' : userList?.[0]?.id,
|
|
||||||
label: oldLang('ReportSpamTitle'),
|
|
||||||
nestedOptions: selectedMessageIds && userList.length >= 2 ? [
|
|
||||||
...nestedOptionsWithAvatarList(),
|
|
||||||
] : undefined,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [oldLang, selectedMessageIds, userList]);
|
|
||||||
|
|
||||||
const ACTION_DELETE_OPTION: IRadioOption[] = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: selectedMessageIds && userList.length >= 2 ? 'delete_all' : userList?.[0]?.id,
|
|
||||||
label: selectedMessageIds && userList.length >= 2
|
|
||||||
? oldLang('DeleteAllFromUsers')
|
|
||||||
: oldLang('DeleteAllFrom', Object.values(userNames)[0]),
|
|
||||||
nestedOptions: selectedMessageIds && userList.length >= 2 ? [
|
|
||||||
...nestedOptionsWithAvatarList(),
|
|
||||||
] : undefined,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [oldLang, selectedMessageIds, userList, userNames]);
|
|
||||||
|
|
||||||
const ACTION_BAN_OPTION: IRadioOption[] = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: selectedMessageIds && userList.length >= 2 ? 'ban' : userList?.[0]?.id,
|
|
||||||
label: selectedMessageIds && userList.length >= 2
|
|
||||||
? (isAdditionalOptionsVisible ? oldLang('DeleteRestrictUsers') : oldLang('DeleteBanUsers'))
|
|
||||||
: (isAdditionalOptionsVisible ? oldLang('KickFromSupergroup')
|
|
||||||
: oldLang('DeleteBan', Object.values(userNames)[0])),
|
|
||||||
nestedOptions: selectedMessageIds && userList.length >= 2 ? [
|
|
||||||
...nestedOptionsWithAvatarList(),
|
|
||||||
] : undefined,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [isAdditionalOptionsVisible, oldLang, selectedMessageIds, userList, userNames]);
|
|
||||||
|
|
||||||
const toggleAdditionalOptions = useLastCallback(() => {
|
|
||||||
setIsAdditionalOptionsVisible((prev) => !prev);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDeleteMessageForAll = useLastCallback(() => {
|
|
||||||
onClose();
|
|
||||||
deleteMessages({ messageIds: selectedMessageIds!, shouldDeleteForAll: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
const filterMessageIdByUserId = useLastCallback((userIds: string[], selectedMessageIdList: number[]) => {
|
|
||||||
if (!chat) return MEMO_EMPTY_ARRAY;
|
|
||||||
return selectedMessageIdList.filter((msgId) => {
|
|
||||||
const sender = selectSenderFromMessage(getGlobal(), chat.id, msgId);
|
|
||||||
return sender && userIds.includes(sender.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleReportSpam = useLastCallback((userMessagesMap: Record<string, number[]>) => {
|
|
||||||
Object.entries(userMessagesMap).forEach(([userId, messageIdList]) => {
|
|
||||||
if (messageIdList.length) {
|
|
||||||
reportChannelSpam({
|
|
||||||
participantId: userId,
|
|
||||||
chatId: chat!.id,
|
|
||||||
messageIds: messageIdList,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDeleteMessages = useLastCallback((filteredMessageIdList: number[]) => {
|
|
||||||
if (filteredMessageIdList && filteredMessageIdList.length) {
|
|
||||||
deleteMessages({ messageIds: filteredMessageIdList, shouldDeleteForAll: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 handleDeleteMessageForSelf = useLastCallback(() => {
|
|
||||||
if (!chat) return;
|
|
||||||
|
|
||||||
if (isSchedule) {
|
|
||||||
deleteScheduledMessages({ messageIds: selectedMessageIds! });
|
|
||||||
} else if (!isSenderOwner && shouldShowOptions) {
|
|
||||||
if (chosenSpanOption) {
|
|
||||||
const userIdList = chosenSpanOption.filter((option) => !Number.isNaN(Number(option)));
|
|
||||||
const userMessagesMap = selectedMessageIds!.reduce<Record<string, number[]>>((acc, msgId) => {
|
|
||||||
const sender = selectSenderFromMessage(getGlobal(), chat.id, msgId);
|
|
||||||
if (sender && userIdList.includes(sender.id)) {
|
|
||||||
if (!acc[sender.id]) {
|
|
||||||
acc[sender.id] = [];
|
|
||||||
}
|
|
||||||
acc[sender.id].push(Number(msgId));
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
handleReportSpam(userMessagesMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chosenDeleteOption) {
|
|
||||||
const userIdList = chosenDeleteOption.filter((option) => !Number.isNaN(Number(option)));
|
|
||||||
const filteredMessageIdList = filterMessageIdByUserId(userIdList, messageIds!);
|
|
||||||
handleDeleteMessages(filteredMessageIdList);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chosenBanOption && !havePermissionChanged) {
|
|
||||||
const userIdList = chosenBanOption.filter((option) => !Number.isNaN(Number(option)));
|
|
||||||
const filteredUserIdList = userIdList.filter((userId) => selectedMessageIds?.some((msgId) => {
|
|
||||||
const sender = selectSenderFromMessage(getGlobal(), chat.id, msgId);
|
|
||||||
return sender && sender.id === userId;
|
|
||||||
}));
|
|
||||||
handleDeleteMember(filteredUserIdList);
|
|
||||||
const filteredMessageIdList = filterMessageIdByUserId(userIdList, selectedMessageIds!);
|
|
||||||
handleDeleteMessages(filteredMessageIdList);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chosenBanOption && havePermissionChanged) {
|
|
||||||
const userIdList = chosenBanOption.filter((option) => !Number.isNaN(Number(option)));
|
|
||||||
const filteredUserIdList = userIdList.filter((userId) => selectedMessageIds?.some((msgId) => {
|
|
||||||
const sender = selectSenderFromMessage(getGlobal(), chat.id, msgId);
|
|
||||||
return sender && sender.id === userId;
|
|
||||||
}));
|
|
||||||
handleUpdateChatMemberBannedRights(filteredUserIdList);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deleteMessages({ messageIds: selectedMessageIds!, shouldDeleteForAll: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
const onCloseHandler = useLastCallback(() => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDeleteOptionChange = useLastCallback((options: string[]) => {
|
|
||||||
setChosenDeleteOption(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleBanOptionChange = useLastCallback((options: string[]) => {
|
|
||||||
setChosenBanOptions(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSpanOptionChange = useLastCallback((options: string[]) => {
|
|
||||||
setChosenSpanOptions(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen && prevIsOpen) {
|
|
||||||
exitMessageSelectMode();
|
|
||||||
setChosenSpanOptions(undefined);
|
|
||||||
setChosenDeleteOption(undefined);
|
|
||||||
setChosenBanOptions(undefined);
|
|
||||||
setIsMediaDropdownOpen(false);
|
|
||||||
setIsAdditionalOptionsVisible(false);
|
|
||||||
resetPermissions();
|
|
||||||
}
|
|
||||||
}, [exitMessageSelectMode, isOpen, prevIsOpen, resetPermissions]);
|
|
||||||
|
|
||||||
function renderHeader() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={shouldShowOptions && styles.container}
|
|
||||||
dir={oldLang.isRtl ? 'rtl' : undefined}
|
|
||||||
>
|
|
||||||
{shouldShowOptions && (
|
|
||||||
<AvatarList
|
|
||||||
size="small"
|
|
||||||
peers={userList}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<h3 className={buildClassName(shouldShowOptions ? styles.title : styles.singleTitle)}>
|
|
||||||
{oldLang('Chat.DeleteMessagesConfirmation', selectedMessageIds?.length)}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAdditionalActionOptions() {
|
|
||||||
return (
|
|
||||||
<div className={styles.options}>
|
|
||||||
<CheckboxGroup
|
|
||||||
options={ACTION_SPAM_OPTION}
|
|
||||||
onChange={handleSpanOptionChange}
|
|
||||||
selected={chosenSpanOption}
|
|
||||||
nestedCheckbox={selectedMessageIds && userList.length >= 2}
|
|
||||||
/>
|
|
||||||
<CheckboxGroup
|
|
||||||
options={ACTION_DELETE_OPTION}
|
|
||||||
onChange={handleDeleteOptionChange}
|
|
||||||
selected={chosenDeleteOption}
|
|
||||||
nestedCheckbox={selectedMessageIds && userList.length >= 2}
|
|
||||||
/>
|
|
||||||
{!isSenderOwner && canBanUsers && (
|
|
||||||
<CheckboxGroup
|
|
||||||
options={ACTION_BAN_OPTION}
|
|
||||||
onChange={handleBanOptionChange}
|
|
||||||
selected={chosenBanOption}
|
|
||||||
nestedCheckbox={selectedMessageIds && userList.length >= 2}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPartiallyRestrictedUser() {
|
|
||||||
return (
|
|
||||||
<div className={buildClassName(styles.restrictionContainer,
|
|
||||||
isAdditionalOptionsVisible && styles.restrictionContainerOpen)}
|
|
||||||
>
|
|
||||||
<h3 className={buildClassName(styles.actionTitle, styles.restrictionTitle)}>
|
|
||||||
{oldLang('UserRestrictionsCanDoUsers', userList.length)}
|
|
||||||
</h3>
|
|
||||||
<PermissionCheckboxList
|
|
||||||
withCheckbox
|
|
||||||
permissionGroup
|
|
||||||
chatId={chat?.id}
|
|
||||||
isMediaDropdownOpen={isMediaDropdownOpen}
|
|
||||||
setIsMediaDropdownOpen={setIsMediaDropdownOpen}
|
|
||||||
handlePermissionChange={handlePermissionChange}
|
|
||||||
permissions={permissions}
|
|
||||||
className={buildClassName(styles.dropdownList, isMediaDropdownOpen && styles.dropdownListOpen)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedMessageIds) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
onEnter={canDeleteForAll ? undefined : handleDeleteMessageForSelf}
|
|
||||||
className="delete"
|
|
||||||
>
|
|
||||||
<div className={styles.main}>
|
|
||||||
{renderHeader()}
|
|
||||||
{!showAdditionalOptions && <p>{lang('AreYouSureDeleteFewMessages')}</p>}
|
|
||||||
{shouldShowOptions && (
|
|
||||||
<>
|
|
||||||
<p className={styles.actionTitle}>{oldLang('DeleteAdditionalActions')}</p>
|
|
||||||
{renderAdditionalActionOptions()}
|
|
||||||
{renderPartiallyRestrictedUser()}
|
|
||||||
{
|
|
||||||
chosenBanOption && canBanUsers && chosenBanOption?.length ? (
|
|
||||||
<ListItem
|
|
||||||
narrow
|
|
||||||
className={styles.listItemButton}
|
|
||||||
buttonClassName={styles.button}
|
|
||||||
onClick={toggleAdditionalOptions}
|
|
||||||
>
|
|
||||||
{oldLang(isAdditionalOptionsVisible ? 'DeleteToggleBanUsers' : 'DeleteToggleRestrictUsers')}
|
|
||||||
<Icon
|
|
||||||
name={isAdditionalOptionsVisible ? 'up' : 'down'}
|
|
||||||
className={buildClassName(styles.button, 'ml-2')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
) : setIsAdditionalOptionsVisible(false)
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{willDeleteForCurrentUserOnly && lang('DeleteForMeDescription')}
|
|
||||||
{(willDeleteForAll && !showAdditionalOptions) && lang('DeleteForEveryoneDescription')}
|
|
||||||
<div className={canDeleteForAll ? 'dialog-buttons-column'
|
|
||||||
: buildClassName('dialog-buttons', isAdditionalOptionsVisible && styles.dialogButtons)}
|
|
||||||
>
|
|
||||||
{canDeleteForAll && (
|
|
||||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteMessageForAll}>
|
|
||||||
{contactName
|
|
||||||
? renderText(oldLang('ChatList.DeleteForEveryone', contactName))
|
|
||||||
: oldLang('Conversation.DeleteMessagesForEveryone')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteMessageForSelf}>
|
|
||||||
{oldLang(canDeleteForAll ? 'ChatList.DeleteForCurrentUser' : 'Delete')}
|
|
||||||
</Button>
|
|
||||||
<Button className="confirm-dialog-button" isText onClick={onCloseHandler}>{oldLang('Cancel')}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
|
||||||
(global, { isSchedule }): StateProps => {
|
|
||||||
const { messageIds: selectedMessageIds } = selectTabState(global).selectedMessages || {};
|
|
||||||
const { canDeleteForAll } = selectCanDeleteSelectedMessages(global);
|
|
||||||
const chat = selectCurrentChat(global);
|
|
||||||
const chatFullInfo = chat && selectChatFullInfo(global, chat.id);
|
|
||||||
const { threadId, type } = selectCurrentMessageList(global) || {};
|
|
||||||
const messageIds = chat && selectCurrentMessageIds(global, chat.id, threadId!, type!);
|
|
||||||
const isChannel = Boolean(chat) && isChatChannel(chat);
|
|
||||||
const isGroup = Boolean(chat) && isChatBasicGroup(chat);
|
|
||||||
const isSuperGroup = Boolean(chat) && isChatSuperGroup(chat);
|
|
||||||
const contactName = chat && isUserId(chat.id)
|
|
||||||
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!))
|
|
||||||
: undefined;
|
|
||||||
const adminMembersById = chatFullInfo?.adminMembersById;
|
|
||||||
const canBanUsers = chat && (chat.isCreator || getHasAdminRight(chat, 'banUsers'));
|
|
||||||
const willDeleteForCurrentUserOnly = chat && isChatBasicGroup(chat) && !canDeleteForAll;
|
|
||||||
const willDeleteForAll = chat && isChatSuperGroup(chat);
|
|
||||||
|
|
||||||
return {
|
|
||||||
chat,
|
|
||||||
isGroup,
|
|
||||||
isChannel,
|
|
||||||
isSuperGroup,
|
|
||||||
selectedMessageIds,
|
|
||||||
currentUserId: global.currentUserId,
|
|
||||||
canDeleteForAll: !isSchedule && canDeleteForAll,
|
|
||||||
contactName,
|
|
||||||
willDeleteForCurrentUserOnly,
|
|
||||||
willDeleteForAll,
|
|
||||||
messageIds,
|
|
||||||
adminMembersById,
|
|
||||||
canBanUsers,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
)(DeleteSelectedMessageModal));
|
|
||||||
@ -18,7 +18,6 @@ import {
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||||
@ -26,7 +25,6 @@ import useCopySelectedMessages from './hooks/useCopySelectedMessages';
|
|||||||
|
|
||||||
import Icon from '../common/icons/Icon';
|
import Icon from '../common/icons/Icon';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import DeleteSelectedMessageModal from './DeleteSelectedMessageModal';
|
|
||||||
|
|
||||||
import './MessageSelectToolbar.scss';
|
import './MessageSelectToolbar.scss';
|
||||||
|
|
||||||
@ -71,27 +69,35 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
copySelectedMessages,
|
copySelectedMessages,
|
||||||
showNotification,
|
showNotification,
|
||||||
reportMessages,
|
reportMessages,
|
||||||
|
openDeleteMessageModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
|
||||||
|
|
||||||
useCopySelectedMessages(isActive);
|
useCopySelectedMessages(isActive);
|
||||||
|
|
||||||
const handleExitMessageSelectMode = useLastCallback(() => {
|
const handleExitMessageSelectMode = useLastCallback(() => {
|
||||||
exitMessageSelectMode();
|
exitMessageSelectMode();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleDelete = useLastCallback(() => {
|
||||||
|
if (!selectedMessageIds || !chat) return;
|
||||||
|
openDeleteMessageModal({
|
||||||
|
chatId: chat.id,
|
||||||
|
messageIds: selectedMessageIds,
|
||||||
|
isSchedule,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return isActive && !isDeleteModalOpen && !isAnyModalOpen
|
return isActive && !isAnyModalOpen
|
||||||
? captureKeyboardListeners({
|
? captureKeyboardListeners({
|
||||||
onBackspace: canDeleteMessages ? openDeleteModal : undefined,
|
onBackspace: canDeleteMessages ? handleDelete : undefined,
|
||||||
onDelete: canDeleteMessages ? openDeleteModal : undefined,
|
onDelete: canDeleteMessages ? handleDelete : undefined,
|
||||||
onEsc: handleExitMessageSelectMode,
|
onEsc: handleExitMessageSelectMode,
|
||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
}, [
|
}, [
|
||||||
isActive, isDeleteModalOpen, openDeleteModal, handleExitMessageSelectMode, isAnyModalOpen,
|
isActive, handleDelete, handleExitMessageSelectMode, isAnyModalOpen,
|
||||||
canDeleteMessages,
|
canDeleteMessages,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -181,18 +187,11 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
renderButton('copy', lang('lng_context_copy_selected_items'), handleCopy)
|
renderButton('copy', lang('lng_context_copy_selected_items'), handleCopy)
|
||||||
)}
|
)}
|
||||||
{canDeleteMessages && (
|
{canDeleteMessages && (
|
||||||
renderButton('delete', lang('EditAdminGroupDeleteMessages'), openDeleteModal, true)
|
renderButton('delete', lang('EditAdminGroupDeleteMessages'), handleDelete, true)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{canDeleteMessages && (
|
|
||||||
<DeleteSelectedMessageModal
|
|
||||||
isOpen={isDeleteModalOpen}
|
|
||||||
isSchedule={isSchedule}
|
|
||||||
onClose={closeDeleteModal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -211,7 +210,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canForward = !isSchedule && chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false;
|
const canForward = !isSchedule && chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false;
|
||||||
const isShareMessageModalOpen = tabState.isShareMessageModalShown;
|
const isShareMessageModalOpen = tabState.isShareMessageModalShown;
|
||||||
const isAnyModalOpen = Boolean(isShareMessageModalOpen || tabState.requestedDraft
|
const isAnyModalOpen = Boolean(isShareMessageModalOpen || tabState.requestedDraft
|
||||||
|| tabState.requestedAttachBotInChat || tabState.requestedAttachBotInstall || tabState.reportModal);
|
|| tabState.requestedAttachBotInChat || tabState.requestedAttachBotInstall || tabState.reportModal
|
||||||
|
|| tabState.deleteMessageModal);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
|
|||||||
@ -152,7 +152,11 @@ const useEditing = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!text && !hasMessageMedia(editedMessage)) {
|
if (!text && !hasMessageMedia(editedMessage)) {
|
||||||
openDeleteMessageModal({ isSchedule: type === 'scheduled', message: editedMessage });
|
openDeleteMessageModal({
|
||||||
|
chatId,
|
||||||
|
messageIds: [editedMessage.id],
|
||||||
|
isSchedule: type === 'scheduled',
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -379,7 +379,14 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
const handleDelete = useLastCallback(() => {
|
const handleDelete = useLastCallback(() => {
|
||||||
setIsMenuOpen(false);
|
setIsMenuOpen(false);
|
||||||
closeMenu();
|
closeMenu();
|
||||||
openDeleteMessageModal({ isSchedule: messageListType === 'scheduled', album, message });
|
const messageIds = album?.messages
|
||||||
|
? album.messages.map(({ id }) => id)
|
||||||
|
: [message.id];
|
||||||
|
openDeleteMessageModal({
|
||||||
|
chatId: message.chatId,
|
||||||
|
messageIds,
|
||||||
|
isSchedule: messageListType === 'scheduled',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const closePinModal = useLastCallback(() => {
|
const closePinModal = useLastCallback(() => {
|
||||||
|
|||||||
@ -173,7 +173,7 @@ const ChatReportPane: FC<OwnProps & StateProps> = ({
|
|||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</Button>
|
</Button>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isBlockUserModalOpen}
|
isOpen={true}
|
||||||
onClose={closeBlockUserModal}
|
onClose={closeBlockUserModal}
|
||||||
title={lang('BlockUserTitle', user ? getUserFirstOrLastName(user) : getChatTitle(lang, chat!))}
|
title={lang('BlockUserTitle', user ? getUserFirstOrLastName(user) : getChatTitle(lang, chat!))}
|
||||||
text={user
|
text={user
|
||||||
|
|||||||
@ -36,7 +36,7 @@ type StateProps = {
|
|||||||
members?: ApiChatMember[];
|
members?: ApiChatMember[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const ITEM_HEIGHT = 24 + 20;
|
const ITEM_HEIGHT = 48;
|
||||||
const BEFORE_ITEMS_COUNT = 2;
|
const BEFORE_ITEMS_COUNT = 2;
|
||||||
const ITEMS_COUNT = 9;
|
const ITEMS_COUNT = 9;
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ type StateProps = {
|
|||||||
isFormFullyDisabled?: boolean;
|
isFormFullyDisabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ITEM_HEIGHT = 24 + 32;
|
const ITEM_HEIGHT = 48;
|
||||||
const SHIFT_HEIGHT_MINUS = 1;
|
const SHIFT_HEIGHT_MINUS = 1;
|
||||||
const BEFORE_ITEMS_COUNT = 2;
|
const BEFORE_ITEMS_COUNT = 2;
|
||||||
const BEFORE_USER_INFO_HEIGHT = 96;
|
const BEFORE_USER_INFO_HEIGHT = 96;
|
||||||
|
|||||||
@ -269,11 +269,11 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0.125rem;
|
left: 0.125rem;
|
||||||
padding: 0 0.5rem 0 3.375rem;
|
padding: 0 0.5rem 0 3.5rem;
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
|
|
||||||
&--open {
|
&--open {
|
||||||
transform: translateY(0.25rem);
|
transform: translateY(0.025rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -166,7 +166,7 @@
|
|||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 3.5rem;
|
left: 1.125rem;
|
||||||
width: 1.125rem;
|
width: 1.125rem;
|
||||||
height: 1.125rem;
|
height: 1.125rem;
|
||||||
}
|
}
|
||||||
@ -217,7 +217,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.3125rem;
|
gap: 0.3125rem;
|
||||||
margin-block: 0;
|
margin-block: 0;
|
||||||
padding: 0 0 0 3.5rem;
|
padding: 0 0 0 4.1875rem;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
@ -253,7 +253,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.avatar {
|
&.avatar {
|
||||||
padding-inline-start: 5.5rem;
|
padding-inline-start: 3.5rem;
|
||||||
|
margin-inline-start: 3.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|||||||
@ -76,7 +76,7 @@ const CheckboxGroup: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
const getCheckedNestedCount = useLastCallback((nestedOptions: IRadioOption[]) => {
|
const getCheckedNestedCount = useLastCallback((nestedOptions: IRadioOption[]) => {
|
||||||
const checkedCount = nestedOptions?.filter((nestedOption) => values.includes(nestedOption.value)).length;
|
const checkedCount = nestedOptions?.filter((nestedOption) => values.includes(nestedOption.value)).length;
|
||||||
return checkedCount > 0 ? checkedCount : undefined;
|
return checkedCount > 0 ? checkedCount : nestedOptions.length;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -200,7 +200,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dialog-checkbox {
|
.dialog-checkbox {
|
||||||
margin: 1rem -1rem;
|
margin: 1rem -1.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-dialog-button {
|
.confirm-dialog-button {
|
||||||
|
|||||||
@ -182,7 +182,12 @@ export const SCROLL_SHORT_TRANSITION_MAX_DISTANCE = 300; // px
|
|||||||
// Average duration of message sending animation
|
// Average duration of message sending animation
|
||||||
export const API_UPDATE_THROTTLE = Math.round((SCROLL_MIN_DURATION + SCROLL_MAX_DURATION) / 2);
|
export const API_UPDATE_THROTTLE = Math.round((SCROLL_MIN_DURATION + SCROLL_MAX_DURATION) / 2);
|
||||||
export const API_THROTTLE_RESET_UPDATES = new Set([
|
export const API_THROTTLE_RESET_UPDATES = new Set([
|
||||||
'newMessage', 'newScheduledMessage', 'deleteMessages', 'deleteScheduledMessages', 'deleteHistory',
|
'newMessage',
|
||||||
|
'newScheduledMessage',
|
||||||
|
'deleteMessages',
|
||||||
|
'deleteScheduledMessages',
|
||||||
|
'deleteHistory',
|
||||||
|
'deleteParticipantHistory',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const LOCK_SCREEN_ANIMATION_DURATION_MS = 200;
|
export const LOCK_SCREEN_ANIMATION_DURATION_MS = 200;
|
||||||
|
|||||||
@ -745,7 +745,9 @@ addActionHandler('joinChannel', async (global, actions, payload): Promise<void>
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('deleteChatUser', (global, actions, payload): ActionReturnType => {
|
addActionHandler('deleteChatUser', (global, actions, payload): ActionReturnType => {
|
||||||
const { chatId, userId, tabId = getCurrentTabId() } = payload;
|
const {
|
||||||
|
chatId, userId, shouldRevokeHistory, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
if (!chat || !user) {
|
if (!chat || !user) {
|
||||||
@ -759,7 +761,7 @@ addActionHandler('deleteChatUser', (global, actions, payload): ActionReturnType
|
|||||||
actions.openChat({ id: undefined, tabId });
|
actions.openChat({ id: undefined, tabId });
|
||||||
}
|
}
|
||||||
|
|
||||||
void callApi('deleteChatUser', { chat, user });
|
void callApi('deleteChatUser', { chat, user, shouldRevokeHistory });
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('deleteChat', (global, actions, payload): ActionReturnType => {
|
addActionHandler('deleteChat', (global, actions, payload): ActionReturnType => {
|
||||||
|
|||||||
@ -760,6 +760,16 @@ addActionHandler('deleteMessages', (global, actions, payload): ActionReturnType
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('deleteParticipantHistory', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
chatId, peerId,
|
||||||
|
} = payload;
|
||||||
|
const chat = selectChat(global, chatId)!;
|
||||||
|
const peer = selectPeer(global, peerId)!;
|
||||||
|
|
||||||
|
void callApi('deleteParticipantHistory', { chat, peer });
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('deleteScheduledMessages', (global, actions, payload): ActionReturnType => {
|
addActionHandler('deleteScheduledMessages', (global, actions, payload): ActionReturnType => {
|
||||||
const { messageIds, tabId = getCurrentTabId() } = payload;
|
const { messageIds, tabId = getCurrentTabId() } = payload;
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
|
|||||||
@ -25,7 +25,11 @@ import {
|
|||||||
isMessageLocal, isUserId,
|
isMessageLocal, isUserId,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import { getMessageReplyInfo, getStoryReplyInfo } from '../../helpers/replies';
|
import { getMessageReplyInfo, getStoryReplyInfo } from '../../helpers/replies';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import {
|
||||||
|
addActionHandler,
|
||||||
|
getGlobal,
|
||||||
|
setGlobal,
|
||||||
|
} from '../../index';
|
||||||
import {
|
import {
|
||||||
addMessages,
|
addMessages,
|
||||||
addViewportId,
|
addViewportId,
|
||||||
@ -659,6 +663,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'deleteParticipantHistory': {
|
||||||
|
const { chatId, peerId } = update;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
deleteParticipantHistory(global, chatId, peerId, actions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'updateCommonBoxMessages': {
|
case 'updateCommonBoxMessages': {
|
||||||
const { ids, messageUpdate } = update;
|
const { ids, messageUpdate } = update;
|
||||||
|
|
||||||
@ -1081,6 +1094,25 @@ function findLastMessage<T extends GlobalState>(global: T, chatId: string, threa
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteParticipantHistory<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
chatId: string,
|
||||||
|
peerId: string,
|
||||||
|
actions: RequiredGlobalActions,
|
||||||
|
) {
|
||||||
|
const byId = selectChatMessages(global, chatId);
|
||||||
|
|
||||||
|
const messageIds = Object.values(byId).filter((message) => {
|
||||||
|
return message.senderId === peerId;
|
||||||
|
}).map((message) => message.id);
|
||||||
|
|
||||||
|
if (!messageIds.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMessages(global, chatId, messageIds, actions);
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteThread<T extends GlobalState>(
|
export function deleteThread<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
|
|||||||
@ -1037,17 +1037,16 @@ function copyTextForMessages(global: GlobalState, chatId: string, messageIds: nu
|
|||||||
|
|
||||||
addActionHandler('openDeleteMessageModal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openDeleteMessageModal', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
message, isSchedule, album,
|
chatId, messageIds, isSchedule,
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
deleteMessageModal: {
|
deleteMessageModal: {
|
||||||
|
chatId,
|
||||||
|
messageIds,
|
||||||
isSchedule,
|
isSchedule,
|
||||||
album,
|
|
||||||
message,
|
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -410,18 +410,13 @@ export function selectSender<T extends GlobalState>(global: T, message: ApiMessa
|
|||||||
|
|
||||||
export function getSendersFromSelectedMessages<T extends GlobalState>(
|
export function getSendersFromSelectedMessages<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chat: ApiChat | undefined,
|
chatId: string,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
messageIds: number[],
|
||||||
) {
|
) {
|
||||||
const { messageIds: selectedMessageIds } = selectTabState(global, tabId).selectedMessages || {};
|
return messageIds.map((id) => {
|
||||||
if (!chat?.id || !selectedMessageIds) {
|
const message = selectChatMessage(global, chatId, id);
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedMessageIds.map((id) => {
|
|
||||||
const message = selectChatMessage(global, chat.id, id);
|
|
||||||
return message && selectSender(global, message);
|
return message && selectSender(global, message);
|
||||||
});
|
}).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectSenderFromMessage<T extends GlobalState>(
|
export function selectSenderFromMessage<T extends GlobalState>(
|
||||||
@ -782,21 +777,19 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// This selector always returns a new object which can not be safely used in shallow-equal checks
|
export function selectCanDeleteMessages<T extends GlobalState>(
|
||||||
export function selectCanDeleteSelectedMessages<T extends GlobalState>(
|
|
||||||
global: T,
|
global: T,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
chatId: string,
|
||||||
|
threadId: ThreadId,
|
||||||
|
messageIds: number[],
|
||||||
) {
|
) {
|
||||||
const { messageIds: selectedMessageIds } = selectTabState(global, tabId).selectedMessages || {};
|
const chatMessages = selectChatMessages(global, chatId);
|
||||||
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
|
||||||
const chatMessages = chatId && selectChatMessages(global, chatId);
|
if (messageIds.length > API_GENERAL_ID_LIMIT) {
|
||||||
if (!chatMessages || !selectedMessageIds || !threadId) {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedMessageIds.length > API_GENERAL_ID_LIMIT) return {};
|
const messageActions = messageIds
|
||||||
|
|
||||||
const messageActions = selectedMessageIds
|
|
||||||
.map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId))
|
.map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
@ -806,6 +799,21 @@ export function selectCanDeleteSelectedMessages<T extends GlobalState>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectCanDeleteSelectedMessages<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
messageIds?: number[],
|
||||||
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
) {
|
||||||
|
const { messageIds: selectedMessageIds } = selectTabState(global, tabId).selectedMessages || {};
|
||||||
|
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
|
const messageIdList = messageIds?.length ? messageIds : selectedMessageIds;
|
||||||
|
if (!chatId || !threadId || !messageIdList) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectCanDeleteMessages(global, chatId, threadId, messageIdList);
|
||||||
|
}
|
||||||
|
|
||||||
export function selectCanReportSelectedMessages<T extends GlobalState>(
|
export function selectCanReportSelectedMessages<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
|||||||
@ -61,7 +61,6 @@ import type {
|
|||||||
ConfettiParams,
|
ConfettiParams,
|
||||||
GiftProfileFilterOptions,
|
GiftProfileFilterOptions,
|
||||||
GlobalSearchContent,
|
GlobalSearchContent,
|
||||||
IAlbum,
|
|
||||||
IAnchorPosition,
|
IAnchorPosition,
|
||||||
ISettings,
|
ISettings,
|
||||||
IThemeSettings,
|
IThemeSettings,
|
||||||
@ -475,6 +474,10 @@ export interface ActionPayloads {
|
|||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
shouldDeleteForAll?: boolean;
|
shouldDeleteForAll?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
deleteParticipantHistory: {
|
||||||
|
peerId: string;
|
||||||
|
chatId: string;
|
||||||
|
} & WithTabId;
|
||||||
markMessageListRead: {
|
markMessageListRead: {
|
||||||
maxId: number;
|
maxId: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
@ -649,7 +652,11 @@ export interface ActionPayloads {
|
|||||||
replyToNextMessage: {
|
replyToNextMessage: {
|
||||||
targetIndexDelta: number;
|
targetIndexDelta: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
deleteChatUser: { chatId: string; userId: string } & WithTabId;
|
deleteChatUser: {
|
||||||
|
chatId: string;
|
||||||
|
userId: string;
|
||||||
|
shouldRevokeHistory?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
deleteChat: { chatId: string } & WithTabId;
|
deleteChat: { chatId: string } & WithTabId;
|
||||||
|
|
||||||
// chat creation
|
// chat creation
|
||||||
@ -2286,9 +2293,9 @@ export interface ActionPayloads {
|
|||||||
closePaidReactionModal: WithTabId | undefined;
|
closePaidReactionModal: WithTabId | undefined;
|
||||||
|
|
||||||
openDeleteMessageModal: ({
|
openDeleteMessageModal: ({
|
||||||
message?: ApiMessage;
|
chatId: string;
|
||||||
|
messageIds: number[];
|
||||||
isSchedule?: boolean;
|
isSchedule?: boolean;
|
||||||
album?: IAlbum;
|
|
||||||
onConfirm?: NoneToVoidFunction;
|
onConfirm?: NoneToVoidFunction;
|
||||||
} & WithTabId);
|
} & WithTabId);
|
||||||
closeDeleteMessageModal: WithTabId | undefined;
|
closeDeleteMessageModal: WithTabId | undefined;
|
||||||
|
|||||||
@ -60,7 +60,6 @@ import type {
|
|||||||
FocusDirection,
|
FocusDirection,
|
||||||
GiftProfileFilterOptions,
|
GiftProfileFilterOptions,
|
||||||
GlobalSearchContent,
|
GlobalSearchContent,
|
||||||
IAlbum,
|
|
||||||
IAnchorPosition,
|
IAnchorPosition,
|
||||||
InlineBotSettings,
|
InlineBotSettings,
|
||||||
ManagementProgress,
|
ManagementProgress,
|
||||||
@ -581,9 +580,9 @@ export type TabState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
deleteMessageModal?: {
|
deleteMessageModal?: {
|
||||||
message?: ApiMessage;
|
chatId: string;
|
||||||
|
messageIds: number[];
|
||||||
isSchedule?: boolean;
|
isSchedule?: boolean;
|
||||||
album?: IAlbum;
|
|
||||||
onConfirm?: NoneToVoidFunction;
|
onConfirm?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1668,6 +1668,7 @@ channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Upd
|
|||||||
channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
|
channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
|
||||||
channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool;
|
channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool;
|
||||||
channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers;
|
channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers;
|
||||||
|
channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory;
|
||||||
channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
|
channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
|
||||||
channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
|
channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
|
||||||
channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector<string> = Bool;
|
channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector<string> = Bool;
|
||||||
|
|||||||
@ -240,6 +240,7 @@
|
|||||||
"help.getPremiumPromo",
|
"help.getPremiumPromo",
|
||||||
"channels.readHistory",
|
"channels.readHistory",
|
||||||
"channels.deleteMessages",
|
"channels.deleteMessages",
|
||||||
|
"channels.deleteParticipantHistory",
|
||||||
"channels.getMessages",
|
"channels.getMessages",
|
||||||
"channels.getParticipants",
|
"channels.getParticipants",
|
||||||
"channels.getParticipant",
|
"channels.getParticipant",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user