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 users = result.users.map(buildApiUser).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);
return {

View File

@ -287,7 +287,7 @@
"SendWithoutSound" = "Send Without Sound";
"SetReminder" = "Set a Reminder";
"ScheduleMessage" = "Schedule Message";
"Updating" = "Updating...";
"Updating" = "Updating";
"OnlineCount_one" = "{count} online";
"OnlineCount_other" = "{count} online";
"Subscribers_one" = "{count} subscriber";
@ -777,6 +777,10 @@
"AccUnpinMessage" = "Unpin Message";
"Comments_one" = "{count} Comment";
"Comments_other" = "{count} Comments";
"CommentsTitle" = "Comments";
"Replies_one" = "{count} Reply";
"Replies_other" = "{count} Replies";
"RepliesTitle" = "Replies";
"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.";
"PollsStopSure" = "Stop";

View File

@ -287,10 +287,14 @@ const GroupChatInfo = ({
};
export default memo(withGlobal<OwnProps>(
(global, { chatId, threadId }): Complete<StateProps> => {
(global, { chatId, threadId, isSavedDialog }): Complete<StateProps> => {
const chat = selectChat(global, chatId);
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 messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
const self = selectUser(global, global.currentUserId!);

View File

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

View File

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

View File

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

View File

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

View File

@ -684,7 +684,18 @@ addActionHandler('requestSavedDialogUpdate', async (global, actions, payload): P
global = addMessages(global, result.messages);
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]);
setGlobal(global);

View File

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

View File

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