diff --git a/src/components/middle/MessageListContent.tsx b/src/components/middle/MessageListContent.tsx
index 89a6ebfd2..59ebd3d97 100644
--- a/src/components/middle/MessageListContent.tsx
+++ b/src/components/middle/MessageListContent.tsx
@@ -3,7 +3,7 @@ import { getIsHeavyAnimating, memo } from '../../lib/teact/teact';
import { getActions, getGlobal } from '../../global';
import type { ApiMessage } from '../../api/types';
-import type { IAlbum, MessageListType, ThreadId } from '../../types';
+import type { IAlbum, IDocumentGroup, MessageListType, ThreadId } from '../../types';
import type { Signal } from '../../util/signals';
import type { MessageDateGroup } from './helpers/groupMessages';
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
@@ -26,7 +26,7 @@ import { formatHumanDate, formatScheduledDateTime } from '../../util/dates/oldDa
import { convertTonFromNanos } from '../../util/formatCurrency';
import { compact } from '../../util/iteratees';
import { formatStarsAsText, formatTonAsText } from '../../util/localization/format';
-import { isAlbum } from './helpers/groupMessages';
+import { isAlbum, isDocumentGroup } from './helpers/groupMessages';
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
import { renderPeerLink } from './message/helpers/messageActions';
@@ -130,6 +130,7 @@ const MessageListContent = ({
const areDatesClickable = !isSavedDialog && !isSchedule;
const shouldRenderSponsoredMessage = canShowAds && isViewportNewest;
+ const shouldHideComments = hasLinkedChat === false || !isChannelChat || Boolean(isChatMonoforum);
const {
observeIntersectionForReading,
@@ -270,7 +271,9 @@ const MessageListContent = ({
};
const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => {
- return acc + messageGroup.senderGroups.flat().length;
+ return acc + messageGroup.senderGroups.flat().reduce((innerAcc, messageOrAlbum) => {
+ return innerAcc + (isDocumentGroup(messageOrAlbum) ? messageOrAlbum.messages.length : 1);
+ }, 0);
}, 0);
let appearanceIndex = 0;
@@ -290,6 +293,7 @@ const MessageListContent = ({
if (
senderGroup.length === 1
&& !isAlbum(senderGroup[0])
+ && !isDocumentGroup(senderGroup[0])
&& isActionMessage(senderGroup[0])
&& senderGroup[0].content.action?.type !== 'phoneCall'
) {
@@ -318,31 +322,115 @@ const MessageListContent = ({
]);
}
- let currentDocumentGroupId: string | undefined;
+ let currentDocumentGroupHasThreadTop = false;
const senderGroupElements = senderGroup.map((
messageOrAlbum,
messageIndex,
) => {
+ function renderMessageElement(
+ message: ApiMessage,
+ position: {
+ isFirstInGroup: boolean;
+ isLastInGroup: boolean;
+ isFirstInDocumentGroup: boolean;
+ isLastInDocumentGroup: boolean;
+ isLastInList: boolean;
+ },
+ isThreadTopMessage: boolean,
+ album?: IAlbum,
+ documentGroup?: IDocumentGroup,
+ ) {
+ const isOwn = isOwnMessage(message);
+ const originalId = getMessageOriginalId(message);
+ const key = isServiceNotificationMessage(message)
+ ? `${message.date}_${originalId}` : originalId;
+
+ return compact([
+ message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
+ message.paidMessageStars && !withUsers && renderPaidMessageAction(message, album),
+ message.suggestedPostInfo && renderSuggestedPostInfoAction(message),
+ ,
+ ]);
+ }
+
+ if (isDocumentGroup(messageOrAlbum)) {
+ const documentGroup = messageOrAlbum;
+ return documentGroup.messages.map((docMessage, docIndex) => {
+ const isFirstInDocGroup = docIndex === 0;
+ const isLastInDocGroup = docIndex === documentGroup.messages.length - 1;
+
+ if (docMessage.previousLocalId && anchorIdRef.current === getMessageHtmlId(docMessage.previousLocalId)) {
+ anchorIdRef.current = getMessageHtmlId(docMessage.id);
+ }
+
+ if (isFirstInDocGroup && docMessage.id === threadId) {
+ currentDocumentGroupHasThreadTop = true;
+ }
+
+ const isThreadTopMessage = docMessage.id === threadId || currentDocumentGroupHasThreadTop;
+
+ if (isLastInDocGroup) {
+ currentDocumentGroupHasThreadTop = false;
+ }
+
+ const position = {
+ isFirstInGroup: messageIndex === 0 && isFirstInDocGroup,
+ isLastInGroup: messageIndex === senderGroup.length - 1 && isLastInDocGroup,
+ isFirstInDocumentGroup: isFirstInDocGroup,
+ isLastInDocumentGroup: isLastInDocGroup,
+ isLastInList: (
+ messageIndex === senderGroup.length - 1
+ && isLastInDocGroup
+ && senderGroupIndex === senderGroupsArray.length - 1
+ && dateGroupIndex === dateGroupsArray.length - 1
+ ),
+ };
+
+ return renderMessageElement(docMessage, position, isThreadTopMessage, undefined, documentGroup);
+ }).flat();
+ }
+
const message = isAlbum(messageOrAlbum) ? messageOrAlbum.mainMessage : messageOrAlbum;
const album = isAlbum(messageOrAlbum) ? messageOrAlbum : undefined;
- const isOwn = isOwnMessage(message);
- const isMessageAlbum = isAlbum(messageOrAlbum);
- const nextMessage = senderGroup[messageIndex + 1];
if (message.previousLocalId && anchorIdRef.current === getMessageHtmlId(message.previousLocalId)) {
anchorIdRef.current = getMessageHtmlId(message.id);
}
- const documentGroupId = !isMessageAlbum && message.groupedId ? message.groupedId : undefined;
- const nextDocumentGroupId = nextMessage && !isAlbum(nextMessage) ? nextMessage.groupedId : undefined;
const isThreadTopMessage = message.id === threadId;
const position = {
isFirstInGroup: messageIndex === 0,
isLastInGroup: messageIndex === senderGroup.length - 1,
- isFirstInDocumentGroup: Boolean(documentGroupId && documentGroupId !== currentDocumentGroupId),
- isLastInDocumentGroup: Boolean(documentGroupId && documentGroupId !== nextDocumentGroupId),
+ isFirstInDocumentGroup: false,
+ isLastInDocumentGroup: false,
isLastInList: (
messageIndex === senderGroup.length - 1
&& senderGroupIndex === senderGroupsArray.length - 1
@@ -350,60 +438,33 @@ const MessageListContent = ({
),
};
- currentDocumentGroupId = documentGroupId;
-
- const originalId = getMessageOriginalId(message);
- // Service notifications saved in cache in previous versions may share the same `previousLocalId`
- const key = isServiceNotificationMessage(message) ? `${message.date}_${originalId}` : originalId;
-
- const noComments = hasLinkedChat === false || !isChannelChat || Boolean(isChatMonoforum);
-
- return compact([
- message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
- message.paidMessageStars && !withUsers && renderPaidMessageAction(message, album),
- message.suggestedPostInfo && renderSuggestedPostInfoAction(message),
- ,
- ]);
+ return renderMessageElement(message, position, isThreadTopMessage, album);
}).flat();
if (!withUsers) return senderGroupElements;
- const lastMessageOrAlbum = senderGroup[senderGroup.length - 1];
- const lastMessage = isAlbum(lastMessageOrAlbum) ? lastMessageOrAlbum.mainMessage : lastMessageOrAlbum;
+ const lastItem = senderGroup[senderGroup.length - 1];
+ const lastMessage = isAlbum(lastItem)
+ ? lastItem.mainMessage
+ : isDocumentGroup(lastItem)
+ ? lastItem.messages[lastItem.messages.length - 1]
+ : lastItem;
const lastMessageId = getMessageOriginalId(lastMessage);
const lastAppearanceOrder = messageCountToAnimate - appearanceIndex;
- const isThreadTopMessage = lastMessage.id === threadId;
const isOwn = isOwnMessage(lastMessage);
- const firstMessageOrAlbum = senderGroup[0];
- const firstMessage = isAlbum(firstMessageOrAlbum) ? firstMessageOrAlbum.mainMessage : firstMessageOrAlbum;
+ const firstItem = senderGroup[0];
+ const firstMessage = isAlbum(firstItem)
+ ? firstItem.mainMessage
+ : isDocumentGroup(firstItem)
+ ? firstItem.messages[0]
+ : firstItem;
const firstMessageId = getMessageOriginalId(firstMessage);
+ const isThreadTopMessage = lastMessage.id === threadId
+ || (firstMessage.id === threadId && Boolean(firstMessage.groupedId));
+
const key = `${firstMessageId}-${lastMessageId}`;
const id = (firstMessageId === lastMessageId) ? `message-group-${firstMessageId}`
: `message-group-${firstMessageId}-${lastMessageId}`;
diff --git a/src/components/middle/helpers/groupMessages.ts b/src/components/middle/helpers/groupMessages.ts
index 701127a8c..976eb71b1 100644
--- a/src/components/middle/helpers/groupMessages.ts
+++ b/src/components/middle/helpers/groupMessages.ts
@@ -1,10 +1,10 @@
import type { ApiMessage } from '../../../api/types';
-import type { IAlbum } from '../../../types';
+import type { IAlbum, IDocumentGroup } from '../../../types';
import { isActionMessage } from '../../../global/helpers';
import { getDayStartAt } from '../../../util/dates/oldDateFormat';
-type SenderGroup = (ApiMessage | IAlbum)[];
+type SenderGroup = (ApiMessage | IAlbum | IDocumentGroup)[];
const GROUP_INTERVAL_SECONDS = 600; // 10 minutes
@@ -14,10 +14,16 @@ export type MessageDateGroup = {
senderGroups: SenderGroup[];
};
-export function isAlbum(messageOrAlbum: ApiMessage | IAlbum): messageOrAlbum is IAlbum {
+export function isAlbum(messageOrAlbum: ApiMessage | IAlbum | IDocumentGroup): messageOrAlbum is IAlbum {
return 'albumId' in messageOrAlbum;
}
+export function isDocumentGroup(
+ messageOrAlbum: ApiMessage | IAlbum | IDocumentGroup,
+): messageOrAlbum is IDocumentGroup {
+ return 'documentGroupId' in messageOrAlbum;
+}
+
export function groupMessages(
messages: ApiMessage[], firstUnreadId?: number, topMessageId?: number, isChatWithSelf?: boolean, withUsers?: boolean,
) {
@@ -27,6 +33,7 @@ export function groupMessages(
senderGroups: [[]],
};
let currentAlbum: IAlbum | undefined;
+ let currentDocumentGroup: IDocumentGroup | undefined;
const dateGroups: MessageDateGroup[] = [initDateGroup];
@@ -40,6 +47,8 @@ export function groupMessages(
messages: [message],
mainMessage: message,
hasMultipleCaptions: false,
+ commentsMessage: message.hasComments ? message : undefined,
+ captionMessage: message.content.text ? message : undefined,
} satisfies IAlbum;
} else {
currentAlbum.messages.push(message);
@@ -63,6 +72,20 @@ export function groupMessages(
hasMultipleCaptions: false,
isPaidMedia: true,
} satisfies IAlbum);
+ } else if (message.groupedId) {
+ if (!currentDocumentGroup) {
+ currentDocumentGroup = {
+ documentGroupId: message.groupedId,
+ messages: [message],
+ firstMessageId: message.id,
+ commentsMessage: message.hasComments ? message : undefined,
+ } satisfies IDocumentGroup;
+ } else {
+ currentDocumentGroup.messages.push(message);
+ if (message.hasComments) {
+ currentDocumentGroup.commentsMessage = message;
+ }
+ }
} else {
currentSenderGroup.push(message);
}
@@ -77,8 +100,16 @@ export function groupMessages(
currentAlbum = undefined;
}
+ if (
+ currentDocumentGroup
+ && (!nextMessage || !nextMessage.groupedId || nextMessage.groupedId !== currentDocumentGroup.documentGroupId)
+ ) {
+ currentSenderGroup.push(currentDocumentGroup);
+ currentDocumentGroup = undefined;
+ }
+
const lastMessageInSenderGroup = currentSenderGroup[currentSenderGroup.length - 1];
- if (nextMessage && !currentAlbum) {
+ if (nextMessage && !currentAlbum && !currentDocumentGroup) {
const nextMessageDayStartsAt = getDayStartAt(nextMessage.date * 1000);
if (currentDateGroup.datetime !== nextMessageDayStartsAt) {
const newDateGroup: MessageDateGroup = {
@@ -101,10 +132,12 @@ export function groupMessages(
|| (nextMessage.date - message.date) > GROUP_INTERVAL_SECONDS
|| (topMessageId
&& (message.id === topMessageId
- || (lastMessageInSenderGroup
- && 'mainMessage' in lastMessageInSenderGroup
- && lastMessageInSenderGroup.mainMessage?.id === topMessageId))
- && nextMessage.id !== topMessageId)
+ || (lastMessageInSenderGroup && (
+ (isAlbum(lastMessageInSenderGroup) && lastMessageInSenderGroup.mainMessage.id === topMessageId)
+ || (isDocumentGroup(lastMessageInSenderGroup) && lastMessageInSenderGroup.firstMessageId === topMessageId)
+ )))
+ && nextMessage.id !== topMessageId
+ && !(message.groupedId && message.groupedId === nextMessage.groupedId))
|| (isChatWithSelf && message.forwardInfo?.fromId !== nextMessage.forwardInfo?.fromId)
) {
currentDateGroup.senderGroups.push([]);
diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx
index 108b77525..533e5d849 100644
--- a/src/components/middle/message/Message.tsx
+++ b/src/components/middle/message/Message.tsx
@@ -34,6 +34,7 @@ import type {
ChatTranslatedMessages,
FocusDirection,
IAlbum,
+ IDocumentGroup,
MessageListType,
ScrollTargetPosition,
TextSummary,
@@ -97,7 +98,6 @@ import {
selectIsMessageFocused,
selectIsMessageProtected,
selectIsMessageSelected,
- selectMessageIdsByGroupId,
selectMessageSummary,
selectOutgoingStatus,
selectPeer,
@@ -225,6 +225,7 @@ type MessagePositionProperties = {
type OwnProps = {
message: ApiMessage;
album?: IAlbum;
+ documentGroup?: IDocumentGroup;
noAvatars?: boolean;
withAvatar?: boolean;
withSenderName?: boolean;
@@ -234,6 +235,7 @@ type OwnProps = {
noReplies: boolean;
appearanceOrder: number;
isJustAdded: boolean;
+ isThreadTop?: boolean;
memoFirstUnreadIdRef?: { current: number | undefined };
getIsMessageListReady?: Signal;
observeIntersectionForBottom?: ObserveFn;
@@ -249,7 +251,6 @@ type StateProps = {
canShowSender: boolean;
originSender?: ApiPeer;
botSender?: ApiUser;
- isThreadTop?: boolean;
shouldHideReply?: boolean;
replyMessage?: ApiMessage;
replyMessageSender?: ApiPeer;
@@ -2004,7 +2005,8 @@ export default memo(withGlobal(
loadingThread,
} = selectTabState(global);
const {
- message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup,
+ message, album, documentGroup, withSenderName, withAvatar, threadId, messageListType,
+ isLastInDocumentGroup, isFirstInGroup,
} = ownProps;
const {
id, chatId, viaBotId, isOutgoing, forwardInfo, transcriptionId, isPinned, viaBusinessBotId, effectId,
@@ -2040,8 +2042,6 @@ export default memo(withGlobal(
? (adminMembersById?.[sender?.id] || members?.find((member) => member.userId === sender?.id))
: undefined;
- const isThreadTop = message.id === threadId;
-
const { replyToMsgId, replyToPeerId, replyFrom } = getMessageReplyInfo(message) || {};
const { peerId: storyReplyPeerId, storyId: storyReplyId } = getStoryReplyInfo(message) || {};
@@ -2094,14 +2094,15 @@ export default memo(withGlobal(
const downloadableMedia = selectMessageDownloadableMedia(global, message);
const isDownloading = downloadableMedia && getIsDownloading(activeDownloads, downloadableMedia);
- const repliesThreadInfo = selectThreadInfo(global, chatId, album?.commentsMessage?.id || id);
-
const isInDocumentGroup = Boolean(message.groupedId) && !message.isInAlbum;
- const documentGroupFirstMessageId = isInDocumentGroup
- ? selectMessageIdsByGroupId(global, chatId, message.groupedId!)![0]
- : undefined;
+
+ const repliesThreadInfo = selectThreadInfo(
+ global, chatId, album?.commentsMessage?.id || documentGroup?.commentsMessage?.id || id,
+ );
const reactionMessage = isInDocumentGroup ? (
- isLastInDocumentGroup ? selectChatMessage(global, chatId, documentGroupFirstMessageId!) : undefined
+ isLastInDocumentGroup && documentGroup?.firstMessageId
+ ? selectChatMessage(global, chatId, documentGroup.firstMessageId)
+ : undefined
) : message;
const readState = selectThreadReadState(global, chatId, threadId);
@@ -2158,7 +2159,6 @@ export default memo(withGlobal(
originSender,
botSender,
shouldHideReply: shouldHideReply || isReplyToTopicStart,
- isThreadTop,
replyMessage,
replyMessageSender,
replyMessageForwardSender,
diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts
index 28201ed28..b61dd0d8f 100644
--- a/src/global/actions/api/messages.ts
+++ b/src/global/actions/api/messages.ts
@@ -141,6 +141,7 @@ import {
selectIsMonoforumAdmin,
selectLanguageCode,
selectListedIds,
+ selectMessageIdsByGroupId,
selectMessageReplyInfo,
selectOutlyingListByMessageId,
selectPeer,
@@ -1809,7 +1810,16 @@ async function loadViewportMessages(
if (threadId !== MAIN_THREAD_ID && !getIsSavedDialog(chatId, threadId, global.currentUserId)) {
const threadFirstMessageId = selectFirstMessageId(global, chatId, threadId);
if ((!ids[0] || threadFirstMessageId === ids[0]) && threadFirstMessageId !== threadId) {
- ids.unshift(Number(threadId));
+ const threadTopMessage = selectChatMessage(global, chatId, Number(threadId));
+ const groupedIds = threadTopMessage?.groupedId
+ ? selectMessageIdsByGroupId(global, chatId, threadTopMessage.groupedId)
+ : undefined;
+
+ if (groupedIds && groupedIds.length > 1) {
+ ids.unshift(...groupedIds);
+ } else {
+ ids.unshift(Number(threadId));
+ }
}
}
diff --git a/src/types/index.ts b/src/types/index.ts
index ba988aaea..c054b47ed 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -96,6 +96,13 @@ export interface IAlbum {
commentsMessage?: ApiMessage;
}
+export interface IDocumentGroup {
+ documentGroupId: string;
+ messages: ApiMessage[];
+ firstMessageId: number;
+ commentsMessage?: ApiMessage;
+}
+
export type ThreadId = string | number;
export type ThemeKey = 'light' | 'dark';