Middle Header: Fix message counters in bot forums and saved (#6848)

This commit is contained in:
zubiden 2026-04-27 14:29:06 +02:00 committed by Alexander Zinchuk
parent 7c503dad68
commit 903f20010f
11 changed files with 110 additions and 43 deletions

View File

@ -205,7 +205,7 @@ export async function fetchMessages({
const messages = result.messages.map(buildApiMessage).filter(Boolean); const messages = result.messages.map(buildApiMessage).filter(Boolean);
const users = result.users.map(buildApiUser).filter(Boolean); const users = result.users.map(buildApiUser).filter(Boolean);
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean); const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
const count = !(result instanceof GramJs.messages.Messages) ? result.count : undefined; const count = 'count' in result ? result.count : messages.length;
const topics = result.topics.map(buildApiTopicWithState).filter(Boolean); const topics = result.topics.map(buildApiTopicWithState).filter(Boolean);
return { return {

View File

@ -287,7 +287,7 @@
"SendWithoutSound" = "Send Without Sound"; "SendWithoutSound" = "Send Without Sound";
"SetReminder" = "Set a Reminder"; "SetReminder" = "Set a Reminder";
"ScheduleMessage" = "Schedule Message"; "ScheduleMessage" = "Schedule Message";
"Updating" = "Updating..."; "Updating" = "Updating";
"OnlineCount_one" = "{count} online"; "OnlineCount_one" = "{count} online";
"OnlineCount_other" = "{count} online"; "OnlineCount_other" = "{count} online";
"Subscribers_one" = "{count} subscriber"; "Subscribers_one" = "{count} subscriber";
@ -777,6 +777,10 @@
"AccUnpinMessage" = "Unpin Message"; "AccUnpinMessage" = "Unpin Message";
"Comments_one" = "{count} Comment"; "Comments_one" = "{count} Comment";
"Comments_other" = "{count} Comments"; "Comments_other" = "{count} Comments";
"CommentsTitle" = "Comments";
"Replies_one" = "{count} Reply";
"Replies_other" = "{count} Replies";
"RepliesTitle" = "Replies";
"LeaveAComment" = "Leave a comment"; "LeaveAComment" = "Leave a comment";
"PollsStopWarning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone."; "PollsStopWarning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.";
"PollsStopSure" = "Stop"; "PollsStopSure" = "Stop";

View File

@ -287,10 +287,14 @@ const GroupChatInfo = ({
}; };
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { chatId, threadId }): Complete<StateProps> => { (global, { chatId, threadId, isSavedDialog }): Complete<StateProps> => {
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined; const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined;
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId)); const areMessagesLoaded = Boolean(
isSavedDialog
? selectChatMessages(global, global.currentUserId!)
: selectChatMessages(global, chatId),
);
const topic = threadId ? selectTopic(global, chatId, threadId) : undefined; const topic = threadId ? selectTopic(global, chatId, threadId) : undefined;
const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!); const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
const self = selectUser(global, global.currentUserId!); const self = selectUser(global, global.currentUserId!);

View File

@ -335,13 +335,19 @@ const PrivateChatInfo = ({
}; };
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { userId, threadId, forceShowSelf }): Complete<StateProps> => { (global, {
userId, threadId, forceShowSelf, isSavedDialog,
}): Complete<StateProps> => {
const { isSynced } = global; const { isSynced } = global;
const user = userId ? selectUser(global, userId) : undefined; const user = userId ? selectUser(global, userId) : undefined;
const userStatus = userId ? selectUserStatus(global, userId) : undefined; const userStatus = userId ? selectUserStatus(global, userId) : undefined;
const isSavedMessages = !forceShowSelf && user && user.isSelf; const isSavedMessages = !forceShowSelf && user && user.isSelf;
const self = isSavedMessages ? user : selectUser(global, global.currentUserId!); const self = isSavedMessages ? user : selectUser(global, global.currentUserId!);
const areMessagesLoaded = Boolean(userId ? selectChatMessages(global, userId) : undefined); const areMessagesLoaded = Boolean(
isSavedDialog
? selectChatMessages(global, global.currentUserId!)
: selectChatMessages(global, userId!),
);
const topic = threadId ? selectTopic(global, userId, threadId) : undefined; const topic = threadId ? selectTopic(global, userId, threadId) : undefined;
const messagesCount = topic && userId ? selectThreadMessagesCount(global, userId, threadId!) : undefined; const messagesCount = topic && userId ? selectThreadMessagesCount(global, userId, threadId!) : undefined;

View File

@ -124,7 +124,7 @@ const LeftMainHeader = ({
}, [searchDate]); }, [searchDate]);
const { connectionStatus, connectionStatusText, connectionStatusPosition } = useConnectionStatus( const { connectionStatus, connectionStatusText, connectionStatusPosition } = useConnectionStatus(
oldLang, lang,
connectionState, connectionState,
isSyncing || isFetchingDifference, isSyncing || isFetchingDifference,
isMessageListOpen, isMessageListOpen,

View File

@ -953,7 +953,7 @@ export default memo(withGlobal<OwnProps>(
const topic = selectTopic(global, chatId, threadId); const topic = selectTopic(global, chatId, threadId);
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
const isEmptyThread = !selectThreadInfo(global, chatId, threadId)?.messagesCount; const isEmptyThread = selectThreadInfo(global, chatId, threadId)?.messagesCount === 0;
const isCurrentUserPremium = selectIsCurrentUserPremium(global); const isCurrentUserPremium = selectIsCurrentUserPremium(global);
const areAdsEnabled = !isCurrentUserPremium || selectUserFullInfo(global, currentUserId)?.areAdsEnabled; const areAdsEnabled = !isCurrentUserPremium || selectUserFullInfo(global, currentUserId)?.areAdsEnabled;

View File

@ -1,5 +1,3 @@
import type { FC } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import { import {
memo, useRef, memo, useRef,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
@ -32,7 +30,9 @@ import {
selectScheduledIds, selectScheduledIds,
selectTabState, selectTabState,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectThreadInfo, selectThreadLocalStateParam } from '../../global/selectors/threads'; import {
selectThreadLocalStateParam, selectThreadMessagesCount,
} from '../../global/selectors/threads';
import { IS_TAURI } from '../../util/browser/globalEnvironment'; import { IS_TAURI } from '../../util/browser/globalEnvironment';
import { IS_MAC_OS } from '../../util/browser/windowEnvironment'; import { IS_MAC_OS } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
@ -40,9 +40,9 @@ import { isUserId } from '../../util/entities/ids';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';
import useConnectionStatus from '../../hooks/useConnectionStatus'; import useConnectionStatus from '../../hooks/useConnectionStatus';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import useLongPress from '../../hooks/useLongPress'; import useLongPress from '../../hooks/useLongPress';
import useOldLang from '../../hooks/useOldLang';
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated'; import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
import useWindowSize from '../../hooks/window/useWindowSize'; import useWindowSize from '../../hooks/window/useWindowSize';
@ -91,7 +91,7 @@ type StateProps = {
emojiStatusSlug?: string; emojiStatusSlug?: string;
}; };
const MiddleHeader: FC<OwnProps & StateProps> = ({ const MiddleHeader = ({
chatId, chatId,
threadId, threadId,
messageListType, messageListType,
@ -115,7 +115,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
emojiStatusSlug, emojiStatusSlug,
isSavedDialog, isSavedDialog,
onFocusPinnedMessage, onFocusPinnedMessage,
}) => { }: OwnProps & StateProps) => {
const { const {
openThreadWithInfo, openThreadWithInfo,
openChat, openChat,
@ -128,7 +128,8 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
openUniqueGiftBySlug, openUniqueGiftBySlug,
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useLang();
const isBackButtonActiveRef = useRef(true); const isBackButtonActiveRef = useRef(true);
const { isDesktop, isTablet } = useAppLayout(); const { isDesktop, isTablet } = useAppLayout();
@ -228,7 +229,41 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const isAudioPlayerRendering = isDesktop && isAudioPlayerActive; const isAudioPlayerRendering = isDesktop && isAudioPlayerActive;
const isPinnedMessagesFullWidth = isAudioPlayerActive || !isDesktop; const isPinnedMessagesFullWidth = isAudioPlayerActive || !isDesktop;
const { connectionStatusText } = useConnectionStatus(lang, connectionState, isSyncing || isFetchingDifference, true); const { connectionStatusText } = useConnectionStatus(
lang, connectionState, isSyncing || isFetchingDifference, true,
);
function renderInfoTitle() {
if (messagesCount === undefined) {
return lang('Loading');
}
if (messageListType === 'thread') {
if (!messagesCount) {
return lang(isComments ? 'CommentsTitle' : 'RepliesTitle');
}
return lang(
isComments ? 'Comments' : 'Replies',
{ count: messagesCount },
{ pluralValue: messagesCount },
);
}
if (messageListType === 'pinned') {
return lang('PinnedMessagesCount', { count: messagesCount }, { pluralValue: messagesCount });
}
if (messageListType === 'scheduled') {
if (isChatWithSelf) {
return lang('Reminders');
}
return lang('Messages', { count: messagesCount }, { pluralValue: messagesCount });
}
return undefined;
}
function renderInfo() { function renderInfo() {
if (messageListType === 'thread') { if (messageListType === 'thread') {
@ -240,25 +275,17 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
return ( return (
<> <>
{renderBackButton(currentTransitionKey === 0)} {renderBackButton(currentTransitionKey === 0)}
<h3> <h3>{renderInfoTitle()}</h3>
{messagesCount !== undefined ? (
messageListType === 'thread' ? (
(messagesCount
? lang(isComments ? 'Comments' : 'Replies', messagesCount, 'i')
: lang(isComments ? 'CommentsTitle' : 'RepliesTitle')))
: messageListType === 'pinned' ? (lang('PinnedMessagesCount', messagesCount, 'i'))
: messageListType === 'scheduled' ? (
isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount, 'i')
) : undefined
) : lang('Loading')}
</h3>
</> </>
); );
} }
function renderChatInfo() { function renderChatInfo() {
// TODO Implement count const savedMessagesStatus = isSavedDialog
const savedMessagesStatus = isSavedDialog ? lang('SavedMessages') : undefined; ? (messagesCount !== undefined
? lang('Messages', { count: messagesCount }, { pluralValue: messagesCount })
: lang('SavedMessages'))
: undefined;
const realChatId = isSavedDialog ? String(threadId) : chatId; const realChatId = isSavedDialog ? String(threadId) : chatId;
@ -387,6 +414,8 @@ export default memo(withGlobal<OwnProps>(
? selectChatMessage(global, audioChatId, audioMessageId) ? selectChatMessage(global, audioChatId, audioMessageId)
: undefined; : undefined;
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
let messagesCount: number | undefined; let messagesCount: number | undefined;
if (messageListType === 'pinned') { if (messageListType === 'pinned') {
const pinnedIds = selectPinnedIds(global, chatId, threadId); const pinnedIds = selectPinnedIds(global, chatId, threadId);
@ -395,8 +424,7 @@ export default memo(withGlobal<OwnProps>(
const scheduledIds = selectScheduledIds(global, chatId, threadId); const scheduledIds = selectScheduledIds(global, chatId, threadId);
messagesCount = scheduledIds?.length; messagesCount = scheduledIds?.length;
} else if (messageListType === 'thread' && threadId !== MAIN_THREAD_ID) { } else if (messageListType === 'thread' && threadId !== MAIN_THREAD_ID) {
const threadInfo = selectThreadInfo(global, chatId, threadId); messagesCount = selectThreadMessagesCount(global, chatId, threadId);
messagesCount = threadInfo?.messagesCount || 0;
} }
const typingStatus = selectThreadLocalStateParam(global, chatId, threadId, 'typingStatus'); const typingStatus = selectThreadLocalStateParam(global, chatId, threadId, 'typingStatus');
@ -405,8 +433,6 @@ export default memo(withGlobal<OwnProps>(
const emojiStatusSticker = emojiStatus && selectCustomEmoji(global, emojiStatus.documentId); const emojiStatusSticker = emojiStatus && selectCustomEmoji(global, emojiStatus.documentId);
const emojiStatusSlug = emojiStatus?.type === 'collectible' ? emojiStatus.slug : undefined; const emojiStatusSlug = emojiStatus?.type === 'collectible' ? emojiStatus.slug : undefined;
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
return { return {
typingStatus, typingStatus,
isLeftColumnShown, isLeftColumnShown,

View File

@ -8,13 +8,16 @@ import { MAIN_THREAD_ID } from '../../../api/types';
import { getIsSavedDialog } from '../../../global/helpers'; import { getIsSavedDialog } from '../../../global/helpers';
import { selectChat } from '../../../global/selectors'; import { selectChat } from '../../../global/selectors';
import { selectThreadLocalStateParam, selectThreadReadState } from '../../../global/selectors/threads'; import {
selectThreadLocalStateParam,
selectThreadMessagesCount,
selectThreadReadState,
} from '../../../global/selectors/threads';
import { isUserId } from '../../../util/entities/ids'; import { isUserId } from '../../../util/entities/ids';
import useConnectionStatus from '../../../hooks/useConnectionStatus'; import useConnectionStatus from '../../../hooks/useConnectionStatus';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import GroupChatInfo from '../../common/GroupChatInfo'; import GroupChatInfo from '../../common/GroupChatInfo';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
@ -35,6 +38,7 @@ type StateProps = {
isFetchingDifference?: boolean; isFetchingDifference?: boolean;
typingStatus?: ApiTypingStatus; typingStatus?: ApiTypingStatus;
isSavedDialog?: boolean; isSavedDialog?: boolean;
messagesCount?: number;
unreadCount?: number; unreadCount?: number;
hasUnreadMark?: boolean; hasUnreadMark?: boolean;
}; };
@ -50,23 +54,26 @@ const QuickPreviewModalHeader: FC<OwnProps & StateProps> = ({
isFetchingDifference, isFetchingDifference,
typingStatus, typingStatus,
isSavedDialog, isSavedDialog,
messagesCount,
unreadCount, unreadCount,
hasUnreadMark, hasUnreadMark,
onClose, onClose,
}) => { }) => {
const lang = useLang(); const lang = useLang();
const oldLang = useOldLang();
const { markChatMessagesRead } = getActions(); const { markChatMessagesRead } = getActions();
const { const {
connectionStatusText, connectionStatusText,
} = useConnectionStatus(oldLang, connectionState, isSyncing || isFetchingDifference, true); } = useConnectionStatus(lang, connectionState, isSyncing || isFetchingDifference, true);
const handleMarkAsRead = useLastCallback(() => { const handleMarkAsRead = useLastCallback(() => {
markChatMessagesRead({ id: chatId }); markChatMessagesRead({ id: chatId });
}); });
const savedMessagesStatus = isSavedDialog ? lang('SavedMessages') : undefined; const savedMessagesStatus = isSavedDialog
const realChatId = isSavedDialog ? String(MAIN_THREAD_ID) : chatId; ? (messagesCount !== undefined
? lang('Messages', { count: messagesCount }, { pluralValue: messagesCount }) : lang('SavedMessages'))
: undefined;
const realChatId = isSavedDialog && threadId ? String(threadId) : chatId;
const displayChatId = chat?.isMonoforum ? chat.linkedMonoforumId! : realChatId; const displayChatId = chat?.isMonoforum ? chat.linkedMonoforumId! : realChatId;
return ( return (
@ -137,6 +144,9 @@ export default memo(withGlobal<OwnProps>(
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
const typingStatus = selectThreadLocalStateParam(global, chatId, threadId || MAIN_THREAD_ID, 'typingStatus'); const typingStatus = selectThreadLocalStateParam(global, chatId, threadId || MAIN_THREAD_ID, 'typingStatus');
const isSavedDialog = getIsSavedDialog(chatId, threadId || MAIN_THREAD_ID, global.currentUserId); const isSavedDialog = getIsSavedDialog(chatId, threadId || MAIN_THREAD_ID, global.currentUserId);
const messagesCount = isSavedDialog && threadId
? selectThreadMessagesCount(global, chatId, threadId)
: undefined;
const readState = selectThreadReadState(global, chatId, threadId || MAIN_THREAD_ID); const readState = selectThreadReadState(global, chatId, threadId || MAIN_THREAD_ID);
const unreadCount = readState?.unreadCount; const unreadCount = readState?.unreadCount;
@ -147,6 +157,7 @@ export default memo(withGlobal<OwnProps>(
isFetchingDifference: global.isFetchingDifference, isFetchingDifference: global.isFetchingDifference,
typingStatus, typingStatus,
isSavedDialog, isSavedDialog,
messagesCount,
unreadCount, unreadCount,
hasUnreadMark: readState?.hasUnreadMark, hasUnreadMark: readState?.hasUnreadMark,
}; };

View File

@ -684,7 +684,18 @@ addActionHandler('requestSavedDialogUpdate', async (global, actions, payload): P
global = addMessages(global, result.messages); global = addMessages(global, result.messages);
if (result.messages.length) { if (result.messages.length) {
global = updateChatLastMessageId(global, chatId, result.messages[0].id, 'saved'); const currentUserId = global.currentUserId!;
const messagesCount = result.count ?? result.messages.length;
const lastMessageId = result.messages[0].id;
global = updateThreadInfo(global, {
isCommentsInfo: false,
chatId: currentUserId,
threadId: chatId,
lastMessageId,
messagesCount,
});
global = updateChatLastMessageId(global, chatId, lastMessageId, 'saved');
global = addChatListIds(global, 'saved', [chatId]); global = addChatListIds(global, 'saved', [chatId]);
setGlobal(global); setGlobal(global);

View File

@ -1,5 +1,5 @@
import type { GlobalState } from '../global/types'; import type { GlobalState } from '../global/types';
import type { OldLangFn } from './useOldLang'; import type { LangFn } from '../util/localization';
import useBrowserOnline from './window/useBrowserOnline'; import useBrowserOnline from './window/useBrowserOnline';
@ -16,7 +16,7 @@ type ConnectionStatusPosition =
| 'none'; | 'none';
export default function useConnectionStatus( export default function useConnectionStatus(
lang: OldLangFn, lang: LangFn,
connectionState: GlobalState['connectionState'], connectionState: GlobalState['connectionState'],
isSyncing: boolean | undefined, isSyncing: boolean | undefined,
hasMiddleHeader: boolean, hasMiddleHeader: boolean,

View File

@ -686,6 +686,8 @@ export interface LangPair {
'PinnedMessageTitleSingle': undefined; 'PinnedMessageTitleSingle': undefined;
'AccPinnedMessages': undefined; 'AccPinnedMessages': undefined;
'AccUnpinMessage': undefined; 'AccUnpinMessage': undefined;
'CommentsTitle': undefined;
'RepliesTitle': undefined;
'LeaveAComment': undefined; 'LeaveAComment': undefined;
'PollsStopWarning': undefined; 'PollsStopWarning': undefined;
'PollsStopSure': undefined; 'PollsStopSure': undefined;
@ -3749,6 +3751,9 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
'Comments': { 'Comments': {
'count': V; 'count': V;
}; };
'Replies': {
'count': V;
};
'ChatContextReactionCount': { 'ChatContextReactionCount': {
'count': V; 'count': V;
}; };