diff --git a/src/components/common/DeleteMessageModal.tsx b/src/components/common/DeleteMessageModal.tsx index c4a46c26a..8095db1b8 100644 --- a/src/components/common/DeleteMessageModal.tsx +++ b/src/components/common/DeleteMessageModal.tsx @@ -22,7 +22,7 @@ import { isUserId, } from '../../global/helpers'; import { - selectAllowedMessageActions, + selectAllowedMessageActionsSlow, selectBot, selectChat, selectChatFullInfo, selectCurrentMessageIds, selectCurrentMessageList, selectSenderFromMessage, selectTabState, @@ -434,7 +434,7 @@ export default memo(withGlobal( const chatFullInfo = chat && selectChatFullInfo(global, chat.id); const { threadId, type } = selectCurrentMessageList(global) || {}; const { canDeleteForAll } = (deleteMessageModal && deleteMessageModal.message && threadId - && selectAllowedMessageActions(global, deleteMessageModal.message, threadId)) || {}; + && 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); diff --git a/src/components/mediaViewer/MediaViewerActions.tsx b/src/components/mediaViewer/MediaViewerActions.tsx index 83d37457e..1043f0a57 100644 --- a/src/components/mediaViewer/MediaViewerActions.tsx +++ b/src/components/mediaViewer/MediaViewerActions.tsx @@ -16,7 +16,7 @@ import { } from '../../global/helpers'; import { selectActiveDownloads, - selectAllowedMessageActions, + selectAllowedMessageActionsSlow, selectCurrentMessageList, selectIsChatProtected, selectIsMessageProtected, @@ -398,7 +398,7 @@ export default memo(withGlobal( const activeDownloads = selectActiveDownloads(global); const isChatProtected = message && selectIsChatProtected(global, message?.chatId); const { canDelete: canDeleteMessage } = (threadId - && message && selectAllowedMessageActions(global, message, threadId)) || {}; + && message && selectAllowedMessageActionsSlow(global, message, threadId)) || {}; const isCurrentAvatar = avatarPhoto && (avatarPhoto.id === avatarOwner?.avatarPhotoId); const canDeleteAvatar = canUpdateMedia && Boolean(avatarPhoto); const canDelete = canDeleteMessage || canDeleteAvatar; diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index b27cc6a0f..413f54d52 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -31,7 +31,7 @@ import { isUserId, } from '../../global/helpers'; import { - selectAllowedMessageActions, + selectAllowedMessageActionsSlow, selectChat, selectChatMessage, selectChatMessages, @@ -633,7 +633,7 @@ export default memo(withGlobal( } = ( firstPinnedMessage && pinnedMessageIds.length === 1 - && selectAllowedMessageActions(global, firstPinnedMessage, threadId) + && selectAllowedMessageActionsSlow(global, firstPinnedMessage, threadId) ) || {}; return { diff --git a/src/components/middle/message/ContextMenuContainer.tsx b/src/components/middle/message/ContextMenuContainer.tsx index e36c7c265..e6804699b 100644 --- a/src/components/middle/message/ContextMenuContainer.tsx +++ b/src/components/middle/message/ContextMenuContainer.tsx @@ -34,7 +34,7 @@ import { } from '../../../global/helpers'; import { selectActiveDownloads, - selectAllowedMessageActions, + selectAllowedMessageActionsSlow, selectCanPlayAnimatedEmojis, selectCanScheduleUntilOnline, selectCanTranslateMessage, @@ -702,7 +702,7 @@ export default memo(withGlobal( canSaveGif, canRevote, canClosePoll, - } = (threadId && selectAllowedMessageActions(global, message, threadId)) || {}; + } = (threadId && selectAllowedMessageActionsSlow(global, message, threadId)) || {}; const userStatus = isPrivate ? selectUserStatus(global, chat.id) : undefined; const isOwn = isOwnMessage(message); diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index d2cf631b3..72cf4be9f 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -64,10 +64,9 @@ import { import { getMessageReplyInfo, getStoryReplyInfo } from '../../../global/helpers/replies'; import { selectActiveDownloads, - selectAllowedMessageActions, selectAnimatedEmoji, selectCanAutoLoadMedia, - selectCanAutoPlayMedia, + selectCanAutoPlayMedia, selectCanReplyToMessage, selectChat, selectChatFullInfo, selectChatMessage, @@ -1754,7 +1753,7 @@ export default memo(withGlobal( isSelected = selectIsMessageSelected(global, id); } - const { canReply } = (messageListType === 'thread' && selectAllowedMessageActions(global, message, threadId)) || {}; + const canReply = messageListType === 'thread' && selectCanReplyToMessage(global, message, threadId); const activeDownloads = selectActiveDownloads(global); const downloadableMedia = getMessageDownloadableMedia(message); const isDownloading = downloadableMedia && getIsDownloading(activeDownloads, downloadableMedia); diff --git a/src/global/actions/ui/messages.ts b/src/global/actions/ui/messages.ts index 566cea8fc..b2d644320 100644 --- a/src/global/actions/ui/messages.ts +++ b/src/global/actions/ui/messages.ts @@ -48,7 +48,7 @@ import { } from '../../reducers'; import { updateTabState } from '../../reducers/tabs'; import { - selectAllowedMessageActions, + selectAllowedMessageActionsSlow, selectChat, selectChatLastMessageId, selectChatMessages, @@ -127,7 +127,7 @@ addActionHandler('editLastMessage', (global, actions, payload): ActionReturnType } const lastOwnEditableMessageId = findLast(viewportIds, (id) => { - return Boolean(chatMessages[id] && selectAllowedMessageActions(global, chatMessages[id], threadId).canEdit); + return Boolean(chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId).canEdit); }); if (!lastOwnEditableMessageId) { @@ -655,7 +655,7 @@ addActionHandler('downloadSelectedMessages', (global, actions, payload): ActionR const chatMessages = selectChatMessages(global, chatId); if (!chatMessages || !threadId) return; const messages = messageIds.map((id) => chatMessages[id]) - .filter((message) => selectAllowedMessageActions(global, message, threadId).canDownload); + .filter((message) => selectAllowedMessageActionsSlow(global, message, threadId).canDownload); messages.forEach((message) => { const media = getMessageDownloadableMedia(message); if (!media) return; @@ -955,7 +955,7 @@ function copyTextForMessages(global: GlobalState, chatId: string, messageIds: nu const messages = messageIds .map((id) => chatMessages[id]) - .filter((message) => selectAllowedMessageActions(global, message, threadId).canCopy) + .filter((message) => selectAllowedMessageActionsSlow(global, message, threadId).canCopy) .sort((message1, message2) => message1.id - message2.id); const resultHtml: string[] = []; diff --git a/src/global/helpers/chats.ts b/src/global/helpers/chats.ts index 11080a3c9..7805a710c 100644 --- a/src/global/helpers/chats.ts +++ b/src/global/helpers/chats.ts @@ -127,7 +127,7 @@ export function isChatAdmin(chat: ApiChat) { } export function getHasAdminRight(chat: ApiChat, key: keyof ApiChatAdminRights) { - return chat.adminRights ? chat.adminRights[key] : false; + return chat.adminRights?.[key] || false; } export function getCanManageTopic(chat: ApiChat, topic: ApiTopic) { diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index 6f206aac2..43a1619eb 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -11,14 +11,12 @@ import type { import type { ThreadId } from '../../types'; import type { IAllowedAttachmentOptions } from '../helpers'; import type { - ChatTranslatedMessages, - GlobalState, MessageListType, TabArgs, TabThread, Thread, + ChatTranslatedMessages, GlobalState, MessageListType, TabArgs, TabThread, Thread, } from '../types'; import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types'; import { - ANONYMOUS_USER_ID, - GENERAL_TOPIC_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID, + ANONYMOUS_USER_ID, GENERAL_TOPIC_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID, } from '../../config'; import { getCurrentTabId } from '../../util/establishMultitabRole'; import { findLast } from '../../util/iteratees'; @@ -33,7 +31,10 @@ import { getHasAdminRight, getIsSavedDialog, getMessageAudio, - getMessageDocument, getMessageLink, getMessagePaidMedia, getMessagePhoto, + getMessageDocument, + getMessageLink, + getMessagePaidMedia, + getMessagePhoto, getMessageVideo, getMessageVoice, getMessageWebPagePhoto, @@ -70,10 +71,7 @@ import { selectPeerStory } from './stories'; import { selectIsStickerFavorite } from './symbols'; import { selectTabState } from './tabs'; import { - selectBot, - selectIsCurrentUserPremium, - selectUser, - selectUserStatus, + selectBot, selectIsCurrentUserPremium, selectUser, selectUserStatus, } from './users'; const MESSAGE_EDIT_ALLOWED_TIME = 172800; // 48 hours @@ -608,7 +606,29 @@ export function selectTopicFromMessage(global: T, message return chat.topics?.[threadId]; } -export function selectAllowedMessageActions(global: T, message: ApiMessage, threadId: ThreadId) { +export function selectCanReplyToMessage(global: T, message: ApiMessage, threadId: ThreadId) { + const chat = selectChat(global, message.chatId); + if (!chat || chat.isRestricted || chat.isForbidden) return false; + + const isLocal = isMessageLocal(message); + const isServiceNotification = isServiceNotificationMessage(message); + + if (isLocal || isServiceNotification) return false; + + const threadInfo = selectThreadInfo(global, message.chatId, threadId); + const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId); + const chatFullInfo = selectChatFullInfo(global, chat.id); + const canPostInChat = getCanPostInChat(chat, threadId, isMessageThread, chatFullInfo); + if (!canPostInChat) return false; + + const messageTopic = selectTopicFromMessage(global, message); + return !messageTopic || !messageTopic.isClosed || messageTopic.isOwner || getHasAdminRight(chat, 'manageTopics'); +} + +// This selector is slow and not to be used within lists (e.g. Message component) +export function selectAllowedMessageActionsSlow( + global: T, message: ApiMessage, threadId: ThreadId, +) { const chat = selectChat(global, message.chatId); if (!chat || chat.isRestricted) { return {}; @@ -628,9 +648,7 @@ export function selectAllowedMessageActions(global: T, me const isAction = isActionMessage(message); const hasTtl = hasMessageTtl(message); const { content } = message; - const messageTopic = selectTopicFromMessage(global, message); const isDocumentSticker = isMessageDocumentSticker(message); - const chatFullInfo = selectChatFullInfo(global, chat.id); const isBoostMessage = message.content.action?.type === 'chatBoost'; const canEditMessagesIndefinitely = isChatWithSelf @@ -652,15 +670,7 @@ export function selectAllowedMessageActions(global: T, me const isSavedDialog = getIsSavedDialog(chat.id, threadId, global.currentUserId); - const threadInfo = selectThreadInfo(global, message.chatId, threadId); - const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId); - const canPostInChat = getCanPostInChat(chat, threadId, isMessageThread, chatFullInfo); - const canReply = (() => { - if (isLocal || isServiceNotification) return false; - if (!canPostInChat || chat.isForbidden) return false; - return !messageTopic || !messageTopic.isClosed || messageTopic.isOwner || getHasAdminRight(chat, 'manageTopics'); - })(); - + const canReply = selectCanReplyToMessage(global, message, threadId); const canReplyGlobally = canReply || (!isSavedDialog && !isLocal && !isServiceNotification && (isSuperGroup || isBasicGroup || isChatChannel(chat))); @@ -793,7 +803,7 @@ export function selectCanDeleteSelectedMessages( } const messageActions = selectedMessageIds - .map((id) => chatMessages[id] && selectAllowedMessageActions(global, chatMessages[id], threadId)) + .map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId)) .filter(Boolean); return { @@ -814,7 +824,7 @@ export function selectCanReportSelectedMessages( } const messageActions = selectedMessageIds - .map((id) => chatMessages[id] && selectAllowedMessageActions(global, chatMessages[id], threadId)) + .map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId)) .filter(Boolean); return messageActions.every((actions) => actions.canReport); @@ -832,7 +842,7 @@ export function selectCanDownloadSelectedMessages( } const messageActions = selectedMessageIds - .map((id) => chatMessages[id] && selectAllowedMessageActions(global, chatMessages[id], threadId)) + .map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId)) .filter(Boolean); return messageActions.some((actions) => actions.canDownload);