diff --git a/src/components/left/main/Chat.tsx b/src/components/left/main/Chat.tsx index 5586f615c..ecea176ad 100644 --- a/src/components/left/main/Chat.tsx +++ b/src/components/left/main/Chat.tsx @@ -125,7 +125,6 @@ const Chat: FC = ({ openChat, focusLastMessage, loadTopics, - openForumPanel, } = getActions(); const { isMobile } = useAppLayout(); @@ -156,17 +155,12 @@ const Chat: FC = ({ }); const handleClick = useCallback(() => { - if (isForum) { - openForumPanel({ chatId }); - return; - } - openChat({ id: chatId, shouldReplaceHistory: true }, { forceOnHeavyAnimation: true }); if (isSelected && canScrollDown) { focusLastMessage(); } - }, [isForum, openChat, chatId, isSelected, canScrollDown, openForumPanel, focusLastMessage]); + }, [openChat, chatId, isSelected, canScrollDown, focusLastMessage]); const handleDragEnter = useCallback((e) => { e.preventDefault(); diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index b6e6494c6..c91ba1072 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -24,6 +24,7 @@ import { LIGHT_THEME_BG_COLOR, ANIMATION_LEVEL_MIN, SUPPORTED_IMAGE_CONTENT_TYPES, + GENERAL_TOPIC_ID, } from '../../config'; import { MASK_IMAGE_DISABLED } from '../../util/environment'; import { DropAreaState } from './composer/DropArea'; @@ -644,7 +645,9 @@ export default memo(withGlobal( const canStartBot = !canRestartBot && isBotNotStarted; const shouldLoadFullChat = Boolean(chat && isChatGroup(chat) && !chat.fullInfo && lastSyncTime); const replyingToId = selectReplyingToId(global, chatId, threadId); - const shouldBlockBeforeReply = chat?.isForum ? (threadId === MAIN_THREAD_ID && !replyingToId) : false; + const shouldBlockSendInForum = chat?.isForum + ? threadId === MAIN_THREAD_ID && !replyingToId && (chat.topics?.[GENERAL_TOPIC_ID]?.isClosed) + : false; return { ...state, @@ -657,10 +660,9 @@ export default memo(withGlobal( areChatSettingsLoaded: Boolean(chat?.settings), canPost: !isPinnedMessageList && (!chat || canPost) - && !(isScheduledMessageList && chat?.isForum && threadId === MAIN_THREAD_ID) && !isBotNotStarted && !(shouldJoinToSend && chat?.isNotJoined) - && !shouldBlockBeforeReply, + && !shouldBlockSendInForum, isPinnedMessageList, isScheduledMessageList, currentUserBannedRights: chat?.currentUserBannedRights, diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 5b992976d..3a97c2def 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -180,7 +180,9 @@ const MiddleHeader: FC = ({ const handlePinnedMessageClick = useCallback((): void => { if (pinnedMessage) { - focusMessage({ chatId: pinnedMessage.chatId, threadId, messageId: pinnedMessage.id }); + focusMessage({ + chatId: pinnedMessage.chatId, threadId, messageId: pinnedMessage.id, noForumTopicPanel: true, + }); const newIndex = cycleRestrict(pinnedMessagesCount || 1, pinnedMessageIndex + 1); setPinnedMessageIndex(newIndex); diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index 5051136f9..b940f97bb 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -1081,7 +1081,9 @@ const Composer: FC = ({ }, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu, isMobile]); const handleAllScheduledClick = useCallback(() => { - openChat({ id: chatId, threadId, type: 'scheduled' }); + openChat({ + id: chatId, threadId, type: 'scheduled', noForumTopicPanel: true, + }); }, [openChat, chatId, threadId]); useEffect(() => { diff --git a/src/components/middle/composer/ComposerEmbeddedMessage.tsx b/src/components/middle/composer/ComposerEmbeddedMessage.tsx index 654dc4fcc..982404b73 100644 --- a/src/components/middle/composer/ComposerEmbeddedMessage.tsx +++ b/src/components/middle/composer/ComposerEmbeddedMessage.tsx @@ -118,7 +118,7 @@ const ComposerEmbeddedMessage: FC = ({ const handleMessageClick = useCallback((): void => { if (isForwarding) return; - focusMessage({ chatId: message!.chatId, messageId: message!.id }); + focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true }); }, [focusMessage, isForwarding, message]); const handleClearClick = useCallback((e: React.MouseEvent): void => { diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 501e5aabf..639d0b1d3 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -31,7 +31,7 @@ import { AudioOrigin } from '../../../types'; import { MAIN_THREAD_ID } from '../../../api/types'; import { IS_ANDROID, IS_TOUCH_ENV } from '../../../util/environment'; -import { EMOJI_STATUS_LOOP_LIMIT } from '../../../config'; +import { EMOJI_STATUS_LOOP_LIMIT, GENERAL_TOPIC_ID } from '../../../config'; import { selectChat, selectChatMessage, @@ -1285,8 +1285,9 @@ export default memo(withGlobal( const hasUnreadReaction = chat?.unreadReactions?.includes(message.id); - const messageTopic = threadId === MAIN_THREAD_ID ? selectTopicFromMessage(global, message) : undefined; const hasTopicChip = threadId === MAIN_THREAD_ID && chat?.isForum && isFirstInGroup; + const messageTopic = hasTopicChip ? (selectTopicFromMessage(global, message) || chat?.topics?.[GENERAL_TOPIC_ID]) + : undefined; return { theme: selectTheme(global), diff --git a/src/components/middle/message/hooks/useInnerHandlers.ts b/src/components/middle/message/hooks/useInnerHandlers.ts index b4853ba52..79c8185dd 100644 --- a/src/components/middle/message/hooks/useInnerHandlers.ts +++ b/src/components/middle/message/hooks/useInnerHandlers.ts @@ -73,6 +73,7 @@ export default function useInnerHandlers( threadId, messageId: replyToMessageId!, replyMessageId: isChatWithRepliesBot && replyToChatId ? undefined : messageId, + noForumTopicPanel: true, }); }, [focusMessage, isChatWithRepliesBot, replyToChatId, chatId, threadId, replyToMessageId, messageId]); diff --git a/src/global/actions/api/chats.ts b/src/global/actions/api/chats.ts index 6903d2b1a..7ed4b2818 100644 --- a/src/global/actions/api/chats.ts +++ b/src/global/actions/api/chats.ts @@ -116,7 +116,7 @@ addActionHandler('preloadTopChatMessages', async (global, actions): Promise { const { - id, threadId = MAIN_THREAD_ID, noForumTopicPanel, tabId = getCurrentTabId(), + id, threadId = MAIN_THREAD_ID, } = payload; if (!id) { return; @@ -137,10 +137,6 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => { }); } - if (chat?.isForum && !noForumTopicPanel) { - actions.openForumPanel({ chatId: id, tabId }); - } - if (!chat) { if (id === currentUserId) { void callApi('fetchChat', { type: 'self' }); diff --git a/src/global/actions/ui/chats.ts b/src/global/actions/ui/chats.ts index b92abd42b..d1cedde26 100644 --- a/src/global/actions/ui/chats.ts +++ b/src/global/actions/ui/chats.ts @@ -5,7 +5,7 @@ import { MAIN_THREAD_ID } from '../../../api/types'; import { exitMessageSelectMode, replaceTabThreadParam, updateCurrentMessageList, } from '../../reducers'; -import { selectCurrentMessageList, selectTabState } from '../../selectors'; +import { selectChat, selectCurrentMessageList, selectTabState } from '../../selectors'; import { closeLocalTextSearch } from './localSearch'; import type { ActionReturnType } from '../../types'; import { updateTabState } from '../../reducers/tabs'; @@ -18,6 +18,7 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => { threadId = MAIN_THREAD_ID, type = 'thread', shouldReplaceHistory = false, + noForumTopicPanel, tabId = getCurrentTabId(), } = payload; @@ -25,7 +26,7 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => { const tabState = selectTabState(global, tabId); if (tabState.premiumModal?.promo && tabState.premiumModal?.isOpen) { - return updateTabState(global, { + global = updateTabState(global, { premiumModal: { ...tabState.premiumModal, isOpen: false, @@ -55,8 +56,14 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => { }, tabId); } - if (id && id !== selectTabState(global, tabId).forumPanelChatId) { - actions.closeForumPanel({ tabId }); + if (id) { + const chat = selectChat(global, id); + + if (chat?.isForum && !noForumTopicPanel) { + actions.openForumPanel({ chatId: id!, tabId }); + } else if (id !== selectTabState(global, tabId).forumPanelChatId) { + actions.closeForumPanel({ tabId }); + } } return updateCurrentMessageList(global, id, threadId, type, shouldReplaceHistory, tabId); diff --git a/src/global/actions/ui/messages.ts b/src/global/actions/ui/messages.ts index 0c259c05e..e5bdb3ac8 100644 --- a/src/global/actions/ui/messages.ts +++ b/src/global/actions/ui/messages.ts @@ -303,7 +303,7 @@ addActionHandler('closePollResults', (global, actions, payload): ActionReturnTyp }); addActionHandler('focusLastMessage', (global, actions, payload): ActionReturnType => { - const { noForumTopicPanel, tabId = getCurrentTabId() } = payload || {}; + const { tabId = getCurrentTabId() } = payload || {}; const currentMessageList = selectCurrentMessageList(global, tabId); if (!currentMessageList) { return; @@ -331,7 +331,7 @@ addActionHandler('focusLastMessage', (global, actions, payload): ActionReturnTyp threadId, messageId: lastMessageId, noHighlight: true, - noForumTopicPanel, + noForumTopicPanel: true, tabId, }); }); @@ -348,7 +348,7 @@ addActionHandler('focusNextReply', (global, actions, payload): ActionReturnType const replyStack = selectReplyStack(global, chatId, threadId, tabId); if (!replyStack || replyStack.length === 0) { - actions.focusLastMessage({ noForumTopicPanel: true, tabId }); + actions.focusLastMessage({ tabId }); } else { const messageId = replyStack.pop(); diff --git a/src/global/helpers/chats.ts b/src/global/helpers/chats.ts index 11a560256..ca5982b63 100644 --- a/src/global/helpers/chats.ts +++ b/src/global/helpers/chats.ts @@ -274,7 +274,7 @@ export function getForumComposerPlaceholder( } if (threadId === MAIN_THREAD_ID) { - if (isReplying) return undefined; + if (isReplying || (chat.topics && !chat.topics[GENERAL_TOPIC_ID]?.isClosed)) return undefined; return lang('lng_forum_replies_only'); } diff --git a/src/global/types.ts b/src/global/types.ts index 45d9a4ae4..e46ec14a5 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -1458,9 +1458,7 @@ export interface ActionPayloads { noForumTopicPanel?: boolean; } & WithTabId; - focusLastMessage: ({ - noForumTopicPanel?: boolean; - } & WithTabId) | undefined; + focusLastMessage: WithTabId | undefined; setReplyingToId: { messageId?: number; } & WithTabId; diff --git a/src/hooks/useChatContextActions.ts b/src/hooks/useChatContextActions.ts index 80acc4bda..c523565b2 100644 --- a/src/hooks/useChatContextActions.ts +++ b/src/hooks/useChatContextActions.ts @@ -76,9 +76,12 @@ const useChatContextActions = ({ return compact([actionOpenInNewTab, actionPin, actionAddToFolder]); } - const actionUnreadMark = chat.unreadCount || chat.hasUnreadMark + const actionMaskAsRead = (chat.unreadCount || chat.hasUnreadMark) ? { title: lang('MarkAsRead'), icon: 'readchats', handler: () => toggleChatUnread({ id: chat.id }) } - : { title: lang('MarkAsUnread'), icon: 'unread', handler: () => toggleChatUnread({ id: chat.id }) }; + : undefined; + const actionMarkAsUnread = !(chat.unreadCount || chat.hasUnreadMark) && !chat.isForum + ? { title: lang('MarkAsUnread'), icon: 'unread', handler: () => toggleChatUnread({ id: chat.id }) } + : undefined; const actionMute = isMuted ? { @@ -117,7 +120,8 @@ const useChatContextActions = ({ return compact([ actionOpenInNewTab, actionAddToFolder, - actionUnreadMark, + actionMaskAsRead, + actionMarkAsUnread, actionPin, !isSelf && actionMute, !isSelf && !isServiceNotifications && !isInFolder && actionArchive,