Fix various problems for comments and threads (#3809)
This commit is contained in:
parent
4571745654
commit
dcba75a11a
@ -29,7 +29,7 @@ import type {
|
|||||||
PhoneCallAction,
|
PhoneCallAction,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
ApiMessageEntityTypes,
|
ApiMessageEntityTypes, MAIN_THREAD_ID,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -41,7 +41,7 @@ import {
|
|||||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage';
|
import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { omitUndefined, pick } from '../../../util/iteratees';
|
||||||
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
||||||
import { interpolateArray } from '../../../util/waveform';
|
import { interpolateArray } from '../../../util/waveform';
|
||||||
import { buildPeer } from '../gramjsBuilders';
|
import { buildPeer } from '../gramjsBuilders';
|
||||||
@ -178,12 +178,12 @@ export function buildApiMessageWithChatId(
|
|||||||
const isInvoiceMedia = mtpMessage.media instanceof GramJs.MessageMediaInvoice
|
const isInvoiceMedia = mtpMessage.media instanceof GramJs.MessageMediaInvoice
|
||||||
&& Boolean(mtpMessage.media.extendedMedia);
|
&& Boolean(mtpMessage.media.extendedMedia);
|
||||||
|
|
||||||
const isEdited = mtpMessage.editDate && !mtpMessage.editHide;
|
const isEdited = Boolean(mtpMessage.editDate) && !mtpMessage.editHide;
|
||||||
const {
|
const {
|
||||||
inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective,
|
inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective,
|
||||||
} = buildReplyButtons(mtpMessage, isInvoiceMedia) || {};
|
} = buildReplyButtons(mtpMessage, isInvoiceMedia) || {};
|
||||||
const forwardInfo = mtpMessage.fwdFrom && buildApiMessageForwardInfo(mtpMessage.fwdFrom, isChatWithSelf);
|
const forwardInfo = mtpMessage.fwdFrom && buildApiMessageForwardInfo(mtpMessage.fwdFrom, isChatWithSelf);
|
||||||
const { replies, mediaUnread: isMediaUnread, postAuthor } = mtpMessage;
|
const { mediaUnread: isMediaUnread, postAuthor } = mtpMessage;
|
||||||
const groupedId = mtpMessage.groupedId && String(mtpMessage.groupedId);
|
const groupedId = mtpMessage.groupedId && String(mtpMessage.groupedId);
|
||||||
const isInAlbum = Boolean(groupedId) && !(content.document || content.audio || content.sticker);
|
const isInAlbum = Boolean(groupedId) && !(content.document || content.audio || content.sticker);
|
||||||
const shouldHideKeyboardButtons = mtpMessage.replyMarkup instanceof GramJs.ReplyKeyboardHide;
|
const shouldHideKeyboardButtons = mtpMessage.replyMarkup instanceof GramJs.ReplyKeyboardHide;
|
||||||
@ -192,8 +192,9 @@ export function buildApiMessageWithChatId(
|
|||||||
const isProtected = mtpMessage.noforwards || isInvoiceMedia;
|
const isProtected = mtpMessage.noforwards || isInvoiceMedia;
|
||||||
const isForwardingAllowed = !mtpMessage.noforwards;
|
const isForwardingAllowed = !mtpMessage.noforwards;
|
||||||
const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId);
|
const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId);
|
||||||
|
const hasComments = mtpMessage.replies?.comments;
|
||||||
|
|
||||||
return {
|
return omitUndefined({
|
||||||
id: mtpMessage.id,
|
id: mtpMessage.id,
|
||||||
chatId,
|
chatId,
|
||||||
isOutgoing,
|
isOutgoing,
|
||||||
@ -209,12 +210,12 @@ export function buildApiMessageWithChatId(
|
|||||||
reactions: mtpMessage.reactions && buildMessageReactions(mtpMessage.reactions),
|
reactions: mtpMessage.reactions && buildMessageReactions(mtpMessage.reactions),
|
||||||
emojiOnlyCount,
|
emojiOnlyCount,
|
||||||
...(mtpMessage.replyTo && { replyInfo: buildApiReplyInfo(mtpMessage.replyTo) }),
|
...(mtpMessage.replyTo && { replyInfo: buildApiReplyInfo(mtpMessage.replyTo) }),
|
||||||
...(forwardInfo && { forwardInfo }),
|
forwardInfo,
|
||||||
...(isEdited && { isEdited }),
|
isEdited,
|
||||||
...(mtpMessage.editDate && { editDate: mtpMessage.editDate }),
|
editDate: mtpMessage.editDate,
|
||||||
...(isMediaUnread && { isMediaUnread }),
|
isMediaUnread,
|
||||||
...(mtpMessage.mentioned && isMediaUnread && { hasUnreadMention: true }),
|
hasUnreadMention: mtpMessage.mentioned && isMediaUnread,
|
||||||
...(mtpMessage.mentioned && { isMentioned: true }),
|
isMentioned: mtpMessage.mentioned,
|
||||||
...(groupedId && {
|
...(groupedId && {
|
||||||
groupedId,
|
groupedId,
|
||||||
isInAlbum,
|
isInAlbum,
|
||||||
@ -225,11 +226,11 @@ export function buildApiMessageWithChatId(
|
|||||||
}),
|
}),
|
||||||
...(shouldHideKeyboardButtons && { shouldHideKeyboardButtons, isHideKeyboardSelective }),
|
...(shouldHideKeyboardButtons && { shouldHideKeyboardButtons, isHideKeyboardSelective }),
|
||||||
...(mtpMessage.viaBotId && { viaBotId: buildApiPeerId(mtpMessage.viaBotId, 'user') }),
|
...(mtpMessage.viaBotId && { viaBotId: buildApiPeerId(mtpMessage.viaBotId, 'user') }),
|
||||||
...(replies && { repliesThreadInfo: buildThreadInfo(replies, mtpMessage.id, chatId) }),
|
postAuthorTitle: postAuthor,
|
||||||
...(postAuthor && { postAuthorTitle: postAuthor }),
|
|
||||||
isProtected,
|
isProtected,
|
||||||
isForwardingAllowed,
|
isForwardingAllowed,
|
||||||
};
|
hasComments,
|
||||||
|
} satisfies ApiMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildMessageDraft(draft: GramJs.TypeDraftMessage): ApiDraft | undefined {
|
export function buildMessageDraft(draft: GramJs.TypeDraftMessage): ApiDraft | undefined {
|
||||||
@ -830,8 +831,11 @@ export function buildLocalForwardedMessage({
|
|||||||
text: !shouldHideText ? strippedText : undefined,
|
text: !shouldHideText ? strippedText : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const replyInfo: ApiReplyInfo | undefined = toThreadId ? {
|
// TODO Prepare reply info between forwarded messages locally, to prevent height jumps
|
||||||
|
const isToMainThread = toThreadId === MAIN_THREAD_ID;
|
||||||
|
const replyInfo: ApiReplyInfo | undefined = toThreadId && !isToMainThread ? {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
|
replyToMsgId: toThreadId,
|
||||||
replyToTopId: toThreadId,
|
replyToTopId: toThreadId,
|
||||||
isForumTopic: toChat.isForum || undefined,
|
isForumTopic: toChat.isForum || undefined,
|
||||||
} : undefined;
|
} : undefined;
|
||||||
@ -968,7 +972,21 @@ function buildUploadingMedia(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildThreadInfo(
|
export function buildApiThreadInfoFromMessage(
|
||||||
|
mtpMessage: GramJs.TypeMessage,
|
||||||
|
): ApiThreadInfo | undefined {
|
||||||
|
const chatId = resolveMessageApiChatId(mtpMessage);
|
||||||
|
if (
|
||||||
|
!chatId
|
||||||
|
|| !(mtpMessage instanceof GramJs.Message)
|
||||||
|
|| !mtpMessage.replies) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildApiThreadInfo(mtpMessage.replies, mtpMessage.id, chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApiThreadInfo(
|
||||||
messageReplies: GramJs.TypeMessageReplies, messageId: number, chatId: string,
|
messageReplies: GramJs.TypeMessageReplies, messageId: number, chatId: string,
|
||||||
): ApiThreadInfo | undefined {
|
): ApiThreadInfo | undefined {
|
||||||
const {
|
const {
|
||||||
@ -980,21 +998,28 @@ function buildThreadInfo(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPostThread = apiChannelId && chatId !== apiChannelId;
|
const baseThreadInfo = {
|
||||||
|
messagesCount: replies,
|
||||||
|
...(maxId && { lastMessageId: maxId }),
|
||||||
|
...(readMaxId && { lastReadMessageId: readMaxId }),
|
||||||
|
...(recentRepliers && { recentReplierIds: recentRepliers.map(getApiChatIdFromMtpPeer) }),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (comments) {
|
||||||
|
return {
|
||||||
|
...baseThreadInfo,
|
||||||
|
isCommentsInfo: true,
|
||||||
|
chatId: apiChannelId!,
|
||||||
|
originChannelId: chatId,
|
||||||
|
originMessageId: messageId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isComments: comments,
|
...baseThreadInfo,
|
||||||
|
isCommentsInfo: false,
|
||||||
|
chatId,
|
||||||
threadId: messageId,
|
threadId: messageId,
|
||||||
...(isPostThread ? {
|
|
||||||
chatId: apiChannelId,
|
|
||||||
originChannelId: chatId,
|
|
||||||
} : {
|
|
||||||
chatId,
|
|
||||||
}),
|
|
||||||
messagesCount: replies,
|
|
||||||
lastMessageId: maxId,
|
|
||||||
lastReadInboxMessageId: readMaxId,
|
|
||||||
...(recentRepliers && { recentReplierIds: recentRepliers.map(getApiChatIdFromMtpPeer) }),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -72,6 +72,7 @@ import {
|
|||||||
import localDb from '../localDb';
|
import localDb from '../localDb';
|
||||||
import { scheduleMutedChatUpdate } from '../scheduleUnmute';
|
import { scheduleMutedChatUpdate } from '../scheduleUnmute';
|
||||||
import { applyState, processUpdate, updateChannelState } from '../updateManager';
|
import { applyState, processUpdate, updateChannelState } from '../updateManager';
|
||||||
|
import { dispatchThreadInfoUpdates } from '../updater';
|
||||||
import { invokeRequest, uploadFile } from './client';
|
import { invokeRequest, uploadFile } from './client';
|
||||||
|
|
||||||
type FullChatData = {
|
type FullChatData = {
|
||||||
@ -130,6 +131,8 @@ export async function fetchChats({
|
|||||||
'chatId',
|
'chatId',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
const peersByKey = preparePeers(result);
|
const peersByKey = preparePeers(result);
|
||||||
if (resultPinned) {
|
if (resultPinned) {
|
||||||
Object.assign(peersByKey, preparePeers(resultPinned, peersByKey));
|
Object.assign(peersByKey, preparePeers(resultPinned, peersByKey));
|
||||||
@ -340,6 +343,8 @@ export async function requestChatUpdate({
|
|||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
const lastRemoteMessage = buildApiMessage(result.messages[0]);
|
const lastRemoteMessage = buildApiMessage(result.messages[0]);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
const lastMessage = lastLocalMessage && (!lastRemoteMessage || (lastLocalMessage.date > lastRemoteMessage.date))
|
const lastMessage = lastLocalMessage && (!lastRemoteMessage || (lastLocalMessage.date > lastRemoteMessage.date))
|
||||||
? lastLocalMessage
|
? lastLocalMessage
|
||||||
: lastRemoteMessage;
|
: lastRemoteMessage;
|
||||||
@ -542,7 +547,7 @@ async function getFullChannelInfo(
|
|||||||
kickedMembers,
|
kickedMembers,
|
||||||
adminMembersById: adminMembers ? buildCollectionByKey(adminMembers, 'userId') : undefined,
|
adminMembersById: adminMembers ? buildCollectionByKey(adminMembers, 'userId') : undefined,
|
||||||
groupCallId: call ? String(call.id) : undefined,
|
groupCallId: call ? String(call.id) : undefined,
|
||||||
linkedChatId: linkedChatId ? buildApiPeerId(linkedChatId, 'chat') : undefined,
|
linkedChatId: linkedChatId ? buildApiPeerId(linkedChatId, 'channel') : undefined,
|
||||||
botCommands,
|
botCommands,
|
||||||
enabledReactions: buildApiChatReactions(availableReactions),
|
enabledReactions: buildApiChatReactions(availableReactions),
|
||||||
sendAsId: defaultSendAs ? getApiChatIdFromMtpPeer(defaultSendAs) : undefined,
|
sendAsId: defaultSendAs ? getApiChatIdFromMtpPeer(defaultSendAs) : undefined,
|
||||||
@ -1584,6 +1589,7 @@ export async function fetchTopics({
|
|||||||
|
|
||||||
const topics = result.topics.map(buildApiTopic).filter(Boolean);
|
const topics = result.topics.map(buildApiTopic).filter(Boolean);
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
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 draftsById = result.topics.reduce((acc, topic) => {
|
const draftsById = result.topics.reduce((acc, topic) => {
|
||||||
@ -1637,6 +1643,7 @@ export async function fetchTopicById({
|
|||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
@ -28,12 +28,12 @@ export {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
fetchMessages, fetchMessage, sendMessage, pinMessage, unpinAllMessages, deleteMessages, deleteHistory,
|
fetchMessages, fetchMessage, sendMessage, pinMessage, unpinAllMessages, deleteMessages, deleteHistory,
|
||||||
markMessageListRead, markMessagesRead, requestThreadInfoUpdate, searchMessagesLocal, searchMessagesGlobal,
|
markMessageListRead, markMessagesRead, searchMessagesLocal, searchMessagesGlobal,
|
||||||
fetchWebPagePreview, editMessage, forwardMessages, loadPollOptionResults, sendPollVote, findFirstMessageIdAfterDate,
|
fetchWebPagePreview, editMessage, forwardMessages, loadPollOptionResults, sendPollVote, findFirstMessageIdAfterDate,
|
||||||
fetchPinnedMessages, fetchScheduledHistory, sendScheduledMessages, rescheduleMessage, deleteScheduledMessages,
|
fetchPinnedMessages, fetchScheduledHistory, sendScheduledMessages, rescheduleMessage, deleteScheduledMessages,
|
||||||
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
||||||
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
||||||
closePoll, fetchExtendedMedia, translateText, fetchMessageViews,
|
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -49,6 +49,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
buildApiMessage,
|
buildApiMessage,
|
||||||
buildApiSponsoredMessage,
|
buildApiSponsoredMessage,
|
||||||
|
buildApiThreadInfo,
|
||||||
|
buildApiThreadInfoFromMessage,
|
||||||
buildLocalForwardedMessage,
|
buildLocalForwardedMessage,
|
||||||
buildLocalMessage,
|
buildLocalMessage,
|
||||||
} from '../apiBuilders/messages';
|
} from '../apiBuilders/messages';
|
||||||
@ -76,9 +78,9 @@ import {
|
|||||||
addEntitiesToLocalDb,
|
addEntitiesToLocalDb,
|
||||||
addMessageToLocalDb,
|
addMessageToLocalDb,
|
||||||
deserializeBytes,
|
deserializeBytes,
|
||||||
resolveMessageApiChatId,
|
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { updateChannelState } from '../updateManager';
|
import { updateChannelState } from '../updateManager';
|
||||||
|
import { dispatchThreadInfoUpdates } from '../updater';
|
||||||
import { requestChatUpdate } from './chats';
|
import { requestChatUpdate } from './chats';
|
||||||
import { handleGramJsUpdate, invokeRequest, uploadFile } from './client';
|
import { handleGramJsUpdate, invokeRequest, uploadFile } from './client';
|
||||||
|
|
||||||
@ -156,13 +158,12 @@ 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 repliesThreadInfos = messages.map(({ repliesThreadInfo }) => repliesThreadInfo).filter(Boolean);
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
users,
|
users,
|
||||||
chats,
|
chats,
|
||||||
repliesThreadInfos,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +221,8 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
|
|||||||
}
|
}
|
||||||
|
|
||||||
const message = mtpMessage && buildApiMessage(mtpMessage);
|
const message = mtpMessage && buildApiMessage(mtpMessage);
|
||||||
|
dispatchThreadInfoUpdates([mtpMessage]);
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -857,8 +860,6 @@ export async function markMessageListRead({
|
|||||||
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
void requestChatUpdate({ chat, noLastMessage: true });
|
void requestChatUpdate({ chat, noLastMessage: true });
|
||||||
} else {
|
|
||||||
void requestThreadInfoUpdate({ chat, threadId });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -923,10 +924,7 @@ export async function fetchMessageViews({
|
|||||||
id,
|
id,
|
||||||
views,
|
views,
|
||||||
forwards,
|
forwards,
|
||||||
messagesCount: replies?.replies,
|
threadInfo: replies ? buildApiThreadInfo(replies, id, chat.id) : undefined,
|
||||||
recentReplierIds: replies?.recentRepliers?.map(getApiChatIdFromMtpPeer),
|
|
||||||
maxId: replies?.maxId,
|
|
||||||
readMaxId: replies?.readMaxId,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -937,94 +935,73 @@ export async function fetchMessageViews({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestThreadInfoUpdate({
|
export async function fetchDiscussionMessage({
|
||||||
chat, threadId, originChannelId,
|
chat, messageId,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat; threadId: number; originChannelId?: string;
|
chat: ApiChat;
|
||||||
|
messageId: number;
|
||||||
}) {
|
}) {
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
const [result, replies] = await Promise.all([
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [topMessageResult, repliesResult] = await Promise.all([
|
|
||||||
invokeRequest(new GramJs.messages.GetDiscussionMessage({
|
invokeRequest(new GramJs.messages.GetDiscussionMessage({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
msgId: Number(threadId),
|
msgId: messageId,
|
||||||
})),
|
}), {
|
||||||
invokeRequest(new GramJs.messages.GetReplies({
|
abortControllerChatId: chat.id,
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
abortControllerThreadId: messageId,
|
||||||
msgId: Number(threadId),
|
}),
|
||||||
|
fetchMessages({
|
||||||
|
chat,
|
||||||
|
threadId: messageId,
|
||||||
offsetId: 1,
|
offsetId: 1,
|
||||||
addOffset: -1,
|
addOffset: -1,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
})),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!topMessageResult || !topMessageResult.messages.length) {
|
if (!result || !replies) return undefined;
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const discussionChatId = resolveMessageApiChatId(topMessageResult.messages[0]);
|
updateLocalDb(result);
|
||||||
if (!discussionChatId) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const topMessageId = topMessageResult.messages[topMessageResult.messages.length - 1].id;
|
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean)
|
||||||
|
.concat(replies.chats);
|
||||||
|
const users = result.users.map(buildApiUser).filter(Boolean)
|
||||||
|
.concat(replies.users);
|
||||||
|
const topMessages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
const messages = topMessages.concat(replies.messages);
|
||||||
|
const threadId = result.messages[result.messages.length - 1]?.id;
|
||||||
|
|
||||||
onUpdate({
|
if (!threadId) return undefined;
|
||||||
'@type': 'updateThreadInfo',
|
|
||||||
chatId: discussionChatId,
|
|
||||||
threadId: topMessageId,
|
|
||||||
threadInfo: {
|
|
||||||
threadId: topMessageId,
|
|
||||||
topMessageId,
|
|
||||||
lastReadInboxMessageId: topMessageResult.readInboxMaxId,
|
|
||||||
messagesCount: (repliesResult instanceof GramJs.messages.ChannelMessages) ? repliesResult.count : undefined,
|
|
||||||
lastMessageId: topMessageResult.maxId,
|
|
||||||
...(originChannelId ? { originChannelId } : undefined),
|
|
||||||
},
|
|
||||||
firstMessageId: repliesResult && 'messages' in repliesResult && repliesResult.messages.length
|
|
||||||
? repliesResult.messages[0].id
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const chats = topMessageResult.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
chats.forEach((newChat) => {
|
const threadInfoUpdates = result.messages.map(buildApiThreadInfoFromMessage).filter(Boolean);
|
||||||
onUpdate({
|
|
||||||
'@type': 'updateChat',
|
|
||||||
id: newChat.id,
|
|
||||||
chat: newChat,
|
|
||||||
noTopChatsRequest: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (chat.isForum) {
|
const {
|
||||||
onUpdate({
|
unreadCount, maxId, readInboxMaxId, readOutboxMaxId,
|
||||||
'@type': 'updateTopic',
|
} = result;
|
||||||
chatId: chat.id,
|
|
||||||
topicId: threadId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addEntitiesToLocalDb(topMessageResult.users);
|
|
||||||
addEntitiesToLocalDb(topMessageResult.chats);
|
|
||||||
|
|
||||||
const users = topMessageResult.users.map(buildApiUser).filter(Boolean);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
topMessageId,
|
chats,
|
||||||
discussionChatId,
|
|
||||||
users,
|
users,
|
||||||
|
messages,
|
||||||
|
topMessages,
|
||||||
|
unreadCount,
|
||||||
|
threadId,
|
||||||
|
lastReadInboxMessageId: readInboxMaxId,
|
||||||
|
lastReadOutboxMessageId: readOutboxMaxId,
|
||||||
|
lastMessageId: maxId,
|
||||||
|
chatId: topMessages[0]?.chatId,
|
||||||
|
firstMessageId: replies.messages[0]?.id,
|
||||||
|
threadInfoUpdates,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchMessagesLocal({
|
export async function searchMessagesLocal({
|
||||||
chat, type, query, topMessageId, minDate, maxDate, ...pagination
|
chat, type, query, threadId, minDate, maxDate, ...pagination
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
type?: ApiMessageSearchType | ApiGlobalMessageSearchType;
|
type?: ApiMessageSearchType | ApiGlobalMessageSearchType;
|
||||||
query?: string;
|
query?: string;
|
||||||
topMessageId?: number;
|
threadId?: number;
|
||||||
offsetId?: number;
|
offsetId?: number;
|
||||||
addOffset?: number;
|
addOffset?: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
@ -1059,7 +1036,7 @@ export async function searchMessagesLocal({
|
|||||||
|
|
||||||
const result = await invokeRequest(new GramJs.messages.Search({
|
const result = await invokeRequest(new GramJs.messages.Search({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
topMsgId: topMessageId,
|
topMsgId: threadId === MAIN_THREAD_ID ? undefined : threadId,
|
||||||
filter,
|
filter,
|
||||||
q: query || '',
|
q: query || '',
|
||||||
minDate,
|
minDate,
|
||||||
@ -1067,7 +1044,7 @@ export async function searchMessagesLocal({
|
|||||||
...pagination,
|
...pagination,
|
||||||
}), {
|
}), {
|
||||||
abortControllerChatId: chat.id,
|
abortControllerChatId: chat.id,
|
||||||
abortControllerThreadId: topMessageId,
|
abortControllerThreadId: threadId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -1083,6 +1060,7 @@ export async function searchMessagesLocal({
|
|||||||
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
let totalCount = messages.length;
|
let totalCount = messages.length;
|
||||||
let nextOffsetId: number | undefined;
|
let nextOffsetId: number | undefined;
|
||||||
@ -1168,6 +1146,7 @@ export async function searchMessagesGlobal({
|
|||||||
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
let totalCount = messages.length;
|
let totalCount = messages.length;
|
||||||
let nextRate: number | undefined;
|
let nextRate: number | undefined;
|
||||||
@ -1417,6 +1396,7 @@ export async function fetchScheduledHistory({ chat }: { chat: ApiChat }) {
|
|||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
@ -1475,6 +1455,7 @@ export async function fetchPinnedMessages({ chat, threadId }: { chat: ApiChat; t
|
|||||||
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
@ -1617,6 +1598,7 @@ export async function fetchUnreadMentions({
|
|||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
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);
|
||||||
|
|
||||||
@ -1653,6 +1635,7 @@ export async function fetchUnreadReactions({
|
|||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import type {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
import { DEBUG, GENERAL_TOPIC_ID } from '../../config';
|
import { DEBUG, GENERAL_TOPIC_ID } from '../../config';
|
||||||
import { omit, pick } from '../../util/iteratees';
|
import { compact, omit, pick } from '../../util/iteratees';
|
||||||
import { getServerTimeOffset, setServerTimeOffset } from '../../util/serverTime';
|
import { getServerTimeOffset, setServerTimeOffset } from '../../util/serverTime';
|
||||||
import { buildApiBotMenuButton } from './apiBuilders/bots';
|
import { buildApiBotMenuButton } from './apiBuilders/bots';
|
||||||
import {
|
import {
|
||||||
@ -38,6 +38,7 @@ import {
|
|||||||
buildApiMessageFromNotification,
|
buildApiMessageFromNotification,
|
||||||
buildApiMessageFromShort,
|
buildApiMessageFromShort,
|
||||||
buildApiMessageFromShortChat,
|
buildApiMessageFromShortChat,
|
||||||
|
buildApiThreadInfoFromMessage,
|
||||||
buildMessageDraft,
|
buildMessageDraft,
|
||||||
} from './apiBuilders/messages';
|
} from './apiBuilders/messages';
|
||||||
import {
|
import {
|
||||||
@ -125,6 +126,16 @@ export function dispatchUserAndChatUpdates(entities: (GramJs.TypeUser | GramJs.T
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dispatchThreadInfoUpdates(messages: (GramJs.TypeMessage | undefined)[]) {
|
||||||
|
const threadInfoUpdates = compact(messages).map(buildApiThreadInfoFromMessage).filter(Boolean);
|
||||||
|
if (!threadInfoUpdates.length) return;
|
||||||
|
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'updateThreadInfos',
|
||||||
|
threadInfoUpdates,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function sendUpdate(update: ApiUpdate) {
|
export function sendUpdate(update: ApiUpdate) {
|
||||||
onUpdate(update);
|
onUpdate(update);
|
||||||
}
|
}
|
||||||
@ -199,6 +210,8 @@ export function updater(update: Update) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message = buildApiMessage(update.message)!;
|
message = buildApiMessage(update.message)!;
|
||||||
|
dispatchThreadInfoUpdates([update.message]);
|
||||||
|
|
||||||
shouldForceReply = 'replyMarkup' in update.message
|
shouldForceReply = 'replyMarkup' in update.message
|
||||||
&& update.message?.replyMarkup instanceof GramJs.ReplyKeyboardForceReply
|
&& update.message?.replyMarkup instanceof GramJs.ReplyKeyboardForceReply
|
||||||
&& (!update.message.replyMarkup.selective || message.isMentioned);
|
&& (!update.message.replyMarkup.selective || message.isMentioned);
|
||||||
@ -348,6 +361,7 @@ export function updater(update: Update) {
|
|||||||
|
|
||||||
// Workaround for a weird server behavior when own message is marked as incoming
|
// Workaround for a weird server behavior when own message is marked as incoming
|
||||||
const message = omit(buildApiMessage(update.message)!, ['isOutgoing']);
|
const message = omit(buildApiMessage(update.message)!, ['isOutgoing']);
|
||||||
|
dispatchThreadInfoUpdates([update.message]);
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateMessage',
|
'@type': 'updateMessage',
|
||||||
@ -548,12 +562,12 @@ export function updater(update: Update) {
|
|||||||
});
|
});
|
||||||
} else if (update instanceof GramJs.UpdateReadChannelDiscussionInbox) {
|
} else if (update instanceof GramJs.UpdateReadChannelDiscussionInbox) {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateThreadInfo',
|
'@type': 'updateThreadInfos',
|
||||||
chatId: buildApiPeerId(update.channelId, 'channel'),
|
threadInfoUpdates: [{
|
||||||
threadId: update.topMsgId,
|
chatId: buildApiPeerId(update.channelId, 'channel'),
|
||||||
threadInfo: {
|
threadId: update.topMsgId,
|
||||||
lastReadInboxMessageId: update.readMaxId,
|
lastReadInboxMessageId: update.readMaxId,
|
||||||
},
|
}],
|
||||||
});
|
});
|
||||||
} else if (update instanceof GramJs.UpdateReadChannelDiscussionOutbox) {
|
} else if (update instanceof GramJs.UpdateReadChannelDiscussionOutbox) {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
|
|||||||
@ -480,7 +480,6 @@ export interface ApiMessage {
|
|||||||
isKeyboardSingleUse?: boolean;
|
isKeyboardSingleUse?: boolean;
|
||||||
isKeyboardSelective?: boolean;
|
isKeyboardSelective?: boolean;
|
||||||
viaBotId?: string;
|
viaBotId?: string;
|
||||||
repliesThreadInfo?: ApiThreadInfo;
|
|
||||||
postAuthorTitle?: string;
|
postAuthorTitle?: string;
|
||||||
isScheduled?: boolean;
|
isScheduled?: boolean;
|
||||||
shouldHideKeyboardButtons?: boolean;
|
shouldHideKeyboardButtons?: boolean;
|
||||||
@ -500,6 +499,7 @@ export interface ApiMessage {
|
|||||||
reactions: ApiPeerReaction[];
|
reactions: ApiPeerReaction[];
|
||||||
};
|
};
|
||||||
reactions?: ApiReactions;
|
reactions?: ApiReactions;
|
||||||
|
hasComments?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiReactions {
|
export interface ApiReactions {
|
||||||
@ -559,18 +559,31 @@ export type ApiReactionCustomEmoji = {
|
|||||||
|
|
||||||
export type ApiReaction = ApiReactionEmoji | ApiReactionCustomEmoji;
|
export type ApiReaction = ApiReactionEmoji | ApiReactionCustomEmoji;
|
||||||
|
|
||||||
export interface ApiThreadInfo {
|
interface ApiBaseThreadInfo {
|
||||||
isComments?: boolean;
|
|
||||||
threadId: number;
|
|
||||||
chatId: string;
|
chatId: string;
|
||||||
topMessageId?: number;
|
|
||||||
originChannelId?: string;
|
|
||||||
messagesCount: number;
|
messagesCount: number;
|
||||||
lastMessageId?: number;
|
lastMessageId?: number;
|
||||||
lastReadInboxMessageId?: number;
|
lastReadInboxMessageId?: number;
|
||||||
recentReplierIds?: string[];
|
recentReplierIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiCommentsInfo extends ApiBaseThreadInfo {
|
||||||
|
isCommentsInfo: true;
|
||||||
|
threadId?: number;
|
||||||
|
originChannelId: string;
|
||||||
|
originMessageId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiMessageThreadInfo extends ApiBaseThreadInfo {
|
||||||
|
isCommentsInfo: false;
|
||||||
|
threadId: number;
|
||||||
|
// For linked messages in discussion
|
||||||
|
fromChannelId?: string;
|
||||||
|
fromMessageId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiThreadInfo = ApiCommentsInfo | ApiMessageThreadInfo;
|
||||||
|
|
||||||
export type ApiMessageOutgoingStatus = 'read' | 'succeeded' | 'pending' | 'failed';
|
export type ApiMessageOutgoingStatus = 'read' | 'succeeded' | 'pending' | 'failed';
|
||||||
|
|
||||||
export type ApiSponsoredMessage = {
|
export type ApiSponsoredMessage = {
|
||||||
|
|||||||
@ -222,12 +222,9 @@ export type ApiUpdatePinnedMessageIds = {
|
|||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiUpdateThreadInfo = {
|
export type ApiUpdateThreadInfos = {
|
||||||
'@type': 'updateThreadInfo';
|
'@type': 'updateThreadInfos';
|
||||||
chatId: string;
|
threadInfoUpdates: Partial<ApiThreadInfo>[];
|
||||||
threadId: number;
|
|
||||||
threadInfo: Partial<ApiThreadInfo>;
|
|
||||||
firstMessageId?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiUpdateScheduledMessageSendSucceeded = {
|
export type ApiUpdateScheduledMessageSendSucceeded = {
|
||||||
@ -685,7 +682,7 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateChat | ApiUpdateChatInbox | ApiUpdateChatTypingStatus | ApiUpdateChatFullInfo | ApiUpdatePinnedChatIds |
|
ApiUpdateChat | ApiUpdateChatInbox | ApiUpdateChatTypingStatus | ApiUpdateChatFullInfo | ApiUpdatePinnedChatIds |
|
||||||
ApiUpdateChatMembers | ApiUpdateChatJoin | ApiUpdateChatLeave | ApiUpdateChatPinned | ApiUpdatePinnedMessageIds |
|
ApiUpdateChatMembers | ApiUpdateChatJoin | ApiUpdateChatLeave | ApiUpdateChatPinned | ApiUpdatePinnedMessageIds |
|
||||||
ApiUpdateChatListType | ApiUpdateChatFolder | ApiUpdateChatFoldersOrder | ApiUpdateRecommendedChatFolders |
|
ApiUpdateChatListType | ApiUpdateChatFolder | ApiUpdateChatFoldersOrder | ApiUpdateRecommendedChatFolders |
|
||||||
ApiUpdateNewMessage | ApiUpdateMessage | ApiUpdateThreadInfo | ApiUpdateCommonBoxMessages | ApiUpdateChannelMessages |
|
ApiUpdateNewMessage | ApiUpdateMessage | ApiUpdateThreadInfos | ApiUpdateCommonBoxMessages |
|
||||||
ApiUpdateDeleteMessages | ApiUpdateMessagePoll | ApiUpdateMessagePollVote | ApiUpdateDeleteHistory |
|
ApiUpdateDeleteMessages | ApiUpdateMessagePoll | ApiUpdateMessagePollVote | ApiUpdateDeleteHistory |
|
||||||
ApiUpdateMessageSendSucceeded | ApiUpdateMessageSendFailed | ApiUpdateServiceNotification |
|
ApiUpdateMessageSendSucceeded | ApiUpdateMessageSendFailed | ApiUpdateServiceNotification |
|
||||||
ApiDeleteContact | ApiUpdateUser | ApiUpdateUserStatus | ApiUpdateUserFullInfo | ApiUpdateDeleteProfilePhotos |
|
ApiDeleteContact | ApiUpdateUser | ApiUpdateUserStatus | ApiUpdateUserFullInfo | ApiUpdateDeleteProfilePhotos |
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const ChatForumLastMessage: FC<OwnProps> = ({
|
|||||||
renderLastMessage,
|
renderLastMessage,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
}) => {
|
}) => {
|
||||||
const { openChat } = getActions();
|
const { openThread } = getActions();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const lastMessageRef = useRef<HTMLDivElement>(null);
|
const lastMessageRef = useRef<HTMLDivElement>(null);
|
||||||
@ -67,8 +67,8 @@ const ChatForumLastMessage: FC<OwnProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
openChat({
|
openThread({
|
||||||
id: chat.id,
|
chatId: chat.id,
|
||||||
threadId: lastActiveTopic.id,
|
threadId: lastActiveTopic.id,
|
||||||
shouldReplaceHistory: true,
|
shouldReplaceHistory: true,
|
||||||
noForumTopicPanel: getIsMobile(),
|
noForumTopicPanel: getIsMobile(),
|
||||||
|
|||||||
@ -349,7 +349,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
openPollModal,
|
openPollModal,
|
||||||
closePollModal,
|
closePollModal,
|
||||||
loadScheduledHistory,
|
loadScheduledHistory,
|
||||||
openChat,
|
openThread,
|
||||||
addRecentEmoji,
|
addRecentEmoji,
|
||||||
sendInlineBotResult,
|
sendInlineBotResult,
|
||||||
loadSendAs,
|
loadSendAs,
|
||||||
@ -1246,8 +1246,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleAllScheduledClick = useLastCallback(() => {
|
const handleAllScheduledClick = useLastCallback(() => {
|
||||||
openChat({
|
openThread({
|
||||||
id: chatId, threadId, type: 'scheduled', noForumTopicPanel: true,
|
chatId, threadId, type: 'scheduled', noForumTopicPanel: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -260,9 +260,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const chat = chatId && selectChat(global, chatId);
|
const chat = chatId && selectChat(global, chatId);
|
||||||
const sendOptions = chat ? getAllowedAttachmentOptions(chat) : undefined;
|
const sendOptions = chat ? getAllowedAttachmentOptions(chat) : undefined;
|
||||||
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
||||||
const isComments = Boolean(threadInfo?.originChannelId);
|
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
const canSendStickers = Boolean(
|
const canSendStickers = Boolean(
|
||||||
chat && threadId && getCanPostInChat(chat, threadId, isComments) && sendOptions?.canSendStickers,
|
chat && threadId && getCanPostInChat(chat, threadId, isMessageThread) && sendOptions?.canSendStickers,
|
||||||
);
|
);
|
||||||
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
|
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import buildClassName from '../../../util/buildClassName';
|
|||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
|
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
|
||||||
import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners';
|
import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners';
|
||||||
import { createLocationHash } from '../../../util/routing';
|
|
||||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
|
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
@ -139,7 +138,6 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive: isVisible,
|
isActive: isVisible,
|
||||||
onBack: handleClose,
|
onBack: handleClose,
|
||||||
hash: chat ? createLocationHash(chat.id, 'thread', MAIN_THREAD_ID) : undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => (isVisible ? captureEscKeyListener(handleClose) : undefined), [handleClose, isVisible]);
|
useEffect(() => (isVisible ? captureEscKeyListener(handleClose) : undefined), [handleClose, isVisible]);
|
||||||
|
|||||||
@ -92,7 +92,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
draft,
|
draft,
|
||||||
wasTopicOpened,
|
wasTopicOpened,
|
||||||
}) => {
|
}) => {
|
||||||
const { openChat, deleteTopic, focusLastMessage } = getActions();
|
const { openThread, deleteTopic, focusLastMessage } = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenTopic = useLastCallback(() => {
|
const handleOpenTopic = useLastCallback(() => {
|
||||||
openChat({ id: chatId, threadId: topic.id, shouldReplaceHistory: true });
|
openThread({ chatId, threadId: topic.id, shouldReplaceHistory: true });
|
||||||
|
|
||||||
if (canScrollDown) {
|
if (canScrollDown) {
|
||||||
focusLastMessage();
|
focusLastMessage();
|
||||||
|
|||||||
@ -50,7 +50,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
|||||||
onSearchDateSelect,
|
onSearchDateSelect,
|
||||||
onReset,
|
onReset,
|
||||||
}) => {
|
}) => {
|
||||||
const { searchMessagesGlobal, openChat } = getActions();
|
const { searchMessagesGlobal, openThread } = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
@ -68,13 +68,14 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleTopicClick = useCallback(
|
const handleTopicClick = useCallback(
|
||||||
(id: number) => {
|
(id: number) => {
|
||||||
openChat({ id: searchChatId, threadId: id, shouldReplaceHistory: true });
|
if (!searchChatId) return;
|
||||||
|
openThread({ chatId: searchChatId, threadId: id, shouldReplaceHistory: true });
|
||||||
|
|
||||||
if (!isMobile) {
|
if (!isMobile) {
|
||||||
onReset();
|
onReset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[openChat, searchChatId, isMobile, onReset],
|
[searchChatId, isMobile, onReset],
|
||||||
);
|
);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
|
|||||||
@ -256,7 +256,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
closePaymentModal,
|
closePaymentModal,
|
||||||
clearReceipt,
|
clearReceipt,
|
||||||
checkAppVersion,
|
checkAppVersion,
|
||||||
openChat,
|
openThread,
|
||||||
toggleLeftColumn,
|
toggleLeftColumn,
|
||||||
loadRecentEmojiStatuses,
|
loadRecentEmojiStatuses,
|
||||||
updatePageTitle,
|
updatePageTitle,
|
||||||
@ -419,8 +419,8 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
const parsedLocationHash = parseLocationHash();
|
const parsedLocationHash = parseLocationHash();
|
||||||
if (!parsedLocationHash) return;
|
if (!parsedLocationHash) return;
|
||||||
|
|
||||||
openChat({
|
openThread({
|
||||||
id: parsedLocationHash.chatId,
|
chatId: parsedLocationHash.chatId,
|
||||||
threadId: parsedLocationHash.threadId,
|
threadId: parsedLocationHash.threadId,
|
||||||
type: parsedLocationHash.type,
|
type: parsedLocationHash.type,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -208,7 +208,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleAsMessagesClick = useLastCallback(() => {
|
const handleAsMessagesClick = useLastCallback(() => {
|
||||||
openChat({ id: chatId, threadId: MAIN_THREAD_ID });
|
openChat({ id: chatId });
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleRequestCall() {
|
function handleRequestCall() {
|
||||||
|
|||||||
@ -181,7 +181,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
toggleStatistics,
|
toggleStatistics,
|
||||||
openBoostStatistics,
|
openBoostStatistics,
|
||||||
openGiftPremiumModal,
|
openGiftPremiumModal,
|
||||||
openChatWithInfo,
|
openThreadWithInfo,
|
||||||
openCreateTopicPanel,
|
openCreateTopicPanel,
|
||||||
openEditTopicPanel,
|
openEditTopicPanel,
|
||||||
openChat,
|
openChat,
|
||||||
@ -231,7 +231,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleViewGroupInfo = useLastCallback(() => {
|
const handleViewGroupInfo = useLastCallback(() => {
|
||||||
openChatWithInfo({ id: chatId, threadId });
|
openThreadWithInfo({ chatId, threadId });
|
||||||
setShouldCloseFast(!isRightColumnShown);
|
setShouldCloseFast(!isRightColumnShown);
|
||||||
closeMenu();
|
closeMenu();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -49,7 +49,6 @@ import {
|
|||||||
selectScrollOffset,
|
selectScrollOffset,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadTopMessageId,
|
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -83,6 +82,7 @@ type OwnProps = {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: number;
|
||||||
type: MessageListType;
|
type: MessageListType;
|
||||||
|
isComments?: boolean;
|
||||||
canPost: boolean;
|
canPost: boolean;
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
onFabToggle: (shouldShow: boolean) => void;
|
onFabToggle: (shouldShow: boolean) => void;
|
||||||
@ -106,18 +106,18 @@ type StateProps = {
|
|||||||
messageIds?: number[];
|
messageIds?: number[];
|
||||||
messagesById?: Record<number, ApiMessage>;
|
messagesById?: Record<number, ApiMessage>;
|
||||||
firstUnreadId?: number;
|
firstUnreadId?: number;
|
||||||
isComments?: boolean;
|
|
||||||
isViewportNewest?: boolean;
|
isViewportNewest?: boolean;
|
||||||
isRestricted?: boolean;
|
isRestricted?: boolean;
|
||||||
restrictionReason?: ApiRestrictionReason;
|
restrictionReason?: ApiRestrictionReason;
|
||||||
focusingId?: number;
|
focusingId?: number;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
lastMessage?: ApiMessage;
|
lastMessage?: ApiMessage;
|
||||||
threadTopMessageId?: number;
|
|
||||||
hasLinkedChat?: boolean;
|
hasLinkedChat?: boolean;
|
||||||
topic?: ApiTopic;
|
topic?: ApiTopic;
|
||||||
noMessageSendingAnimation?: boolean;
|
noMessageSendingAnimation?: boolean;
|
||||||
isServiceNotificationsChat?: boolean;
|
isServiceNotificationsChat?: boolean;
|
||||||
|
isEmptyThread?: boolean;
|
||||||
|
isForum?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
||||||
@ -143,6 +143,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
isChatLoaded,
|
isChatLoaded,
|
||||||
|
isForum,
|
||||||
isChannelChat,
|
isChannelChat,
|
||||||
isGroupChat,
|
isGroupChat,
|
||||||
canPost,
|
canPost,
|
||||||
@ -158,10 +159,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
isViewportNewest,
|
isViewportNewest,
|
||||||
isRestricted,
|
isRestricted,
|
||||||
restrictionReason,
|
restrictionReason,
|
||||||
|
isEmptyThread,
|
||||||
focusingId,
|
focusingId,
|
||||||
isSelectModeActive,
|
isSelectModeActive,
|
||||||
lastMessage,
|
lastMessage,
|
||||||
threadTopMessageId,
|
|
||||||
hasLinkedChat,
|
hasLinkedChat,
|
||||||
withBottomShift,
|
withBottomShift,
|
||||||
withDefaultBg,
|
withDefaultBg,
|
||||||
@ -244,15 +245,20 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
: ['id'];
|
: ['id'];
|
||||||
|
|
||||||
return listedMessages.length
|
return listedMessages.length
|
||||||
? groupMessages(orderBy(listedMessages, orderRule), memoUnreadDividerBeforeIdRef.current, isChatWithSelf)
|
? groupMessages(
|
||||||
|
orderBy(listedMessages, orderRule),
|
||||||
|
memoUnreadDividerBeforeIdRef.current,
|
||||||
|
!isForum ? threadId : undefined,
|
||||||
|
isChatWithSelf,
|
||||||
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
}, [messageIds, messagesById, type, isServiceNotificationsChat, isChatWithSelf]);
|
}, [messageIds, messagesById, type, isServiceNotificationsChat, isForum, threadId, isChatWithSelf]);
|
||||||
|
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
if (!messageIds || !messagesById || type === 'scheduled') {
|
if (!messageIds || !messagesById || type === 'scheduled') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ids = messageIds.filter((id) => messagesById[id]?.reactions);
|
const ids = messageIds.filter((id) => messagesById[id]?.reactions?.results.length);
|
||||||
|
|
||||||
if (!ids.length) return;
|
if (!ids.length) return;
|
||||||
|
|
||||||
@ -285,7 +291,8 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
if (!messageIds || !messagesById || threadId !== MAIN_THREAD_ID || type === 'scheduled') {
|
if (!messageIds || !messagesById || threadId !== MAIN_THREAD_ID || type === 'scheduled') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ids = messageIds.filter((id) => messagesById[id]?.repliesThreadInfo?.isComments
|
const global = getGlobal();
|
||||||
|
const ids = messageIds.filter((id) => selectThreadInfo(global, chatId, id)?.isCommentsInfo
|
||||||
|| messagesById[id]?.views !== undefined);
|
|| messagesById[id]?.views !== undefined);
|
||||||
|
|
||||||
if (!ids.length) return;
|
if (!ids.length) return;
|
||||||
@ -606,6 +613,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
getContainerHeight={getContainerHeight}
|
getContainerHeight={getContainerHeight}
|
||||||
isViewportNewest={Boolean(isViewportNewest)}
|
isViewportNewest={Boolean(isViewportNewest)}
|
||||||
isUnread={Boolean(firstUnreadId)}
|
isUnread={Boolean(firstUnreadId)}
|
||||||
|
isEmptyThread={isEmptyThread}
|
||||||
withUsers={withUsers}
|
withUsers={withUsers}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
@ -615,7 +623,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
type={type}
|
type={type}
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
threadTopMessageId={threadTopMessageId}
|
|
||||||
hasLinkedChat={hasLinkedChat}
|
hasLinkedChat={hasLinkedChat}
|
||||||
isSchedule={messageGroups ? type === 'scheduled' : false}
|
isSchedule={messageGroups ? type === 'scheduled' : false}
|
||||||
shouldRenderBotInfo={isBot}
|
shouldRenderBotInfo={isBot}
|
||||||
@ -642,12 +649,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const messagesById = type === 'scheduled'
|
const messagesById = type === 'scheduled'
|
||||||
? selectChatScheduledMessages(global, chatId)
|
? selectChatScheduledMessages(global, chatId)
|
||||||
: selectChatMessages(global, chatId);
|
: selectChatMessages(global, chatId);
|
||||||
const threadTopMessageId = selectThreadTopMessageId(global, chatId, threadId);
|
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
threadId !== MAIN_THREAD_ID && !chat?.isForum
|
threadId !== MAIN_THREAD_ID && !chat?.isForum
|
||||||
&& !(messagesById && threadTopMessageId && messagesById[threadTopMessageId])
|
&& !(messagesById && threadId && messagesById[threadId])
|
||||||
) {
|
) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -664,6 +669,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const topic = chat.topics?.[threadId];
|
const topic = chat.topics?.[threadId];
|
||||||
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
|
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
|
||||||
|
const isEmptyThread = !selectThreadInfo(global, chatId, threadId)?.messagesCount;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
@ -678,16 +684,16 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isBot: Boolean(chatBot),
|
isBot: Boolean(chatBot),
|
||||||
messageIds,
|
messageIds,
|
||||||
messagesById,
|
messagesById,
|
||||||
isComments: Boolean(threadInfo?.originChannelId),
|
|
||||||
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
||||||
isViewportNewest: type !== 'thread' || selectIsViewportNewest(global, chatId, threadId),
|
isViewportNewest: type !== 'thread' || selectIsViewportNewest(global, chatId, threadId),
|
||||||
focusingId,
|
focusingId,
|
||||||
isSelectModeActive: selectIsInSelectMode(global),
|
isSelectModeActive: selectIsInSelectMode(global),
|
||||||
threadTopMessageId,
|
|
||||||
hasLinkedChat: chatFullInfo ? Boolean(chatFullInfo.linkedChatId) : undefined,
|
hasLinkedChat: chatFullInfo ? Boolean(chatFullInfo.linkedChatId) : undefined,
|
||||||
topic,
|
topic,
|
||||||
noMessageSendingAnimation: !selectPerformanceSettingsValue(global, 'messageSendingAnimations'),
|
noMessageSendingAnimation: !selectPerformanceSettingsValue(global, 'messageSendingAnimations'),
|
||||||
isServiceNotificationsChat: chatId === SERVICE_NOTIFICATIONS_USER_ID,
|
isServiceNotificationsChat: chatId === SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
|
isForum: chat.isForum,
|
||||||
|
isEmptyThread,
|
||||||
...(withLastMessageWhenPreloading && { lastMessage }),
|
...(withLastMessageWhenPreloading && { lastMessage }),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -41,6 +41,7 @@ interface OwnProps {
|
|||||||
isUnread: boolean;
|
isUnread: boolean;
|
||||||
withUsers: boolean;
|
withUsers: boolean;
|
||||||
isChannelChat: boolean | undefined;
|
isChannelChat: boolean | undefined;
|
||||||
|
isEmptyThread?: boolean;
|
||||||
isComments?: boolean;
|
isComments?: boolean;
|
||||||
noAvatars: boolean;
|
noAvatars: boolean;
|
||||||
containerRef: RefObject<HTMLDivElement>;
|
containerRef: RefObject<HTMLDivElement>;
|
||||||
@ -49,7 +50,6 @@ interface OwnProps {
|
|||||||
memoFirstUnreadIdRef: { current: number | undefined };
|
memoFirstUnreadIdRef: { current: number | undefined };
|
||||||
type: MessageListType;
|
type: MessageListType;
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
threadTopMessageId: number | undefined;
|
|
||||||
hasLinkedChat: boolean | undefined;
|
hasLinkedChat: boolean | undefined;
|
||||||
isSchedule: boolean;
|
isSchedule: boolean;
|
||||||
shouldRenderBotInfo?: boolean;
|
shouldRenderBotInfo?: boolean;
|
||||||
@ -71,6 +71,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
isViewportNewest,
|
isViewportNewest,
|
||||||
isUnread,
|
isUnread,
|
||||||
isComments,
|
isComments,
|
||||||
|
isEmptyThread,
|
||||||
withUsers,
|
withUsers,
|
||||||
isChannelChat,
|
isChannelChat,
|
||||||
noAvatars,
|
noAvatars,
|
||||||
@ -80,7 +81,6 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
memoFirstUnreadIdRef,
|
memoFirstUnreadIdRef,
|
||||||
type,
|
type,
|
||||||
isReady,
|
isReady,
|
||||||
threadTopMessageId,
|
|
||||||
hasLinkedChat,
|
hasLinkedChat,
|
||||||
isSchedule,
|
isSchedule,
|
||||||
shouldRenderBotInfo,
|
shouldRenderBotInfo,
|
||||||
@ -193,6 +193,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const documentGroupId = !isMessageAlbum && message.groupedId ? message.groupedId : undefined;
|
const documentGroupId = !isMessageAlbum && message.groupedId ? message.groupedId : undefined;
|
||||||
const nextDocumentGroupId = nextMessage && !isAlbum(nextMessage) ? nextMessage.groupedId : undefined;
|
const nextDocumentGroupId = nextMessage && !isAlbum(nextMessage) ? nextMessage.groupedId : undefined;
|
||||||
|
const isTopicTopMessage = message.id === threadId;
|
||||||
|
|
||||||
const position = {
|
const position = {
|
||||||
isFirstInGroup: messageIndex === 0,
|
isFirstInGroup: messageIndex === 0,
|
||||||
@ -214,8 +215,6 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const noComments = hasLinkedChat === false || !isChannelChat;
|
const noComments = hasLinkedChat === false || !isChannelChat;
|
||||||
|
|
||||||
const isTopicTopMessage = message.id === threadTopMessageId;
|
|
||||||
|
|
||||||
return compact([
|
return compact([
|
||||||
message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
|
message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
|
||||||
<Message
|
<Message
|
||||||
@ -243,9 +242,11 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
onPinnedIntersectionChange={onPinnedIntersectionChange}
|
onPinnedIntersectionChange={onPinnedIntersectionChange}
|
||||||
getIsMessageListReady={getIsReady}
|
getIsMessageListReady={getIsReady}
|
||||||
/>,
|
/>,
|
||||||
message.id === threadTopMessageId && (
|
message.id === threadId && (
|
||||||
<div className="local-action-message" key="discussion-started">
|
<div className="local-action-message" key="discussion-started">
|
||||||
<span>{lang('DiscussionStarted')}</span>
|
<span>{lang(isEmptyThread
|
||||||
|
? (isComments ? 'NoComments' : 'NoReplies') : 'DiscussionStarted')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -53,7 +53,6 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadTopMessageId,
|
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildStyle from '../../util/buildStyle';
|
||||||
@ -103,6 +102,7 @@ interface OwnProps {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: number;
|
||||||
|
isComments?: boolean;
|
||||||
messageListType?: MessageListType;
|
messageListType?: MessageListType;
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
draftReplyInfo?: ApiInputMessageReplyInfo;
|
draftReplyInfo?: ApiInputMessageReplyInfo;
|
||||||
@ -157,6 +157,7 @@ function MiddleColumn({
|
|||||||
leftColumnRef,
|
leftColumnRef,
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
|
isComments,
|
||||||
messageListType,
|
messageListType,
|
||||||
isMobile,
|
isMobile,
|
||||||
chat,
|
chat,
|
||||||
@ -510,6 +511,7 @@ function MiddleColumn({
|
|||||||
chatId={renderingChatId!}
|
chatId={renderingChatId!}
|
||||||
threadId={renderingThreadId!}
|
threadId={renderingThreadId!}
|
||||||
messageListType={renderingMessageListType!}
|
messageListType={renderingMessageListType!}
|
||||||
|
isComments={isComments}
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
getCurrentPinnedIndexes={getCurrentPinnedIndexes}
|
getCurrentPinnedIndexes={getCurrentPinnedIndexes}
|
||||||
@ -528,6 +530,7 @@ function MiddleColumn({
|
|||||||
chatId={renderingChatId!}
|
chatId={renderingChatId!}
|
||||||
threadId={renderingThreadId!}
|
threadId={renderingThreadId!}
|
||||||
type={renderingMessageListType!}
|
type={renderingMessageListType!}
|
||||||
|
isComments={isComments}
|
||||||
canPost={renderingCanPost!}
|
canPost={renderingCanPost!}
|
||||||
hasTools={renderingHasTools}
|
hasTools={renderingHasTools}
|
||||||
onFabToggle={setIsFabShown}
|
onFabToggle={setIsFabShown}
|
||||||
@ -734,8 +737,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
||||||
|
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
const isComments = Boolean(threadInfo?.originChannelId);
|
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
const canPost = chat && getCanPostInChat(chat, threadId, isComments);
|
const canPost = chat && getCanPostInChat(chat, threadId, isMessageThread);
|
||||||
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
|
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
|
||||||
const isPinnedMessageList = messageListType === 'pinned';
|
const isPinnedMessageList = messageListType === 'pinned';
|
||||||
const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID;
|
const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID;
|
||||||
@ -761,7 +764,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const isCommentThread = threadId !== MAIN_THREAD_ID && !chat?.isForum;
|
const isCommentThread = threadId !== MAIN_THREAD_ID && !chat?.isForum;
|
||||||
const topMessageId = isCommentThread ? selectThreadTopMessageId(global, chatId, threadId) : undefined;
|
const topMessageId = isCommentThread ? threadId : undefined;
|
||||||
|
|
||||||
const canUnpin = chat && (
|
const canUnpin = chat && (
|
||||||
isPrivate || (
|
isPrivate || (
|
||||||
@ -779,6 +782,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
draftReplyInfo,
|
draftReplyInfo,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
areChatSettingsLoaded: Boolean(chat?.settings),
|
areChatSettingsLoaded: Boolean(chat?.settings),
|
||||||
|
isComments: isMessageThread,
|
||||||
canPost: !isPinnedMessageList
|
canPost: !isPinnedMessageList
|
||||||
&& (!chat || canPost)
|
&& (!chat || canPost)
|
||||||
&& !isBotNotStarted
|
&& !isBotNotStarted
|
||||||
|
|||||||
@ -47,7 +47,6 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadParam,
|
selectThreadParam,
|
||||||
selectThreadTopMessageId,
|
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import cycleRestrict from '../../util/cycleRestrict';
|
import cycleRestrict from '../../util/cycleRestrict';
|
||||||
@ -86,6 +85,7 @@ type OwnProps = {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: number;
|
||||||
messageListType: MessageListType;
|
messageListType: MessageListType;
|
||||||
|
isComments?: boolean;
|
||||||
isReady?: boolean;
|
isReady?: boolean;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
getCurrentPinnedIndexes: Signal<Record<string, number>>;
|
getCurrentPinnedIndexes: Signal<Record<string, number>>;
|
||||||
@ -105,7 +105,6 @@ type StateProps = {
|
|||||||
isRightColumnShown?: boolean;
|
isRightColumnShown?: boolean;
|
||||||
audioMessage?: ApiMessage;
|
audioMessage?: ApiMessage;
|
||||||
messagesCount?: number;
|
messagesCount?: number;
|
||||||
isComments?: boolean;
|
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
hasButtonInHeader?: boolean;
|
hasButtonInHeader?: boolean;
|
||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
@ -147,7 +146,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
onFocusPinnedMessage,
|
onFocusPinnedMessage,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChatWithInfo,
|
openThreadWithInfo,
|
||||||
pinMessage,
|
pinMessage,
|
||||||
focusMessage,
|
focusMessage,
|
||||||
openChat,
|
openChat,
|
||||||
@ -156,6 +155,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
toggleLeftColumn,
|
toggleLeftColumn,
|
||||||
exitMessageSelectMode,
|
exitMessageSelectMode,
|
||||||
openPremiumModal,
|
openPremiumModal,
|
||||||
|
openThread,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -197,7 +197,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
} = useFastClick((e: React.MouseEvent<HTMLDivElement | HTMLButtonElement>) => {
|
} = useFastClick((e: React.MouseEvent<HTMLDivElement | HTMLButtonElement>) => {
|
||||||
if (e.type === 'mousedown' && (e.target as Element).closest('.title > .custom-emoji')) return;
|
if (e.type === 'mousedown' && (e.target as Element).closest('.title > .custom-emoji')) return;
|
||||||
|
|
||||||
openChatWithInfo({ id: chatId, threadId });
|
openThreadWithInfo({ chatId, threadId });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleUnpinMessage = useLastCallback((messageId: number) => {
|
const handleUnpinMessage = useLastCallback((messageId: number) => {
|
||||||
@ -217,7 +217,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleAllPinnedClick = useLastCallback(() => {
|
const handleAllPinnedClick = useLastCallback(() => {
|
||||||
openChat({ id: chatId, threadId, type: 'pinned' });
|
openThread({ chatId, threadId, type: 'pinned' });
|
||||||
});
|
});
|
||||||
|
|
||||||
const setBackButtonActive = useLastCallback(() => {
|
const setBackButtonActive = useLastCallback(() => {
|
||||||
@ -354,7 +354,9 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
<h3>
|
<h3>
|
||||||
{messagesCount !== undefined ? (
|
{messagesCount !== undefined ? (
|
||||||
messageListType === 'thread' ? (
|
messageListType === 'thread' ? (
|
||||||
lang(isComments ? 'CommentsCount' : 'Replies', messagesCount, 'i'))
|
(messagesCount
|
||||||
|
? lang(isComments ? 'Comments' : 'Replies', messagesCount, 'i')
|
||||||
|
: lang(isComments ? 'CommentsTitle' : 'RepliesTitle')))
|
||||||
: messageListType === 'pinned' ? (lang('PinnedMessagesCount', messagesCount, 'i'))
|
: messageListType === 'pinned' ? (lang('PinnedMessagesCount', messagesCount, 'i'))
|
||||||
: messageListType === 'scheduled' ? (
|
: messageListType === 'scheduled' ? (
|
||||||
isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount, 'i')
|
isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount, 'i')
|
||||||
@ -560,10 +562,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (threadId !== MAIN_THREAD_ID && !chat?.isForum) {
|
if (threadId !== MAIN_THREAD_ID && !chat?.isForum) {
|
||||||
const pinnedMessageId = selectThreadTopMessageId(global, chatId, threadId);
|
const pinnedMessageId = threadId;
|
||||||
const message = pinnedMessageId ? selectChatMessage(global, chatId, pinnedMessageId) : undefined;
|
const message = pinnedMessageId ? selectChatMessage(global, chatId, pinnedMessageId) : undefined;
|
||||||
const topMessageSender = message ? selectForwardedSender(global, message) : undefined;
|
const topMessageSender = message ? selectForwardedSender(global, message) : undefined;
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -571,7 +572,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
messagesById,
|
messagesById,
|
||||||
canUnpin: false,
|
canUnpin: false,
|
||||||
topMessageSender,
|
topMessageSender,
|
||||||
isComments: Boolean(threadInfo?.originChannelId),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,9 @@ export function isAlbum(messageOrAlbum: ApiMessage | IAlbum): messageOrAlbum is
|
|||||||
return 'albumId' in messageOrAlbum;
|
return 'albumId' in messageOrAlbum;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function groupMessages(messages: ApiMessage[], firstUnreadId?: number, isChatWithSelf = false) {
|
export function groupMessages(
|
||||||
|
messages: ApiMessage[], firstUnreadId?: number, topMessageId?: number, isChatWithSelf?: boolean,
|
||||||
|
) {
|
||||||
let currentSenderGroup: SenderGroup = [];
|
let currentSenderGroup: SenderGroup = [];
|
||||||
let currentDateGroup = {
|
let currentDateGroup = {
|
||||||
originalDate: messages[0].date,
|
originalDate: messages[0].date,
|
||||||
@ -39,7 +41,7 @@ export function groupMessages(messages: ApiMessage[], firstUnreadId?: number, is
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
currentAlbum.messages.push(message);
|
currentAlbum.messages.push(message);
|
||||||
if (message.content.text) {
|
if (message.hasComments || (message.content.text && !currentAlbum.mainMessage.hasComments)) {
|
||||||
currentAlbum.mainMessage = message;
|
currentAlbum.mainMessage = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,6 +58,7 @@ export function groupMessages(messages: ApiMessage[], firstUnreadId?: number, is
|
|||||||
currentSenderGroup.push(currentAlbum);
|
currentSenderGroup.push(currentAlbum);
|
||||||
currentAlbum = undefined;
|
currentAlbum = undefined;
|
||||||
}
|
}
|
||||||
|
const lastSenderGroupItem = currentSenderGroup[currentSenderGroup.length - 1];
|
||||||
if (nextMessage) {
|
if (nextMessage) {
|
||||||
const nextMessageDayStartsAt = getDayStartAt(nextMessage.date * 1000);
|
const nextMessageDayStartsAt = getDayStartAt(nextMessage.date * 1000);
|
||||||
if (currentDateGroup.datetime !== nextMessageDayStartsAt) {
|
if (currentDateGroup.datetime !== nextMessageDayStartsAt) {
|
||||||
@ -77,6 +80,11 @@ export function groupMessages(messages: ApiMessage[], firstUnreadId?: number, is
|
|||||||
|| message.inlineButtons
|
|| message.inlineButtons
|
||||||
|| nextMessage.inlineButtons
|
|| nextMessage.inlineButtons
|
||||||
|| (nextMessage.date - message.date) > GROUP_INTERVAL_SECONDS
|
|| (nextMessage.date - message.date) > GROUP_INTERVAL_SECONDS
|
||||||
|
|| (topMessageId
|
||||||
|
&& (message.id === topMessageId
|
||||||
|
|| (lastSenderGroupItem
|
||||||
|
&& 'mainMessage' in lastSenderGroupItem && lastSenderGroupItem.mainMessage?.id === topMessageId))
|
||||||
|
&& nextMessage.id !== topMessageId)
|
||||||
|| (isChatWithSelf && message.forwardInfo?.senderUserId !== nextMessage.forwardInfo?.senderUserId)
|
|| (isChatWithSelf && message.forwardInfo?.senderUserId !== nextMessage.forwardInfo?.senderUserId)
|
||||||
) {
|
) {
|
||||||
currentSenderGroup = [];
|
currentSenderGroup = [];
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import type { Signal } from '../../../util/signals';
|
|||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
import { requestMeasure } from '../../../lib/fasterdom/fasterdom';
|
import { requestMeasure } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { isLocalMessageId } from '../../../global/helpers';
|
|
||||||
import { debounce } from '../../../util/schedulers';
|
import { debounce } from '../../../util/schedulers';
|
||||||
import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/windowEnvironment';
|
import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/windowEnvironment';
|
||||||
|
|
||||||
@ -92,12 +91,6 @@ export default function useScrollHooks(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading history while sending a message can return the same message and cause ambiguity
|
|
||||||
const isFirstMessageLocal = isLocalMessageId(messageIds[0]);
|
|
||||||
if (isFirstMessageLocal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.forEach(({ isIntersecting, target }) => {
|
entries.forEach(({ isIntersecting, target }) => {
|
||||||
if (!isIntersecting) return;
|
if (!isIntersecting) return;
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,11 @@
|
|||||||
transition: background-color 0.15s, color 0.15s;
|
transition: background-color 0.15s, color 0.15s;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.Message .has-appendix &::before {
|
.Message .has-appendix &::before {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
@ -45,7 +50,7 @@
|
|||||||
bottom: 3rem;
|
bottom: 3rem;
|
||||||
height: 3.375rem;
|
height: 3.375rem;
|
||||||
border-radius: 1.375rem;
|
border-radius: 1.375rem;
|
||||||
padding: 0.375rem 0.3125rem 0.25rem;
|
padding: 0.375rem;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
color: white;
|
color: white;
|
||||||
background-color: var(--pattern-color);
|
background-color: var(--pattern-color);
|
||||||
@ -66,7 +71,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Message:hover & {
|
.Message:hover &, &.loading {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +102,7 @@
|
|||||||
.recent-repliers,
|
.recent-repliers,
|
||||||
.icon-comments,
|
.icon-comments,
|
||||||
.label,
|
.label,
|
||||||
.icon-next {
|
.CommentButton_icon-open {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,8 +158,8 @@
|
|||||||
margin-inline-end: 0.875rem;
|
margin-inline-end: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-next {
|
.CommentButton_icon-open {
|
||||||
margin-inline-start: auto;
|
position: absolute;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,3 +215,34 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CommentButton_loading, .CommentButton_icon-open, .CommentButton_icon-comments {
|
||||||
|
transition: transform 250ms ease-in-out, opacity 250ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CommentButton_icon-open {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CommentButton_loading {
|
||||||
|
position: absolute;
|
||||||
|
--spinner-size: 1.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
right: 0.5rem;
|
||||||
|
|
||||||
|
.CommentButton-custom-shape & {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CommentButton_right {
|
||||||
|
position: relative;
|
||||||
|
margin-inline-start: auto;
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CommentButton_hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.4);
|
||||||
|
}
|
||||||
|
|||||||
@ -2,9 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal } from '../../../global';
|
import { getActions, getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type { ApiCommentsInfo } from '../../../api/types';
|
||||||
ApiThreadInfo,
|
|
||||||
} from '../../../api/types';
|
|
||||||
|
|
||||||
import { selectPeer } from '../../../global/selectors';
|
import { selectPeer } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
@ -12,30 +10,42 @@ import { formatIntegerCompact } from '../../../util/textFormat';
|
|||||||
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||||
|
|
||||||
import AnimatedCounter from '../../common/AnimatedCounter';
|
import AnimatedCounter from '../../common/AnimatedCounter';
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
|
import Spinner from '../../ui/Spinner';
|
||||||
|
|
||||||
import './CommentButton.scss';
|
import './CommentButton.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
threadInfo: ApiThreadInfo;
|
threadInfo: ApiCommentsInfo;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
isCustomShape?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SHOW_LOADER_DELAY = 450;
|
||||||
|
|
||||||
const CommentButton: FC<OwnProps> = ({
|
const CommentButton: FC<OwnProps> = ({
|
||||||
|
isCustomShape,
|
||||||
threadInfo,
|
threadInfo,
|
||||||
disabled,
|
disabled,
|
||||||
|
isLoading,
|
||||||
}) => {
|
}) => {
|
||||||
const { openComments } = getActions();
|
const { openThread } = getActions();
|
||||||
|
|
||||||
|
const shouldRenderLoading = useAsyncRendering([isLoading], SHOW_LOADER_DELAY);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const {
|
const {
|
||||||
threadId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, originChannelId,
|
originMessageId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, originChannelId,
|
||||||
} = threadInfo;
|
} = threadInfo;
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
openComments({ id: chatId, threadId, originChannelId });
|
openThread({
|
||||||
|
isComments: true, chatId, originMessageId, originChannelId,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const recentRepliers = useMemo(() => {
|
const recentRepliers = useMemo(() => {
|
||||||
@ -73,7 +83,7 @@ const CommentButton: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const hasUnread = Boolean(lastReadInboxMessageId && lastMessageId && lastReadInboxMessageId < lastMessageId);
|
const hasUnread = Boolean(lastReadInboxMessageId && lastMessageId && lastReadInboxMessageId < lastMessageId);
|
||||||
|
|
||||||
const commentsText = messagesCount ? (lang('Comments', '%COMMENTS_COUNT%', undefined, messagesCount) as string)
|
const commentsText = messagesCount ? (lang('CommentsCount', '%COMMENTS_COUNT%', undefined, messagesCount) as string)
|
||||||
.split('%')
|
.split('%')
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
return (s === 'COMMENTS_COUNT' ? <AnimatedCounter text={formatIntegerCompact(messagesCount)} /> : s);
|
return (s === 'COMMENTS_COUNT' ? <AnimatedCounter text={formatIntegerCompact(messagesCount)} /> : s);
|
||||||
@ -83,17 +93,48 @@ const CommentButton: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-cnt={formatIntegerCompact(messagesCount)}
|
data-cnt={formatIntegerCompact(messagesCount)}
|
||||||
className={buildClassName('CommentButton', hasUnread && 'has-unread', disabled && 'disabled')}
|
className={buildClassName(
|
||||||
|
'CommentButton',
|
||||||
|
hasUnread && 'has-unread',
|
||||||
|
disabled && 'disabled',
|
||||||
|
isCustomShape && 'CommentButton-custom-shape',
|
||||||
|
isLoading && 'loading',
|
||||||
|
)}
|
||||||
dir={lang.isRtl ? 'rtl' : 'ltr'}
|
dir={lang.isRtl ? 'rtl' : 'ltr'}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<i className="icon icon-comments-sticker" />
|
<i
|
||||||
{(!recentRepliers || recentRepliers.length === 0) && <i className="icon icon-comments" />}
|
className={buildClassName(
|
||||||
|
'CommentButton_icon-comments icon icon-comments-sticker',
|
||||||
|
isLoading && shouldRenderLoading && 'CommentButton_hidden',
|
||||||
|
)}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
{!recentRepliers?.length && <i className="icon icon-comments" aria-hidden />}
|
||||||
{renderRecentRepliers()}
|
{renderRecentRepliers()}
|
||||||
<div className="label" dir="auto">
|
<div className="label" dir="auto">
|
||||||
{messagesCount ? commentsText : lang('LeaveAComment')}
|
{messagesCount ? commentsText : lang('LeaveAComment')}
|
||||||
</div>
|
</div>
|
||||||
<i className="icon icon-next" />
|
<div className="CommentButton_right">
|
||||||
|
{isLoading && (
|
||||||
|
<Spinner
|
||||||
|
className={buildClassName(
|
||||||
|
'CommentButton_loading',
|
||||||
|
!shouldRenderLoading && 'CommentButton_hidden',
|
||||||
|
)}
|
||||||
|
color={isCustomShape ? 'white' : 'blue'}
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
<i
|
||||||
|
className={buildClassName(
|
||||||
|
'CommentButton_icon-open icon icon-next',
|
||||||
|
isLoading && shouldRenderLoading && 'CommentButton_hidden',
|
||||||
|
)}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -166,7 +166,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openThread,
|
||||||
updateDraftReplyInfo,
|
updateDraftReplyInfo,
|
||||||
setEditingId,
|
setEditingId,
|
||||||
pinMessage,
|
pinMessage,
|
||||||
@ -301,8 +301,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenThread = useLastCallback(() => {
|
const handleOpenThread = useLastCallback(() => {
|
||||||
openChat({
|
openThread({
|
||||||
id: message.chatId,
|
chatId: message.chatId,
|
||||||
threadId: message.id,
|
threadId: message.id,
|
||||||
});
|
});
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
|||||||
@ -714,6 +714,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-action-button-shown {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&.own .message-action-button {
|
&.own .message-action-button {
|
||||||
left: -3rem;
|
left: -3rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,6 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadTopMessageId,
|
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
selectUploadProgress,
|
selectUploadProgress,
|
||||||
selectUser,
|
selectUser,
|
||||||
@ -271,6 +270,7 @@ type StateProps = {
|
|||||||
withStickerEffects?: boolean;
|
withStickerEffects?: boolean;
|
||||||
webPageStory?: ApiTypeStory;
|
webPageStory?: ApiTypeStory;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
|
isLoadingComments?: boolean;
|
||||||
shouldWarnAboutSvg?: boolean;
|
shouldWarnAboutSvg?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -334,6 +334,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
outgoingStatus,
|
outgoingStatus,
|
||||||
uploadProgress,
|
uploadProgress,
|
||||||
isInDocumentGroup,
|
isInDocumentGroup,
|
||||||
|
isLoadingComments,
|
||||||
isProtected,
|
isProtected,
|
||||||
isChatProtected,
|
isChatProtected,
|
||||||
isFocused,
|
isFocused,
|
||||||
@ -663,7 +664,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
&& !isInDocumentGroupNotLast
|
&& !isInDocumentGroupNotLast
|
||||||
&& messageListType === 'thread'
|
&& messageListType === 'thread'
|
||||||
&& !noComments;
|
&& !noComments;
|
||||||
const withCommentButton = repliesThreadInfo && !isInDocumentGroupNotLast && messageListType === 'thread'
|
const withCommentButton = repliesThreadInfo?.isCommentsInfo
|
||||||
|
&& !isInDocumentGroupNotLast && messageListType === 'thread'
|
||||||
&& !noComments;
|
&& !noComments;
|
||||||
const withQuickReactionButton = !isTouchScreen && !phoneCall && !isInSelectMode && defaultReaction
|
const withQuickReactionButton = !isTouchScreen && !phoneCall && !isInSelectMode && defaultReaction
|
||||||
&& !isInDocumentGroupNotLast && !isStoryMention;
|
&& !isInDocumentGroupNotLast && !isStoryMention;
|
||||||
@ -719,7 +721,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
replyToMsgId,
|
replyToMsgId,
|
||||||
replyMessage,
|
replyMessage,
|
||||||
message.id,
|
message.id,
|
||||||
isQuote || isReplyPrivate,
|
shouldHideReply || isQuote || isReplyPrivate,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEnsureStory(
|
useEnsureStory(
|
||||||
@ -1370,7 +1372,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{!isInDocumentGroupNotLast && metaPosition === 'standalone' && !isStoryMention && renderReactionsAndMeta()}
|
{!isInDocumentGroupNotLast && metaPosition === 'standalone' && !isStoryMention && renderReactionsAndMeta()}
|
||||||
{canShowActionButton && canForward ? (
|
{canShowActionButton && canForward ? (
|
||||||
<Button
|
<Button
|
||||||
className="message-action-button"
|
className={buildClassName(
|
||||||
|
'message-action-button', isLoadingComments && 'message-action-button-shown',
|
||||||
|
)}
|
||||||
color="translucent-white"
|
color="translucent-white"
|
||||||
round
|
round
|
||||||
size="tiny"
|
size="tiny"
|
||||||
@ -1381,7 +1385,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
) : canShowActionButton && canFocus ? (
|
) : canShowActionButton && canFocus ? (
|
||||||
<Button
|
<Button
|
||||||
className="message-action-button"
|
className={buildClassName(
|
||||||
|
'message-action-button', isLoadingComments && 'message-action-button-shown',
|
||||||
|
)}
|
||||||
color="translucent-white"
|
color="translucent-white"
|
||||||
round
|
round
|
||||||
size="tiny"
|
size="tiny"
|
||||||
@ -1391,7 +1397,14 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
<i className="icon icon-arrow-right" />
|
<i className="icon icon-arrow-right" />
|
||||||
</Button>
|
</Button>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{withCommentButton && <CommentButton threadInfo={repliesThreadInfo!} disabled={noComments} />}
|
{withCommentButton && (
|
||||||
|
<CommentButton
|
||||||
|
threadInfo={repliesThreadInfo}
|
||||||
|
disabled={noComments}
|
||||||
|
isLoading={isLoadingComments}
|
||||||
|
isCustomShape={isCustomShape}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{withAppendix && <MessageAppendix isOwn={isOwn} />}
|
{withAppendix && <MessageAppendix isOwn={isOwn} />}
|
||||||
{withQuickReactionButton && quickReactionPosition === 'in-content' && renderQuickReactionButton()}
|
{withQuickReactionButton && quickReactionPosition === 'in-content' && renderQuickReactionButton()}
|
||||||
</div>
|
</div>
|
||||||
@ -1430,13 +1443,14 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, ownProps): StateProps => {
|
(global, ownProps): StateProps => {
|
||||||
const {
|
const {
|
||||||
focusedMessage, forwardMessages, activeEmojiInteractions, activeReactions,
|
focusedMessage, forwardMessages, activeReactions, activeEmojiInteractions,
|
||||||
|
loadingThread,
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
const {
|
const {
|
||||||
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup,
|
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup,
|
||||||
} = ownProps;
|
} = ownProps;
|
||||||
const {
|
const {
|
||||||
id, chatId, viaBotId, isOutgoing, forwardInfo, transcriptionId, isPinned, repliesThreadInfo,
|
id, chatId, viaBotId, isOutgoing, forwardInfo, transcriptionId, isPinned,
|
||||||
} = message;
|
} = message;
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
@ -1460,16 +1474,13 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
? chatFullInfo?.adminMembersById?.[sender?.id]
|
? chatFullInfo?.adminMembersById?.[sender?.id]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const threadTopMessageId = threadId ? selectThreadTopMessageId(global, chatId, threadId) : undefined;
|
const isThreadTop = message.id === threadId;
|
||||||
const isThreadTop = message.id === threadTopMessageId;
|
|
||||||
|
|
||||||
const { replyToMsgId, replyToPeerId, replyFrom } = getMessageReplyInfo(message) || {};
|
const { replyToMsgId, replyToPeerId, replyFrom } = getMessageReplyInfo(message) || {};
|
||||||
const { userId: storyReplyUserId, storyId: storyReplyId } = getStoryReplyInfo(message) || {};
|
const { userId: storyReplyUserId, storyId: storyReplyId } = getStoryReplyInfo(message) || {};
|
||||||
|
|
||||||
const shouldHideReply = replyToMsgId && replyToMsgId === threadTopMessageId;
|
const shouldHideReply = replyToMsgId && replyToMsgId === threadId;
|
||||||
const replyMessage = replyToMsgId && !shouldHideReply
|
const replyMessage = replyToMsgId ? selectChatMessage(global, replyToPeerId || chatId, replyToMsgId) : undefined;
|
||||||
? selectChatMessage(global, replyToPeerId || chatId, replyToMsgId)
|
|
||||||
: undefined;
|
|
||||||
const forwardHeader = forwardInfo || replyFrom;
|
const forwardHeader = forwardInfo || replyFrom;
|
||||||
const replyMessageSender = replyMessage ? selectReplySender(global, replyMessage) : forwardHeader && !isRepliesChat
|
const replyMessageSender = replyMessage ? selectReplySender(global, replyMessage) : forwardHeader && !isRepliesChat
|
||||||
? selectSenderFromHeader(global, forwardHeader) : undefined;
|
? selectSenderFromHeader(global, forwardHeader) : undefined;
|
||||||
@ -1509,9 +1520,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const { canReply } = (messageListType === 'thread' && selectAllowedMessageActions(global, message, threadId)) || {};
|
const { canReply } = (messageListType === 'thread' && selectAllowedMessageActions(global, message, threadId)) || {};
|
||||||
const isDownloading = selectIsDownloading(global, message);
|
const isDownloading = selectIsDownloading(global, message);
|
||||||
const actualRepliesThreadInfo = repliesThreadInfo
|
|
||||||
? selectThreadInfo(global, repliesThreadInfo.chatId, repliesThreadInfo.threadId) || repliesThreadInfo
|
const repliesThreadInfo = selectThreadInfo(global, chatId, album?.mainMessage.id || id);
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const isInDocumentGroup = Boolean(message.groupedId) && !message.isInAlbum;
|
const isInDocumentGroup = Boolean(message.groupedId) && !message.isInAlbum;
|
||||||
const documentGroupFirstMessageId = isInDocumentGroup
|
const documentGroupFirstMessageId = isInDocumentGroup
|
||||||
@ -1584,7 +1594,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canAutoPlayMedia: selectCanAutoPlayMedia(global, message),
|
canAutoPlayMedia: selectCanAutoPlayMedia(global, message),
|
||||||
autoLoadFileMaxSizeMb: global.settings.byKey.autoLoadFileMaxSizeMb,
|
autoLoadFileMaxSizeMb: global.settings.byKey.autoLoadFileMaxSizeMb,
|
||||||
shouldLoopStickers: selectShouldLoopStickers(global),
|
shouldLoopStickers: selectShouldLoopStickers(global),
|
||||||
repliesThreadInfo: actualRepliesThreadInfo,
|
repliesThreadInfo,
|
||||||
availableReactions: global.availableReactions,
|
availableReactions: global.availableReactions,
|
||||||
defaultReaction: isMessageLocal(message) || messageListType === 'scheduled'
|
defaultReaction: isMessageLocal(message) || messageListType === 'scheduled'
|
||||||
? undefined : selectDefaultReaction(global, chatId),
|
? undefined : selectDefaultReaction(global, chatId),
|
||||||
@ -1606,6 +1616,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'),
|
withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'),
|
||||||
webPageStory,
|
webPageStory,
|
||||||
isConnected,
|
isConnected,
|
||||||
|
isLoadingComments: repliesThreadInfo?.isCommentsInfo
|
||||||
|
&& loadingThread?.loadingChatId === repliesThreadInfo?.originChannelId
|
||||||
|
&& loadingThread?.loadingMessageId === repliesThreadInfo?.originMessageId,
|
||||||
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
||||||
...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }),
|
...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }),
|
||||||
...(typeof uploadProgress === 'number' && { uploadProgress }),
|
...(typeof uploadProgress === 'number' && { uploadProgress }),
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export default function useInnerHandlers(
|
|||||||
const {
|
const {
|
||||||
openChat, showNotification, focusMessage, openMediaViewer, openAudioPlayer,
|
openChat, showNotification, focusMessage, openMediaViewer, openAudioPlayer,
|
||||||
markMessagesRead, cancelSendingMessage, sendPollVote, openForwardMenu,
|
markMessagesRead, cancelSendingMessage, sendPollVote, openForwardMenu,
|
||||||
openChatLanguageModal, openStoryViewer, focusMessageInComments,
|
openChatLanguageModal, openThread, openStoryViewer,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -156,7 +156,7 @@ export default function useInnerHandlers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (replyToPeerId && replyToTopId) {
|
if (replyToPeerId && replyToTopId) {
|
||||||
focusMessageInComments({
|
focusMessage({
|
||||||
chatId: replyToPeerId,
|
chatId: replyToPeerId,
|
||||||
threadId: replyToTopId,
|
threadId: replyToTopId,
|
||||||
messageId: forwardInfo!.fromMessageId!,
|
messageId: forwardInfo!.fromMessageId!,
|
||||||
@ -181,8 +181,8 @@ export default function useInnerHandlers(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenThread = useLastCallback(() => {
|
const handleOpenThread = useLastCallback(() => {
|
||||||
openChat({
|
openThread({
|
||||||
id: message.chatId,
|
chatId: message.chatId,
|
||||||
threadId: message.id,
|
threadId: message.id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -169,8 +169,8 @@ export default memo(withGlobal(
|
|||||||
const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined;
|
const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined;
|
||||||
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
|
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
|
||||||
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
||||||
const isComments = Boolean(threadInfo?.originChannelId);
|
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
const canPostInChat = Boolean(chat) && Boolean(threadId) && getCanPostInChat(chat, threadId, isComments);
|
const canPostInChat = Boolean(chat) && Boolean(threadId) && getCanPostInChat(chat, threadId, isMessageThread);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query,
|
query,
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import type {
|
|
||||||
ApiChat, ApiChatType, ApiContact, ApiInputMessageReplyInfo, ApiPeer, ApiUrlAuthResult,
|
|
||||||
} from '../../../api/types';
|
|
||||||
import type { InlineBotSettings } from '../../../types';
|
import type { InlineBotSettings } from '../../../types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
||||||
|
import {
|
||||||
|
type ApiChat, type ApiChatType, type ApiContact, type ApiInputMessageReplyInfo, type ApiPeer, type ApiUrlAuthResult,
|
||||||
|
MAIN_THREAD_ID,
|
||||||
|
} from '../../../api/types';
|
||||||
|
|
||||||
import { GENERAL_REFETCH_INTERVAL } from '../../../config';
|
import { GENERAL_REFETCH_INTERVAL } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
@ -793,8 +794,8 @@ addActionHandler('callAttachBot', (global, actions, payload): ActionReturnType =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ('chatId' in payload) {
|
if ('chatId' in payload) {
|
||||||
const { chatId, threadId, url } = payload;
|
const { chatId, threadId = MAIN_THREAD_ID, url } = payload;
|
||||||
actions.openChat({ id: chatId, threadId, tabId });
|
actions.openThread({ chatId, threadId, tabId });
|
||||||
actions.requestWebView({
|
actions.requestWebView({
|
||||||
url,
|
url,
|
||||||
peerId: chatId!,
|
peerId: chatId!,
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import {
|
|||||||
import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink';
|
import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { getOrderedIds } from '../../../util/folderManager';
|
import { getOrderedIds } from '../../../util/folderManager';
|
||||||
import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
import { buildCollectionByKey, omit, pick } from '../../../util/iteratees';
|
||||||
import * as langProvider from '../../../util/langProvider';
|
import * as langProvider from '../../../util/langProvider';
|
||||||
import { debounce, pause, throttle } from '../../../util/schedulers';
|
import { debounce, pause, throttle } from '../../../util/schedulers';
|
||||||
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||||
@ -70,6 +70,7 @@ import {
|
|||||||
updateListedTopicIds,
|
updateListedTopicIds,
|
||||||
updateManagementProgress,
|
updateManagementProgress,
|
||||||
updatePeerFullInfo,
|
updatePeerFullInfo,
|
||||||
|
updateThread,
|
||||||
updateThreadInfo,
|
updateThreadInfo,
|
||||||
updateTopic,
|
updateTopic,
|
||||||
updateTopics,
|
updateTopics,
|
||||||
@ -78,13 +79,24 @@ import {
|
|||||||
import { updateGroupCall } from '../../reducers/calls';
|
import { updateGroupCall } from '../../reducers/calls';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectChat, selectChatByUsername,
|
selectChat,
|
||||||
selectChatFolder, selectChatFullInfo, selectChatListType, selectCurrentChat, selectCurrentMessageList, selectDraft,
|
selectChatByUsername,
|
||||||
|
selectChatFolder,
|
||||||
|
selectChatFullInfo,
|
||||||
|
selectChatListType,
|
||||||
|
selectCurrentChat,
|
||||||
|
selectCurrentMessageList,
|
||||||
|
selectDraft,
|
||||||
selectIsChatPinned,
|
selectIsChatPinned,
|
||||||
selectLastServiceNotification,
|
selectLastServiceNotification,
|
||||||
selectStickerSet,
|
selectStickerSet,
|
||||||
selectSupportChat, selectTabState, selectThread, selectThreadInfo, selectThreadOriginChat, selectThreadTopMessageId,
|
selectSupportChat,
|
||||||
selectUser, selectUserByPhoneNumber, selectVisibleUsers,
|
selectTabState,
|
||||||
|
selectThread,
|
||||||
|
selectThreadInfo,
|
||||||
|
selectUser,
|
||||||
|
selectUserByPhoneNumber,
|
||||||
|
selectVisibleUsers,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { selectGroupCall } from '../../selectors/calls';
|
import { selectGroupCall } from '../../selectors/calls';
|
||||||
import { selectCurrentLimit } from '../../selectors/limits';
|
import { selectCurrentLimit } from '../../selectors/limits';
|
||||||
@ -132,16 +144,19 @@ addActionHandler('preloadTopChatMessages', async (global, actions): Promise<void
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
function abortChatRequests(chatId: string, threadId?: number) {
|
||||||
const {
|
callApi('abortChatRequests', { chatId, threadId });
|
||||||
id, threadId = MAIN_THREAD_ID, noRequestThreadInfoUpdate, tabId = getCurrentTabId(),
|
}
|
||||||
} = payload;
|
|
||||||
|
|
||||||
|
function abortChatRequestsForCurrentChat<T extends GlobalState>(
|
||||||
|
global: T, newChatId?: string, newThreadId?: number,
|
||||||
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
) {
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
const currentChatId = currentMessageList?.chatId;
|
const currentChatId = currentMessageList?.chatId;
|
||||||
const currentThreadId = currentMessageList?.threadId;
|
const currentThreadId = currentMessageList?.threadId;
|
||||||
|
|
||||||
if (currentChatId && (currentChatId !== id || currentThreadId !== threadId)) {
|
if (currentChatId && (currentChatId !== newChatId || currentThreadId !== newThreadId)) {
|
||||||
const [isChatOpened, isThreadOpened] = Object.values(global.byTabId)
|
const [isChatOpened, isThreadOpened] = Object.values(global.byTabId)
|
||||||
.reduce(([accHasChatOpened, accHasThreadOpened], { id: otherTabId }) => {
|
.reduce(([accHasChatOpened, accHasThreadOpened], { id: otherTabId }) => {
|
||||||
if (otherTabId === tabId || (accHasChatOpened && accHasThreadOpened)) {
|
if (otherTabId === tabId || (accHasChatOpened && accHasThreadOpened)) {
|
||||||
@ -153,14 +168,33 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
|||||||
const isSameThread = isSameChat && otherMessageList?.threadId === currentThreadId;
|
const isSameThread = isSameChat && otherMessageList?.threadId === currentThreadId;
|
||||||
|
|
||||||
return [accHasChatOpened || isSameChat, accHasThreadOpened || isSameThread];
|
return [accHasChatOpened || isSameChat, accHasThreadOpened || isSameThread];
|
||||||
}, [currentChatId === id, false]);
|
}, [currentChatId === newChatId, false]);
|
||||||
|
|
||||||
const shouldAbortChatRequests = !isChatOpened || !isThreadOpened;
|
const shouldAbortChatRequests = !isChatOpened || !isThreadOpened;
|
||||||
|
|
||||||
if (shouldAbortChatRequests) {
|
if (shouldAbortChatRequests) {
|
||||||
callApi('abortChatRequests', { chatId: currentChatId, threadId: isChatOpened ? currentThreadId : undefined });
|
abortChatRequests(currentChatId, isChatOpened ? currentThreadId : undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
id, type, noForumTopicPanel, shouldReplaceHistory, shouldReplaceLast,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
actions.processOpenChatOrThread({
|
||||||
|
chatId: id,
|
||||||
|
type,
|
||||||
|
threadId: MAIN_THREAD_ID,
|
||||||
|
noForumTopicPanel,
|
||||||
|
shouldReplaceHistory,
|
||||||
|
shouldReplaceLast,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
|
||||||
|
abortChatRequestsForCurrentChat(global, id, MAIN_THREAD_ID, tabId);
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
@ -186,54 +220,227 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
|||||||
actions.requestChatUpdate({ chatId: id });
|
actions.requestChatUpdate({ chatId: id });
|
||||||
}
|
}
|
||||||
actions.closeStoryViewer({ tabId });
|
actions.closeStoryViewer({ tabId });
|
||||||
|
|
||||||
if (threadId !== MAIN_THREAD_ID && !noRequestThreadInfoUpdate) {
|
|
||||||
actions.requestThreadInfoUpdate({ chatId: id, threadId });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openComments', async (global, actions, payload): Promise<void> => {
|
addActionHandler('openThread', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
id, threadId, originChannelId, tabId = getCurrentTabId(),
|
type, isComments, noForumTopicPanel, shouldReplaceHistory, shouldReplaceLast,
|
||||||
|
focusMessageId,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
|
let { chatId } = payload;
|
||||||
|
let threadId: number | undefined;
|
||||||
|
let loadingChatId: string;
|
||||||
|
let loadingThreadId: number;
|
||||||
|
|
||||||
if (threadId !== MAIN_THREAD_ID) {
|
if (!isComments) {
|
||||||
const topMessageId = selectThreadTopMessageId(global, id, threadId);
|
loadingChatId = payload.chatId;
|
||||||
if (!topMessageId) {
|
threadId = payload.threadId;
|
||||||
const chat = selectThreadOriginChat(global, id, threadId);
|
loadingThreadId = threadId;
|
||||||
if (!chat) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const originalChat = selectChat(global, loadingChatId);
|
||||||
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
actions.openChat({
|
actions.openChat({
|
||||||
id, threadId, tabId, noRequestThreadInfoUpdate: true,
|
id: chatId,
|
||||||
|
type,
|
||||||
|
noForumTopicPanel,
|
||||||
|
shouldReplaceHistory,
|
||||||
|
shouldReplaceLast,
|
||||||
|
tabId,
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
} else if (originalChat?.isForum) {
|
||||||
|
actions.processOpenChatOrThread({
|
||||||
|
chatId,
|
||||||
|
type,
|
||||||
|
threadId,
|
||||||
|
isComments,
|
||||||
|
noForumTopicPanel,
|
||||||
|
shouldReplaceHistory,
|
||||||
|
shouldReplaceLast,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { originChannelId, originMessageId } = payload;
|
||||||
|
|
||||||
const result = await callApi('requestThreadInfoUpdate', { chat, threadId, originChannelId });
|
loadingChatId = originChannelId;
|
||||||
if (!result) {
|
loadingThreadId = originMessageId;
|
||||||
actions.openPreviousChat({ tabId });
|
}
|
||||||
return;
|
|
||||||
}
|
const chat = selectChat(global, loadingChatId);
|
||||||
|
const threadInfo = selectThreadInfo(global, loadingChatId, loadingThreadId);
|
||||||
|
const thread = selectThread(global, loadingChatId, loadingThreadId);
|
||||||
|
if (!chat) return;
|
||||||
|
|
||||||
|
abortChatRequestsForCurrentChat(global, loadingChatId, loadingThreadId, tabId);
|
||||||
|
|
||||||
|
if (chatId
|
||||||
|
&& threadInfo?.threadId
|
||||||
|
&& (isComments || (thread?.listedIds?.length && thread.listedIds.includes(threadInfo.threadId)))) {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
loadingThread: undefined,
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
actions.processOpenChatOrThread({
|
||||||
|
chatId,
|
||||||
|
type,
|
||||||
|
threadId: threadInfo.threadId,
|
||||||
|
isComments,
|
||||||
|
noForumTopicPanel,
|
||||||
|
shouldReplaceHistory,
|
||||||
|
shouldReplaceLast,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { loadingThread } = selectTabState(global, tabId);
|
||||||
|
if (loadingThread) {
|
||||||
|
abortChatRequests(loadingThread.loadingChatId, loadingThread.loadingMessageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
loadingThread: {
|
||||||
|
loadingChatId,
|
||||||
|
loadingMessageId: loadingThreadId,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const openPreviousChat = () => {
|
||||||
|
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
||||||
|
const currentGlobal = getGlobal();
|
||||||
|
if (isComments
|
||||||
|
|| selectCurrentMessageList(currentGlobal, tabId)?.chatId !== loadingChatId
|
||||||
|
|| selectCurrentMessageList(currentGlobal, tabId)?.threadId !== loadingThreadId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
actions.openPreviousChat({ tabId });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isComments) {
|
||||||
|
actions.processOpenChatOrThread({
|
||||||
|
chatId,
|
||||||
|
type,
|
||||||
|
threadId: threadId!,
|
||||||
|
tabId,
|
||||||
|
isComments,
|
||||||
|
noForumTopicPanel,
|
||||||
|
shouldReplaceHistory,
|
||||||
|
shouldReplaceLast,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await callApi('fetchDiscussionMessage', {
|
||||||
|
chat: selectChat(global, loadingChatId)!,
|
||||||
|
messageId: loadingThreadId,
|
||||||
|
});
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
loadingThread = selectTabState(global, tabId).loadingThread;
|
||||||
|
if (loadingThread?.loadingChatId !== loadingChatId || loadingThread?.loadingMessageId !== loadingThreadId) {
|
||||||
|
openPreviousChat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
loadingThread: undefined,
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
actions.showNotification({
|
||||||
|
message: langProvider.translate(isComments ? 'ChannelPostDeleted' : 'lng_message_not_found'),
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
|
||||||
|
openPreviousChat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
threadId ??= result.threadId;
|
||||||
|
chatId ??= result.chatId;
|
||||||
|
|
||||||
|
if (!chatId) {
|
||||||
|
openPreviousChat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
global = addChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
|
global = addMessages(global, result.messages);
|
||||||
|
if (isComments) {
|
||||||
|
global = updateThreadInfo(global, loadingChatId, loadingThreadId, {
|
||||||
|
threadId,
|
||||||
|
});
|
||||||
|
|
||||||
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
|
isCommentsInfo: false,
|
||||||
|
threadId,
|
||||||
|
chatId,
|
||||||
|
fromChannelId: loadingChatId,
|
||||||
|
fromMessageId: loadingThreadId,
|
||||||
|
...(threadInfo
|
||||||
|
&& pick(threadInfo, ['messagesCount', 'lastMessageId', 'lastReadInboxMessageId', 'recentReplierIds'])),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
global = updateThread(global, chatId, threadId, {
|
||||||
|
firstMessageId: result.firstMessageId,
|
||||||
|
});
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
if (focusMessageId) {
|
||||||
|
actions.focusMessage({
|
||||||
|
chatId,
|
||||||
|
threadId: threadId!,
|
||||||
|
messageId: focusMessageId,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.loadViewportMessages({
|
||||||
|
chatId,
|
||||||
|
threadId,
|
||||||
|
tabId,
|
||||||
|
onError: () => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
global = updateTabState(global, {
|
||||||
|
loadingThread: undefined,
|
||||||
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
actions.openChat({
|
actions.showNotification({
|
||||||
id,
|
message: langProvider.translate('Group.ErrorAccessDenied'),
|
||||||
threadId: result.topMessageId,
|
|
||||||
tabId,
|
tabId,
|
||||||
shouldReplaceLast: true,
|
|
||||||
noRequestThreadInfoUpdate: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
},
|
||||||
actions.openChat({
|
onLoaded: () => {
|
||||||
id,
|
global = getGlobal();
|
||||||
threadId: topMessageId,
|
loadingThread = selectTabState(global, tabId).loadingThread;
|
||||||
|
if (loadingThread?.loadingChatId !== loadingChatId || loadingThread?.loadingMessageId !== loadingThreadId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
loadingThread: undefined,
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
actions.processOpenChatOrThread({
|
||||||
|
chatId,
|
||||||
|
type,
|
||||||
|
threadId: threadId!,
|
||||||
tabId,
|
tabId,
|
||||||
noRequestThreadInfoUpdate: true,
|
isComments,
|
||||||
|
noForumTopicPanel,
|
||||||
|
shouldReplaceHistory,
|
||||||
|
shouldReplaceLast,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openLinkedChat', async (global, actions, payload): Promise<void> => {
|
addActionHandler('openLinkedChat', async (global, actions, payload): Promise<void> => {
|
||||||
@ -250,28 +457,6 @@ addActionHandler('openLinkedChat', async (global, actions, payload): Promise<voi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('focusMessageInComments', async (global, actions, payload): Promise<void> => {
|
|
||||||
const {
|
|
||||||
chatId, threadId, messageId, tabId = getCurrentTabId(),
|
|
||||||
} = payload!;
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (!chat) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await callApi('requestThreadInfoUpdate', { chat, threadId });
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
global = getGlobal();
|
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
|
||||||
setGlobal(global);
|
|
||||||
|
|
||||||
actions.focusMessage({
|
|
||||||
chatId, threadId, messageId, tabId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('openSupportChat', async (global, actions, payload): Promise<void> => {
|
addActionHandler('openSupportChat', async (global, actions, payload): Promise<void> => {
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
const chat = selectSupportChat(global);
|
const chat = selectSupportChat(global);
|
||||||
@ -1227,20 +1412,16 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { chatId, type } = selectCurrentMessageList(global, tabId) || {};
|
|
||||||
const usernameChat = selectChatByUsername(global, username);
|
const usernameChat = selectChatByUsername(global, username);
|
||||||
if (chatId && commentId && messageId && usernameChat && type === 'thread') {
|
if (commentId && messageId && usernameChat) {
|
||||||
const threadInfo = selectThreadInfo(global, chatId, messageId);
|
actions.openThread({
|
||||||
|
isComments: true,
|
||||||
if (threadInfo && threadInfo.chatId === chatId) {
|
originChannelId: usernameChat.id,
|
||||||
actions.focusMessage({
|
originMessageId: messageId,
|
||||||
chatId: threadInfo.chatId,
|
tabId,
|
||||||
threadId: threadInfo.threadId,
|
focusMessageId: commentId,
|
||||||
messageId: commentId,
|
});
|
||||||
tabId,
|
return;
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isWebApp) actions.openChat({ id: TMP_CHAT_ID, tabId });
|
if (!isWebApp) actions.openChat({ id: TMP_CHAT_ID, tabId });
|
||||||
@ -1249,8 +1430,6 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
|
|||||||
|
|
||||||
if (!chatByUsername) return;
|
if (!chatByUsername) return;
|
||||||
|
|
||||||
global = getGlobal();
|
|
||||||
|
|
||||||
if (isWebApp && chatByUsername) {
|
if (isWebApp && chatByUsername) {
|
||||||
const theme = extractCurrentThemeParams();
|
const theme = extractCurrentThemeParams();
|
||||||
|
|
||||||
@ -1266,29 +1445,12 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
|
|||||||
|
|
||||||
if (!messageId) return;
|
if (!messageId) return;
|
||||||
|
|
||||||
const threadInfo = selectThreadInfo(global, chatByUsername.id, messageId);
|
actions.openThread({
|
||||||
let discussionChatId: string | undefined;
|
isComments: true,
|
||||||
|
originChannelId: chatByUsername.id,
|
||||||
if (!threadInfo) {
|
originMessageId: messageId,
|
||||||
const result = await callApi('requestThreadInfoUpdate', { chat: chatByUsername, threadId: messageId });
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
global = getGlobal();
|
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
|
||||||
setGlobal(global);
|
|
||||||
|
|
||||||
discussionChatId = result.discussionChatId;
|
|
||||||
} else {
|
|
||||||
discussionChatId = threadInfo.chatId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!discussionChatId) return;
|
|
||||||
|
|
||||||
actions.focusMessage({
|
|
||||||
chatId: discussionChatId,
|
|
||||||
threadId: messageId,
|
|
||||||
messageId: Number(commentId),
|
|
||||||
tabId,
|
tabId,
|
||||||
|
focusMessageId: commentId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1971,8 +2133,8 @@ addActionHandler('createTopic', async (global, actions, payload): Promise<void>
|
|||||||
chat, title, iconColor, iconEmojiId,
|
chat, title, iconColor, iconEmojiId,
|
||||||
});
|
});
|
||||||
if (topicId) {
|
if (topicId) {
|
||||||
actions.openChat({
|
actions.openThread({
|
||||||
id: chatId, threadId: topicId, shouldReplaceHistory: true, tabId,
|
chatId, threadId: topicId, shouldReplaceHistory: true, tabId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
actions.closeCreateTopicPanel({ tabId });
|
actions.closeCreateTopicPanel({ tabId });
|
||||||
@ -2673,7 +2835,7 @@ async function openChatByUsername<T extends GlobalState>(
|
|||||||
chatId: chat.id, threadId, messageId: channelPostId, tabId,
|
chatId: chat.id, threadId, messageId: channelPostId, tabId,
|
||||||
});
|
});
|
||||||
} else if (!isCurrentChat) {
|
} else if (!isCurrentChat) {
|
||||||
actions.openChat({ id: chat.id, threadId, tabId });
|
actions.openThread({ chatId: chat.id, threadId: threadId ?? MAIN_THREAD_ID, tabId });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startParam) {
|
if (startParam) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import type { ApiChat } from '../../../api/types';
|
||||||
import type { SharedMediaType } from '../../../types';
|
import type { SharedMediaType } from '../../../types';
|
||||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
||||||
import { type ApiChat, MAIN_THREAD_ID } from '../../../api/types';
|
|
||||||
|
|
||||||
import { MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
|
import { MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
@ -21,7 +21,6 @@ import {
|
|||||||
selectCurrentMediaSearch,
|
selectCurrentMediaSearch,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectCurrentTextSearch,
|
selectCurrentTextSearch,
|
||||||
selectThreadInfo,
|
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Promise<void> => {
|
addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Promise<void> => {
|
||||||
@ -36,12 +35,6 @@ addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Pr
|
|||||||
const { query, results } = currentSearch;
|
const { query, results } = currentSearch;
|
||||||
const offsetId = results?.nextOffsetId;
|
const offsetId = results?.nextOffsetId;
|
||||||
|
|
||||||
let topMessageId: number | undefined;
|
|
||||||
if (threadId !== MAIN_THREAD_ID) {
|
|
||||||
const threadInfo = selectThreadInfo(global, chatId!, threadId);
|
|
||||||
topMessageId = threadInfo?.topMessageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -50,7 +43,7 @@ addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Pr
|
|||||||
chat,
|
chat,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
query,
|
query,
|
||||||
topMessageId,
|
threadId,
|
||||||
limit: MESSAGE_SEARCH_SLICE,
|
limit: MESSAGE_SEARCH_SLICE,
|
||||||
offsetId,
|
offsetId,
|
||||||
});
|
});
|
||||||
@ -147,7 +140,7 @@ async function searchSharedMedia<T extends GlobalState>(
|
|||||||
chat,
|
chat,
|
||||||
type,
|
type,
|
||||||
limit: SHARED_MEDIA_SLICE * 2,
|
limit: SHARED_MEDIA_SLICE * 2,
|
||||||
topMessageId: threadId === MAIN_THREAD_ID ? undefined : threadId,
|
threadId,
|
||||||
offsetId,
|
offsetId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import {
|
|||||||
import { ensureProtocol } from '../../../util/ensureProtocol';
|
import { ensureProtocol } from '../../../util/ensureProtocol';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import {
|
import {
|
||||||
areSortedArraysIntersecting, buildCollectionByKey, omit, split, unique,
|
areSortedArraysIntersecting, buildCollectionByKey, omit, partition, split, unique,
|
||||||
} from '../../../util/iteratees';
|
} from '../../../util/iteratees';
|
||||||
import { translate } from '../../../util/langProvider';
|
import { translate } from '../../../util/langProvider';
|
||||||
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers';
|
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers';
|
||||||
@ -44,8 +44,11 @@ import { IS_IOS } from '../../../util/windowEnvironment';
|
|||||||
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
||||||
import {
|
import {
|
||||||
getMessageOriginalId,
|
getMessageOriginalId,
|
||||||
getUserFullName, isChatChannel,
|
getUserFullName,
|
||||||
isDeletedUser, isMessageLocal,
|
isChatChannel,
|
||||||
|
isDeletedUser,
|
||||||
|
isLocalMessageId,
|
||||||
|
isMessageLocal,
|
||||||
isServiceNotificationMessage,
|
isServiceNotificationMessage,
|
||||||
isUserBot,
|
isUserBot,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
@ -72,7 +75,6 @@ import {
|
|||||||
updateRequestedMessageTranslation,
|
updateRequestedMessageTranslation,
|
||||||
updateSponsoredMessage,
|
updateSponsoredMessage,
|
||||||
updateThreadInfo,
|
updateThreadInfo,
|
||||||
updateThreadInfos,
|
|
||||||
updateThreadUnreadFromForwardedMessage,
|
updateThreadUnreadFromForwardedMessage,
|
||||||
updateTopic,
|
updateTopic,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
@ -107,8 +109,6 @@ import {
|
|||||||
selectSponsoredMessage,
|
selectSponsoredMessage,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadIdFromMessage,
|
selectThreadIdFromMessage,
|
||||||
selectThreadOriginChat,
|
|
||||||
selectThreadTopMessageId,
|
|
||||||
selectTranslationLanguage,
|
selectTranslationLanguage,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
@ -127,6 +127,8 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
|
|||||||
direction = LoadMoreDirection.Around,
|
direction = LoadMoreDirection.Around,
|
||||||
isBudgetPreload = false,
|
isBudgetPreload = false,
|
||||||
shouldForceRender = false,
|
shouldForceRender = false,
|
||||||
|
onLoaded,
|
||||||
|
onError,
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload || {};
|
} = payload || {};
|
||||||
|
|
||||||
@ -135,6 +137,7 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
|
|||||||
if (!chatId || !threadId) {
|
if (!chatId || !threadId) {
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
if (!currentMessageList) {
|
if (!currentMessageList) {
|
||||||
|
onError?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +148,7 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
|
|||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
// TODO Revise if `chat.isRestricted` check is needed
|
// TODO Revise if `chat.isRestricted` check is needed
|
||||||
if (!chat || chat.isRestricted) {
|
if (!chat || chat.isRestricted) {
|
||||||
|
onError?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,12 +172,18 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
|
|||||||
if (!areAllLocal) {
|
if (!areAllLocal) {
|
||||||
onTickEnd(() => {
|
onTickEnd(() => {
|
||||||
void loadViewportMessages(
|
void loadViewportMessages(
|
||||||
global, chat, threadId!, offsetId, LoadMoreDirection.Around, isOutlying, isBudgetPreload, tabId,
|
global, chat, threadId!, offsetId, LoadMoreDirection.Around, isOutlying, isBudgetPreload, onLoaded, tabId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
onLoaded?.();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const offsetId = direction === LoadMoreDirection.Backwards ? viewportIds[0] : viewportIds[viewportIds.length - 1];
|
const offsetId = direction === LoadMoreDirection.Backwards ? viewportIds[0] : viewportIds[viewportIds.length - 1];
|
||||||
|
|
||||||
|
// Prevent requests with local offsets
|
||||||
|
if (isLocalMessageId(offsetId)) return;
|
||||||
|
|
||||||
const isOutlying = Boolean(listedIds && !listedIds.includes(offsetId));
|
const isOutlying = Boolean(listedIds && !listedIds.includes(offsetId));
|
||||||
const historyIds = (isOutlying
|
const historyIds = (isOutlying
|
||||||
? selectOutlyingListByMessageId(global, chatId, threadId, offsetId) : listedIds)!;
|
? selectOutlyingListByMessageId(global, chatId, threadId, offsetId) : listedIds)!;
|
||||||
@ -187,7 +197,17 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
|
|||||||
|
|
||||||
onTickEnd(() => {
|
onTickEnd(() => {
|
||||||
void loadWithBudget(
|
void loadWithBudget(
|
||||||
global, actions, areAllLocal, isOutlying, isBudgetPreload, chat, threadId!, direction, offsetId, tabId,
|
global,
|
||||||
|
actions,
|
||||||
|
areAllLocal,
|
||||||
|
isOutlying,
|
||||||
|
isBudgetPreload,
|
||||||
|
chat,
|
||||||
|
threadId!,
|
||||||
|
direction,
|
||||||
|
offsetId,
|
||||||
|
onLoaded,
|
||||||
|
tabId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -204,17 +224,18 @@ async function loadWithBudget<T extends GlobalState>(
|
|||||||
actions: RequiredGlobalActions,
|
actions: RequiredGlobalActions,
|
||||||
areAllLocal: boolean, isOutlying: boolean, isBudgetPreload: boolean,
|
areAllLocal: boolean, isOutlying: boolean, isBudgetPreload: boolean,
|
||||||
chat: ApiChat, threadId: number, direction: LoadMoreDirection, offsetId?: number,
|
chat: ApiChat, threadId: number, direction: LoadMoreDirection, offsetId?: number,
|
||||||
|
onLoaded?: NoneToVoidFunction,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
) {
|
) {
|
||||||
if (!areAllLocal) {
|
if (!areAllLocal) {
|
||||||
await loadViewportMessages(
|
await loadViewportMessages(
|
||||||
global, chat, threadId, offsetId, direction, isOutlying, isBudgetPreload, tabId,
|
global, chat, threadId, offsetId, direction, isOutlying, isBudgetPreload, onLoaded, tabId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isBudgetPreload) {
|
if (!isBudgetPreload) {
|
||||||
actions.loadViewportMessages({
|
actions.loadViewportMessages({
|
||||||
chatId: chat.id, threadId, direction, isBudgetPreload: true, tabId,
|
chatId: chat.id, threadId, direction, isBudgetPreload: true, onLoaded, tabId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -571,8 +592,7 @@ addActionHandler('unpinAllMessages', async (global, actions, payload): Promise<v
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const topId = selectThreadTopMessageId(global, chatId, threadId);
|
await callApi('unpinAllMessages', { chat, threadId });
|
||||||
await callApi('unpinAllMessages', { chat, threadId: topId });
|
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const pinnedIds = selectPinnedIds(global, chatId, threadId);
|
const pinnedIds = selectPinnedIds(global, chatId, threadId);
|
||||||
@ -734,6 +754,14 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
|
|
||||||
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||||
const minId = selectFirstUnreadId(global, chatId, threadId);
|
const minId = selectFirstUnreadId(global, chatId, threadId);
|
||||||
|
|
||||||
|
if (threadId !== MAIN_THREAD_ID && !chat.isForum) {
|
||||||
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
|
lastReadInboxMessageId: maxId,
|
||||||
|
});
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
if (!viewportIds || !minId || !chat.unreadCount) {
|
if (!viewportIds || !minId || !chat.unreadCount) {
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
@ -759,11 +787,6 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Support local marking read for comments
|
|
||||||
if (threadId !== MAIN_THREAD_ID) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return updateChat(global, chatId, {
|
return updateChat(global, chatId, {
|
||||||
lastReadInboxMessageId: maxId,
|
lastReadInboxMessageId: maxId,
|
||||||
unreadCount: Math.max(0, chat.unreadCount - readCount),
|
unreadCount: Math.max(0, chat.unreadCount - readCount),
|
||||||
@ -886,24 +909,28 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
|||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fromChatId, messageIds, toChatId, withMyScore, noAuthors, noCaptions, toThreadId,
|
fromChatId, messageIds, toChatId, withMyScore, noAuthors, noCaptions, toThreadId = MAIN_THREAD_ID,
|
||||||
} = selectTabState(global, tabId).forwardMessages;
|
} = selectTabState(global, tabId).forwardMessages;
|
||||||
|
|
||||||
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
const isToMainThread = toThreadId === MAIN_THREAD_ID;
|
||||||
|
|
||||||
const fromChat = fromChatId ? selectChat(global, fromChatId) : undefined;
|
const fromChat = fromChatId ? selectChat(global, fromChatId) : undefined;
|
||||||
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
||||||
|
|
||||||
const messages = fromChatId && messageIds
|
const messages = fromChatId && messageIds
|
||||||
? messageIds
|
? messageIds
|
||||||
.sort((a, b) => a - b)
|
.sort((a, b) => a - b)
|
||||||
.map((id) => selectChatMessage(global, fromChatId, id)).filter(Boolean)
|
.map((id) => selectChatMessage(global, fromChatId, id)).filter(Boolean)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (!fromChat || !toChat || !messages || (toThreadId && !toChat.isForum)) {
|
if (!fromChat || !toChat || !messages || (toThreadId && !isToMainThread && !toChat.isForum)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendAs = selectSendAs(global, toChatId!);
|
const sendAs = selectSendAs(global, toChatId!);
|
||||||
|
|
||||||
const realMessages = messages.filter((m) => !isServiceNotificationMessage(m));
|
const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m));
|
||||||
if (realMessages.length) {
|
if (realMessages.length) {
|
||||||
(async () => {
|
(async () => {
|
||||||
await rafPromise(); // Wait one frame for any previous `sendMessage` to be processed
|
await rafPromise(); // Wait one frame for any previous `sendMessage` to be processed
|
||||||
@ -923,8 +950,7 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
messages
|
serviceMessages
|
||||||
.filter((m) => isServiceNotificationMessage(m))
|
|
||||||
.forEach((message) => {
|
.forEach((message) => {
|
||||||
const { text, entities } = message.content.text || {};
|
const { text, entities } = message.content.text || {};
|
||||||
const { sticker, poll } = message.content;
|
const { sticker, poll } = message.content;
|
||||||
@ -1022,22 +1048,6 @@ addActionHandler('rescheduleMessage', (global, actions, payload): ActionReturnTy
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('requestThreadInfoUpdate', async (global, actions, payload): Promise<void> => {
|
|
||||||
const { chatId, threadId } = payload;
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (!chat) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const originChannelId = selectThreadOriginChat(global, chatId, threadId)?.id;
|
|
||||||
|
|
||||||
const result = await callApi('requestThreadInfoUpdate', { chat, threadId, originChannelId });
|
|
||||||
if (!result) return;
|
|
||||||
global = getGlobal();
|
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
|
||||||
setGlobal(global);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('transcribeAudio', async (global, actions, payload): Promise<void> => {
|
addActionHandler('transcribeAudio', async (global, actions, payload): Promise<void> => {
|
||||||
const { messageId, chatId } = payload;
|
const { messageId, chatId } = payload;
|
||||||
|
|
||||||
@ -1093,6 +1103,7 @@ async function loadViewportMessages<T extends GlobalState>(
|
|||||||
direction: LoadMoreDirection,
|
direction: LoadMoreDirection,
|
||||||
isOutlying = false,
|
isOutlying = false,
|
||||||
isBudgetPreload = false,
|
isBudgetPreload = false,
|
||||||
|
onLoaded?: NoneToVoidFunction,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
) {
|
) {
|
||||||
const chatId = chat.id;
|
const chatId = chat.id;
|
||||||
@ -1133,7 +1144,7 @@ async function loadViewportMessages<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
messages, users, chats, repliesThreadInfos,
|
messages, users, chats,
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
@ -1146,7 +1157,7 @@ async function loadViewportMessages<T extends GlobalState>(
|
|||||||
const ids = Object.keys(byId).map(Number);
|
const ids = Object.keys(byId).map(Number);
|
||||||
|
|
||||||
if (threadId !== MAIN_THREAD_ID) {
|
if (threadId !== MAIN_THREAD_ID) {
|
||||||
const threadFirstMessageId = selectFirstMessageId(global, chatId, threadId) || {};
|
const threadFirstMessageId = selectFirstMessageId(global, chatId, threadId);
|
||||||
if ((!ids[0] || threadFirstMessageId === ids[0]) && threadFirstMessageId !== threadId) {
|
if ((!ids[0] || threadFirstMessageId === ids[0]) && threadFirstMessageId !== threadId) {
|
||||||
ids.unshift(threadId);
|
ids.unshift(threadId);
|
||||||
}
|
}
|
||||||
@ -1159,7 +1170,6 @@ async function loadViewportMessages<T extends GlobalState>(
|
|||||||
|
|
||||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||||
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
||||||
global = updateThreadInfos(global, repliesThreadInfos);
|
|
||||||
|
|
||||||
let listedIds = selectListedIds(global, chatId, threadId);
|
let listedIds = selectListedIds(global, chatId, threadId);
|
||||||
const outlyingList = offsetId ? selectOutlyingListByMessageId(global, chatId, threadId, offsetId) : undefined;
|
const outlyingList = offsetId ? selectOutlyingListByMessageId(global, chatId, threadId, offsetId) : undefined;
|
||||||
@ -1174,12 +1184,15 @@ async function loadViewportMessages<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isBudgetPreload) {
|
if (!isBudgetPreload) {
|
||||||
const historyIds = isOutlying ? outlyingList! : listedIds!;
|
const historyIds = isOutlying && outlyingList ? outlyingList : listedIds;
|
||||||
const { newViewportIds } = getViewportSlice(historyIds, offsetId, direction);
|
if (historyIds) {
|
||||||
global = safeReplaceViewportIds(global, chatId, threadId, newViewportIds!, tabId);
|
const { newViewportIds } = getViewportSlice(historyIds, offsetId, direction);
|
||||||
|
global = safeReplaceViewportIds(global, chatId, threadId, newViewportIds!, tabId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
onLoaded?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadMessage<T extends GlobalState>(
|
async function loadMessage<T extends GlobalState>(
|
||||||
@ -1570,7 +1583,7 @@ addActionHandler('setForwardChatOrTopic', async (global, actions, payload): Prom
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
actions.openChat({ id: chatId, threadId: topicId, tabId });
|
actions.openThread({ chatId, threadId: topicId || MAIN_THREAD_ID, tabId });
|
||||||
actions.closeMediaViewer({ tabId });
|
actions.closeMediaViewer({ tabId });
|
||||||
actions.exitMessageSelectMode({ tabId });
|
actions.exitMessageSelectMode({ tabId });
|
||||||
});
|
});
|
||||||
@ -1732,19 +1745,7 @@ addActionHandler('loadMessageViews', async (global, actions, payload): Promise<v
|
|||||||
forwards: update.forwards,
|
forwards: update.forwards,
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = selectChatMessage(global, chatId, update.id);
|
global = updateThreadInfo(global, chatId, update.id, update.threadInfo);
|
||||||
if (!message) return;
|
|
||||||
|
|
||||||
const repliesChatId = message.repliesThreadInfo?.chatId;
|
|
||||||
const threadId = message.repliesThreadInfo?.threadId;
|
|
||||||
if (!repliesChatId || !threadId) return;
|
|
||||||
|
|
||||||
global = updateThreadInfo(global, repliesChatId, threadId, {
|
|
||||||
messagesCount: update.messagesCount,
|
|
||||||
recentReplierIds: update.recentReplierIds,
|
|
||||||
lastMessageId: update.maxId,
|
|
||||||
lastReadInboxMessageId: update.readMaxId,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { addCallback } from '../../../lib/teact/teactn';
|
import { addCallback } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import type { ApiChat, ApiMessage } from '../../../api/types';
|
import type { ApiChat } from '../../../api/types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type { ActionReturnType, GlobalState, Thread } from '../../types';
|
import type { ActionReturnType, GlobalState, Thread } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import { DEBUG, MESSAGE_LIST_SLICE, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
import { DEBUG, MESSAGE_LIST_SLICE, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||||
import { init as initFolderManager } from '../../../util/folderManager';
|
import { init as initFolderManager } from '../../../util/folderManager';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import {
|
||||||
|
buildCollectionByKey, omitUndefined, pick, unique,
|
||||||
|
} from '../../../util/iteratees';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import {
|
import {
|
||||||
addActionHandler, getActions, getGlobal, setGlobal,
|
addActionHandler, getActions, getGlobal, setGlobal,
|
||||||
@ -17,8 +19,8 @@ import {
|
|||||||
safeReplaceViewportIds,
|
safeReplaceViewportIds,
|
||||||
updateChats,
|
updateChats,
|
||||||
updateListedIds,
|
updateListedIds,
|
||||||
updateThread, updateThreadInfo,
|
updateThread,
|
||||||
updateThreadInfos,
|
updateThreadInfo,
|
||||||
updateUsers,
|
updateUsers,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
@ -107,11 +109,11 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
acc[chatId] = Object
|
acc[chatId] = Object
|
||||||
.keys(global.messages.byChatId[chatId].threadsById)
|
.keys(global.messages.byChatId[chatId].threadsById)
|
||||||
.reduce<Record<number, Partial<Thread>>>((acc2, threadId) => {
|
.reduce<Record<number, Partial<Thread>>>((acc2, threadId) => {
|
||||||
acc2[Number(threadId)] = {
|
acc2[Number(threadId)] = omitUndefined({
|
||||||
draft: selectDraft(global, chatId, Number(threadId)),
|
draft: selectDraft(global, chatId, Number(threadId)),
|
||||||
editingId: selectEditingId(global, chatId, Number(threadId)),
|
editingId: selectEditingId(global, chatId, Number(threadId)),
|
||||||
editingDraft: selectEditingDraft(global, chatId, Number(threadId)),
|
editingDraft: selectEditingDraft(global, chatId, Number(threadId)),
|
||||||
};
|
});
|
||||||
|
|
||||||
return acc2;
|
return acc2;
|
||||||
}, {});
|
}, {});
|
||||||
@ -123,11 +125,21 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
const activeThreadId = currentThreadId || MAIN_THREAD_ID;
|
const activeThreadId = currentThreadId || MAIN_THREAD_ID;
|
||||||
const threadInfo = currentThreadId && currentChatId
|
const threadInfo = currentChatId && currentThreadId
|
||||||
? selectThreadInfo(global, currentChatId, currentThreadId) : undefined;
|
? selectThreadInfo(global, currentChatId, currentThreadId) : undefined;
|
||||||
const currentChat = currentChatId ? global.chats.byId[currentChatId] : undefined;
|
const currentChat = currentChatId ? global.chats.byId[currentChatId] : undefined;
|
||||||
if (currentChatId && currentChat) {
|
if (currentChatId && currentChat) {
|
||||||
const result = await loadTopMessages(currentChat, activeThreadId, threadInfo?.lastReadInboxMessageId);
|
const [result, resultDiscussion] = await Promise.all([
|
||||||
|
loadTopMessages(
|
||||||
|
currentChat,
|
||||||
|
activeThreadId,
|
||||||
|
activeThreadId !== MAIN_THREAD_ID ? activeThreadId : undefined,
|
||||||
|
),
|
||||||
|
activeThreadId !== MAIN_THREAD_ID ? callApi('fetchDiscussionMessage', {
|
||||||
|
chat: currentChat,
|
||||||
|
messageId: activeThreadId,
|
||||||
|
}) : undefined,
|
||||||
|
]);
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const { chatId: newCurrentChatId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId: newCurrentChatId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
|
|
||||||
@ -142,10 +154,12 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const allMessages = ([] as ApiMessage[]).concat(result.messages, localMessages);
|
const isDiscussionStartLoaded = result.messages.some(({ id }) => id === resultDiscussion?.firstMessageId);
|
||||||
|
const threadStartMessages = (isDiscussionStartLoaded && resultDiscussion?.topMessages) || [];
|
||||||
|
const allMessages = threadStartMessages.concat(result.messages, localMessages);
|
||||||
const allMessagesWithTopicLastMessages = allMessages.concat(topicLastMessages);
|
const allMessagesWithTopicLastMessages = allMessages.concat(topicLastMessages);
|
||||||
const byId = buildCollectionByKey(allMessagesWithTopicLastMessages, 'id');
|
const byId = buildCollectionByKey(allMessagesWithTopicLastMessages, 'id');
|
||||||
const listedIds = allMessages.map(({ id }) => id);
|
const listedIds = unique(allMessages.map(({ id }) => id));
|
||||||
|
|
||||||
if (!wasReset) {
|
if (!wasReset) {
|
||||||
global = {
|
global = {
|
||||||
@ -166,8 +180,16 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
|
|
||||||
global = addChatMessagesById(global, currentChatId, byId);
|
global = addChatMessagesById(global, currentChatId, byId);
|
||||||
global = updateListedIds(global, currentChatId, activeThreadId, listedIds);
|
global = updateListedIds(global, currentChatId, activeThreadId, listedIds);
|
||||||
if (threadInfo?.originChannelId) {
|
if (resultDiscussion) {
|
||||||
global = updateThreadInfo(global, currentChatId, activeThreadId, threadInfo);
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
resultDiscussion.threadInfoUpdates.forEach((update) => {
|
||||||
|
global = updateThreadInfo(global, currentChatId, activeThreadId, update);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (threadInfo && !threadInfo.isCommentsInfo && activeThreadId !== MAIN_THREAD_ID) {
|
||||||
|
global = updateThreadInfo(global, currentChatId, activeThreadId, {
|
||||||
|
...pick(threadInfo, ['fromChannelId', 'fromMessageId']),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
Object.values(global.byTabId).forEach(({ id: otherTabId }) => {
|
Object.values(global.byTabId).forEach(({ id: otherTabId }) => {
|
||||||
@ -178,9 +200,6 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
});
|
});
|
||||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
global = updateUsers(global, buildCollectionByKey(result.users, 'id'));
|
global = updateUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
if (result.repliesThreadInfos.length) {
|
|
||||||
global = updateThreadInfos(global, result.repliesThreadInfos);
|
|
||||||
}
|
|
||||||
|
|
||||||
areMessagesLoaded = true;
|
areMessagesLoaded = true;
|
||||||
}
|
}
|
||||||
@ -235,11 +254,11 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadTopMessages(chat: ApiChat, threadId: number, lastReadInboxId?: number) {
|
function loadTopMessages(chat: ApiChat, threadId: number, offsetId?: number) {
|
||||||
return callApi('fetchMessages', {
|
return callApi('fetchMessages', {
|
||||||
chat,
|
chat,
|
||||||
threadId,
|
threadId,
|
||||||
offsetId: lastReadInboxId || chat.lastReadInboxMessageId,
|
offsetId: offsetId || chat.lastReadInboxMessageId,
|
||||||
addOffset: -(Math.round(MESSAGE_LIST_SLICE / 2) + 1),
|
addOffset: -(Math.round(MESSAGE_LIST_SLICE / 2) + 1),
|
||||||
limit: MESSAGE_LIST_SLICE,
|
limit: MESSAGE_LIST_SLICE,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiMessage, ApiPollResult, ApiReactions, ApiThreadInfo,
|
ApiChat, ApiMessage, ApiPollResult, ApiReactions,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type {
|
import type {
|
||||||
@ -33,6 +33,7 @@ import {
|
|||||||
updateMessageTranslations,
|
updateMessageTranslations,
|
||||||
updateScheduledMessage,
|
updateScheduledMessage,
|
||||||
updateThreadInfo,
|
updateThreadInfo,
|
||||||
|
updateThreadInfos,
|
||||||
updateThreadUnreadFromForwardedMessage,
|
updateThreadUnreadFromForwardedMessage,
|
||||||
updateTopic,
|
updateTopic,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
@ -75,15 +76,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
global = updateWithLocalMedia(global, chatId, id, message);
|
global = updateWithLocalMedia(global, chatId, id, message);
|
||||||
global = updateListedAndViewportIds(global, actions, message as ApiMessage);
|
global = updateListedAndViewportIds(global, actions, message as ApiMessage);
|
||||||
|
|
||||||
if (message.repliesThreadInfo) {
|
|
||||||
global = updateThreadInfo(
|
|
||||||
global,
|
|
||||||
message.repliesThreadInfo.chatId,
|
|
||||||
message.repliesThreadInfo.threadId,
|
|
||||||
message.repliesThreadInfo,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMessage = selectChatMessage(global, chatId, id)!;
|
const newMessage = selectChatMessage(global, chatId, id)!;
|
||||||
const replyInfo = getMessageReplyInfo(newMessage);
|
const replyInfo = getMessageReplyInfo(newMessage);
|
||||||
const storyReplyInfo = getStoryReplyInfo(newMessage);
|
const storyReplyInfo = getStoryReplyInfo(newMessage);
|
||||||
@ -114,11 +106,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { threadInfo } = selectThreadByMessage(global, message as ApiMessage) || {};
|
|
||||||
if (threadInfo && !isLocal) {
|
|
||||||
actions.requestThreadInfoUpdate({ chatId, threadId: threadInfo.threadId });
|
|
||||||
}
|
|
||||||
|
|
||||||
// @perf Wait until scroll animation finishes or simply rely on delivery status update
|
// @perf Wait until scroll animation finishes or simply rely on delivery status update
|
||||||
// (which is itself delayed)
|
// (which is itself delayed)
|
||||||
if (!isLocal) {
|
if (!isLocal) {
|
||||||
@ -204,14 +191,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
global = updateWithLocalMedia(global, chatId, id, message);
|
global = updateWithLocalMedia(global, chatId, id, message);
|
||||||
|
|
||||||
const newMessage = selectChatMessage(global, chatId, id)!;
|
const newMessage = selectChatMessage(global, chatId, id)!;
|
||||||
if (message.repliesThreadInfo) {
|
|
||||||
global = updateThreadInfo(
|
|
||||||
global,
|
|
||||||
message.repliesThreadInfo.chatId,
|
|
||||||
message.repliesThreadInfo.threadId,
|
|
||||||
message.repliesThreadInfo,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentMessage) {
|
if (currentMessage) {
|
||||||
global = updateChatLastMessage(global, chatId, newMessage);
|
global = updateChatLastMessage(global, chatId, newMessage);
|
||||||
@ -293,7 +272,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
|
|
||||||
actions.markMessageListRead({ maxId: message.id, tabId });
|
actions.markMessageListRead({ maxId: message.id, tabId });
|
||||||
});
|
});
|
||||||
if (thread?.threadInfo) {
|
if (thread?.threadInfo?.threadId) {
|
||||||
global = replaceThreadParam(global, chatId, thread.threadInfo.threadId, 'threadInfo', {
|
global = replaceThreadParam(global, chatId, thread.threadInfo.threadId, 'threadInfo', {
|
||||||
...thread.threadInfo,
|
...thread.threadInfo,
|
||||||
lastMessageId: message.id,
|
lastMessageId: message.id,
|
||||||
@ -364,43 +343,33 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateThreadInfo': {
|
case 'updateThreadInfos': {
|
||||||
const {
|
const {
|
||||||
chatId, threadId, threadInfo, firstMessageId,
|
threadInfoUpdates,
|
||||||
} = update;
|
} = update;
|
||||||
|
|
||||||
const currentThreadInfo = selectThreadInfo(global, chatId, threadId);
|
global = updateThreadInfos(global, threadInfoUpdates);
|
||||||
const newThreadInfo = {
|
threadInfoUpdates.forEach((threadInfo) => {
|
||||||
...currentThreadInfo,
|
const { chatId, threadId } = threadInfo;
|
||||||
...threadInfo,
|
if (!chatId || !threadId) return;
|
||||||
};
|
|
||||||
|
|
||||||
if (!newThreadInfo.threadId) {
|
const chat = selectChat(global, chatId);
|
||||||
return;
|
const currentThreadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
}
|
if (chat?.isForum && threadInfo.lastReadInboxMessageId !== currentThreadInfo?.lastReadInboxMessageId) {
|
||||||
|
actions.loadTopicById({ chatId, topicId: threadId });
|
||||||
global = updateThreadInfo(global, chatId, threadId, newThreadInfo as ApiThreadInfo);
|
|
||||||
|
|
||||||
if (firstMessageId) {
|
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'firstMessageId', firstMessageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (chat?.isForum && threadInfo.lastReadInboxMessageId !== currentThreadInfo?.lastReadInboxMessageId) {
|
|
||||||
actions.loadTopicById({ chatId, topicId: threadId });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update reply thread last read message id if already read in main thread
|
|
||||||
if (threadInfo.topMessageId === threadId && !chat?.isForum) {
|
|
||||||
const lastReadInboxMessageId = chat?.lastReadInboxMessageId;
|
|
||||||
const lastReadInboxMessageIdInThread = newThreadInfo.lastReadInboxMessageId || lastReadInboxMessageId;
|
|
||||||
if (lastReadInboxMessageId && lastReadInboxMessageIdInThread) {
|
|
||||||
global = updateThreadInfo(global, chatId, threadId, {
|
|
||||||
lastReadInboxMessageId: Math.max(lastReadInboxMessageIdInThread, lastReadInboxMessageId),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// Update reply thread last read message id if already read in main thread
|
||||||
|
if (!chat?.isForum) {
|
||||||
|
const lastReadInboxMessageId = chat?.lastReadInboxMessageId;
|
||||||
|
const lastReadInboxMessageIdInThread = threadInfo.lastReadInboxMessageId || lastReadInboxMessageId;
|
||||||
|
if (lastReadInboxMessageId && lastReadInboxMessageIdInThread) {
|
||||||
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
|
lastReadInboxMessageId: Math.max(lastReadInboxMessageIdInThread, lastReadInboxMessageId),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -807,35 +776,37 @@ function updateListedAndViewportIds<T extends GlobalState>(
|
|||||||
) {
|
) {
|
||||||
const { id, chatId } = message;
|
const { id, chatId } = message;
|
||||||
|
|
||||||
const { threadInfo, firstMessageId } = selectThreadByMessage(global, message) || {};
|
const { threadInfo } = selectThreadByMessage(global, message) || {};
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const isUnreadChatNotLoaded = chat?.unreadCount && !selectListedIds(global, chatId, MAIN_THREAD_ID);
|
const isUnreadChatNotLoaded = chat?.unreadCount && !selectListedIds(global, chatId, MAIN_THREAD_ID);
|
||||||
|
|
||||||
global = updateThreadUnread(global, actions, message);
|
global = updateThreadUnread(global, actions, message);
|
||||||
|
const { threadId } = threadInfo ?? {};
|
||||||
|
|
||||||
if (threadInfo) {
|
if (threadInfo && threadId) {
|
||||||
if (firstMessageId || !isMessageLocal(message)) {
|
global = updateListedIds(global, chatId, threadId, [id]);
|
||||||
global = updateListedIds(global, chatId, threadInfo.threadId, [id]);
|
|
||||||
|
|
||||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
if (selectIsViewportNewest(global, chatId, threadInfo.threadId, tabId)) {
|
if (selectIsViewportNewest(global, chatId, threadId, tabId)) {
|
||||||
global = addViewportId(global, chatId, threadInfo.threadId, id, tabId);
|
// Always keep the first unread message in the viewport list
|
||||||
|
const firstUnreadId = selectFirstUnreadId(global, chatId, threadId);
|
||||||
|
const candidateGlobal = addViewportId(global, chatId, threadId, id, tabId);
|
||||||
|
const newViewportIds = selectViewportIds(candidateGlobal, chatId, threadId, tabId);
|
||||||
|
|
||||||
if (!firstMessageId) {
|
if (!firstUnreadId || newViewportIds!.includes(firstUnreadId)) {
|
||||||
global = replaceThreadParam(global, chatId, threadInfo.threadId, 'firstMessageId', message.id);
|
global = candidateGlobal;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
global = replaceThreadParam(global, chatId, threadInfo.threadId, 'threadInfo', {
|
global = replaceThreadParam(global, chatId, threadId, 'threadInfo', {
|
||||||
...threadInfo,
|
...threadInfo,
|
||||||
lastMessageId: message.id,
|
lastMessageId: message.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isMessageLocal(message) && !isActionMessage(message)) {
|
if (!isMessageLocal(message) && !isActionMessage(message)) {
|
||||||
global = updateThreadInfo(global, chatId, threadInfo.threadId, {
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
messagesCount: (threadInfo.messagesCount || 0) + 1,
|
messagesCount: (threadInfo.messagesCount || 0) + 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -895,9 +866,9 @@ function updateChatLastMessage<T extends GlobalState>(
|
|||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findLastMessage<T extends GlobalState>(global: T, chatId: string) {
|
function findLastMessage<T extends GlobalState>(global: T, chatId: string, threadId = MAIN_THREAD_ID) {
|
||||||
const byId = selectChatMessages(global, chatId);
|
const byId = selectChatMessages(global, chatId);
|
||||||
const listedIds = selectListedIds(global, chatId, MAIN_THREAD_ID);
|
const listedIds = selectListedIds(global, chatId, threadId);
|
||||||
|
|
||||||
if (!byId || !listedIds) {
|
if (!byId || !listedIds) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -906,7 +877,7 @@ function findLastMessage<T extends GlobalState>(global: T, chatId: string) {
|
|||||||
let i = listedIds.length;
|
let i = listedIds.length;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
const message = byId[listedIds[i]];
|
const message = byId[listedIds[i]];
|
||||||
if (!message.isDeleting) {
|
if (message && !message.isDeleting) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -923,6 +894,9 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
|
const threadIdsToUpdate = new Set<number>();
|
||||||
|
threadIdsToUpdate.add(MAIN_THREAD_ID);
|
||||||
|
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
global = updateChatMessage(global, chatId, id, {
|
global = updateChatMessage(global, chatId, id, {
|
||||||
isDeleting: true,
|
isDeleting: true,
|
||||||
@ -930,21 +904,10 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
|
|
||||||
global = clearMessageTranslation(global, chatId, id);
|
global = clearMessageTranslation(global, chatId, id);
|
||||||
|
|
||||||
const newLastMessage = findLastMessage(global, chatId);
|
|
||||||
if (newLastMessage) {
|
|
||||||
global = updateChatLastMessage(global, chatId, newLastMessage, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chat.topics?.[id]) {
|
if (chat.topics?.[id]) {
|
||||||
global = deleteTopic(global, chatId, id);
|
global = deleteTopic(global, chatId, id);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
actions.requestChatUpdate({ chatId });
|
|
||||||
|
|
||||||
const threadIdsToUpdate: number[] = [];
|
|
||||||
|
|
||||||
ids.forEach((id) => {
|
|
||||||
const message = selectChatMessage(global, chatId, id);
|
const message = selectChatMessage(global, chatId, id);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
@ -954,20 +917,36 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
|
|
||||||
const threadId = selectThreadIdFromMessage(global, message);
|
const threadId = selectThreadIdFromMessage(global, message);
|
||||||
if (threadId) {
|
if (threadId) {
|
||||||
threadIdsToUpdate.push(threadId);
|
threadIdsToUpdate.add(threadId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
actions.requestChatUpdate({ chatId });
|
||||||
|
|
||||||
|
const idsSet = new Set(ids);
|
||||||
|
|
||||||
|
threadIdsToUpdate.forEach((threadId) => {
|
||||||
|
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
|
if (!threadInfo?.lastMessageId || !idsSet.has(threadInfo.lastMessageId)) return;
|
||||||
|
|
||||||
|
const newLastMessage = findLastMessage(global, chatId, threadId);
|
||||||
|
if (!newLastMessage) return;
|
||||||
|
|
||||||
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
|
global = updateChatLastMessage(global, chatId, newLastMessage, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
|
lastMessageId: newLastMessage.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = deleteChatMessages(global, chatId, ids);
|
global = deleteChatMessages(global, chatId, ids);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
unique(threadIdsToUpdate).forEach((threadId) => {
|
|
||||||
actions.requestThreadInfoUpdate({ chatId, threadId });
|
|
||||||
});
|
|
||||||
}, ANIMATION_DELAY);
|
}, ANIMATION_DELAY);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -14,9 +14,9 @@ import {
|
|||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { closeLocalTextSearch } from './localSearch';
|
import { closeLocalTextSearch } from './localSearch';
|
||||||
|
|
||||||
addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
id,
|
chatId,
|
||||||
threadId = MAIN_THREAD_ID,
|
threadId = MAIN_THREAD_ID,
|
||||||
type = 'thread',
|
type = 'thread',
|
||||||
shouldReplaceHistory = false,
|
shouldReplaceHistory = false,
|
||||||
@ -38,12 +38,12 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!currentMessageList || (
|
if (!currentMessageList || (
|
||||||
currentMessageList.chatId !== id
|
currentMessageList.chatId !== chatId
|
||||||
|| currentMessageList.threadId !== threadId
|
|| currentMessageList.threadId !== threadId
|
||||||
|| currentMessageList.type !== type
|
|| currentMessageList.type !== type
|
||||||
)) {
|
)) {
|
||||||
if (id) {
|
if (chatId) {
|
||||||
global = replaceTabThreadParam(global, id, threadId, 'replyStack', [], tabId);
|
global = replaceTabThreadParam(global, chatId, threadId, 'replyStack', [], tabId);
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
activeReactions: {},
|
activeReactions: {},
|
||||||
@ -57,25 +57,25 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
|||||||
isStatisticsShown: false,
|
isStatisticsShown: false,
|
||||||
boostStatistics: undefined,
|
boostStatistics: undefined,
|
||||||
contentToBeScheduled: undefined,
|
contentToBeScheduled: undefined,
|
||||||
...(id !== selectTabState(global, tabId).forwardMessages.toChatId && {
|
...(chatId !== selectTabState(global, tabId).forwardMessages.toChatId && {
|
||||||
forwardMessages: {},
|
forwardMessages: {},
|
||||||
}),
|
}),
|
||||||
}, tabId);
|
}, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id) {
|
if (chatId) {
|
||||||
const chat = selectChat(global, id);
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
if (chat?.isForum && !noForumTopicPanel) {
|
if (chat?.isForum && !noForumTopicPanel) {
|
||||||
actions.openForumPanel({ chatId: id!, tabId });
|
actions.openForumPanel({ chatId, tabId });
|
||||||
} else if (id !== selectTabState(global, tabId).forumPanelChatId) {
|
} else if (chatId !== selectTabState(global, tabId).forumPanelChatId) {
|
||||||
actions.closeForumPanel({ tabId });
|
actions.closeForumPanel({ tabId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.updatePageTitle({ tabId });
|
actions.updatePageTitle({ tabId });
|
||||||
|
|
||||||
return updateCurrentMessageList(global, id, threadId, type, shouldReplaceHistory, shouldReplaceLast, tabId);
|
return updateCurrentMessageList(global, chatId, threadId, type, shouldReplaceHistory, shouldReplaceLast, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openChatInNewTab', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openChatInNewTab', (global, actions, payload): ActionReturnType => {
|
||||||
@ -110,13 +110,26 @@ addActionHandler('openChatWithInfo', (global, actions, payload): ActionReturnTyp
|
|||||||
actions.openChat({ ...payload, tabId });
|
actions.openChat({ ...payload, tabId });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openThreadWithInfo', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
...selectTabState(global, tabId),
|
||||||
|
isChatInfoShown: true,
|
||||||
|
}, tabId);
|
||||||
|
global = { ...global, lastIsChatInfoShown: true };
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
actions.openThread({ ...payload, tabId });
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('openChatWithDraft', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openChatWithDraft', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
chatId, text, threadId, files, filter, tabId = getCurrentTabId(),
|
chatId, text, threadId = MAIN_THREAD_ID, files, filter, tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
if (chatId) {
|
if (chatId) {
|
||||||
actions.openChat({ id: chatId, threadId, tabId });
|
actions.openThread({ chatId, threadId, tabId });
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
|
|||||||
@ -440,8 +440,8 @@ addActionHandler('focusMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||||
if (viewportIds && viewportIds.includes(messageId)) {
|
if (viewportIds && viewportIds.includes(messageId)) {
|
||||||
setGlobal(global, { forceOnHeavyAnimation: true });
|
setGlobal(global, { forceOnHeavyAnimation: true });
|
||||||
actions.openChat({
|
actions.openThread({
|
||||||
id: chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
type: messageListType,
|
type: messageListType,
|
||||||
shouldReplaceHistory,
|
shouldReplaceHistory,
|
||||||
@ -462,8 +462,8 @@ addActionHandler('focusMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
|
|
||||||
setGlobal(global, { forceOnHeavyAnimation: true });
|
setGlobal(global, { forceOnHeavyAnimation: true });
|
||||||
|
|
||||||
actions.openChat({
|
actions.openThread({
|
||||||
id: chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
type: messageListType,
|
type: messageListType,
|
||||||
shouldReplaceHistory,
|
shouldReplaceHistory,
|
||||||
@ -471,6 +471,8 @@ addActionHandler('focusMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
tabId,
|
tabId,
|
||||||
});
|
});
|
||||||
actions.loadViewportMessages({
|
actions.loadViewportMessages({
|
||||||
|
chatId,
|
||||||
|
threadId,
|
||||||
tabId,
|
tabId,
|
||||||
shouldForceRender: true,
|
shouldForceRender: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,11 +2,9 @@ import { addCallback } from '../../../lib/teact/teactn';
|
|||||||
|
|
||||||
import type { ApiError, ApiNotification } from '../../../api/types';
|
import type { ApiError, ApiNotification } from '../../../api/types';
|
||||||
import type { ActionReturnType, GlobalState } from '../../types';
|
import type { ActionReturnType, GlobalState } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEBUG, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT, INACTIVE_MARKER,
|
DEBUG, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT, INACTIVE_MARKER, PAGE_TITLE,
|
||||||
PAGE_TITLE,
|
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { getAllMultitabTokens, getCurrentTabId, reestablishMasterToSelf } from '../../../util/establishMultitabRole';
|
import { getAllMultitabTokens, getCurrentTabId, reestablishMasterToSelf } from '../../../util/establishMultitabRole';
|
||||||
import { getAllNotificationsCount } from '../../../util/folderManager';
|
import { getAllNotificationsCount } from '../../../util/folderManager';
|
||||||
@ -134,7 +132,7 @@ addActionHandler('closeManagement', (global, actions, payload): ActionReturnType
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionReturnType => {
|
||||||
const { tabId = getCurrentTabId() } = payload;
|
const { tabId = getCurrentTabId() } = payload;
|
||||||
if (!getIsMobile() && !getIsTablet()) {
|
if (!getIsMobile() && !getIsTablet()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -541,7 +539,7 @@ addActionHandler('openCreateTopicPanel', (global, actions, payload): ActionRetur
|
|||||||
|
|
||||||
// Topic panel can be opened only if there is a selected chat
|
// Topic panel can be opened only if there is a selected chat
|
||||||
const currentChat = selectCurrentChat(global, tabId);
|
const currentChat = selectCurrentChat(global, tabId);
|
||||||
if (!currentChat) actions.openChat({ id: chatId, threadId: MAIN_THREAD_ID, tabId });
|
if (!currentChat) actions.openChat({ id: chatId, tabId });
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
createTopicPanel: {
|
createTopicPanel: {
|
||||||
|
|||||||
@ -5,16 +5,16 @@ import { addActionHandler } from '../../index';
|
|||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import { selectTabState } from '../../selectors';
|
import { selectTabState } from '../../selectors';
|
||||||
|
|
||||||
addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
id,
|
chatId,
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
if (id) {
|
if (chatId) {
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
reactionPicker: {
|
reactionPicker: {
|
||||||
chatId: id,
|
chatId,
|
||||||
messageId: undefined,
|
messageId: undefined,
|
||||||
position: undefined,
|
position: undefined,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -33,7 +33,6 @@ import {
|
|||||||
selectChat,
|
selectChat,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectThreadOriginChat,
|
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
selectVisibleUsers,
|
selectVisibleUsers,
|
||||||
} from './selectors';
|
} from './selectors';
|
||||||
@ -335,17 +334,8 @@ function reduceChats<T extends GlobalState>(global: T): GlobalState['chats'] {
|
|||||||
const { chats: { byId }, currentUserId } = global;
|
const { chats: { byId }, currentUserId } = global;
|
||||||
const currentChatIds = compact(
|
const currentChatIds = compact(
|
||||||
Object.values(global.byTabId)
|
Object.values(global.byTabId)
|
||||||
.flatMap(({ id: tabId }): MessageList[] | undefined => {
|
.map(({ id: tabId }): MessageList | undefined => {
|
||||||
const messageList = selectCurrentMessageList(global, tabId);
|
return selectCurrentMessageList(global, tabId);
|
||||||
if (!messageList) return undefined;
|
|
||||||
|
|
||||||
const { chatId, threadId } = messageList;
|
|
||||||
const origin = selectThreadOriginChat(global, chatId, threadId);
|
|
||||||
return origin ? [{
|
|
||||||
chatId: origin.id,
|
|
||||||
threadId: MAIN_THREAD_ID,
|
|
||||||
type: 'thread',
|
|
||||||
}, messageList] : [messageList];
|
|
||||||
}),
|
}),
|
||||||
).map(({ chatId }) => chatId);
|
).map(({ chatId }) => chatId);
|
||||||
|
|
||||||
@ -407,14 +397,17 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
|||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
const threadIds = compact(Object.values(global.byTabId).map(({ id: tabId }) => {
|
const threadIds = unique(compact(Object.values(global.byTabId).map(({ id: tabId }) => {
|
||||||
const { chatId: tabChatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId: tabChatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
if (!tabChatId || tabChatId !== chatId || !threadId || threadId === MAIN_THREAD_ID) {
|
if (!tabChatId || tabChatId !== chatId || !threadId || threadId === MAIN_THREAD_ID) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return threadId;
|
return threadId;
|
||||||
}));
|
}).concat(
|
||||||
|
Object.values(global.messages.byChatId[chatId].threadsById || {})
|
||||||
|
.map(({ threadInfo }) => (threadInfo?.isCommentsInfo ? threadInfo?.originMessageId : undefined)),
|
||||||
|
)));
|
||||||
|
|
||||||
const threadIdsToSave = threadIds.length ? [MAIN_THREAD_ID, ...threadIds] : [MAIN_THREAD_ID];
|
const threadIdsToSave = threadIds.length ? [MAIN_THREAD_ID, ...threadIds] : [MAIN_THREAD_ID];
|
||||||
const threadsToSave = pickTruthy(current.threadsById, threadIdsToSave);
|
const threadsToSave = pickTruthy(current.threadsById, threadIdsToSave);
|
||||||
|
|||||||
@ -142,7 +142,7 @@ export function isUserRightBanned(chat: ApiChat, key: keyof ApiChatBannedRights)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCanPostInChat(chat: ApiChat, threadId: number, isComments?: boolean) {
|
export function getCanPostInChat(chat: ApiChat, threadId: number, isMessageThread?: boolean) {
|
||||||
if (threadId !== MAIN_THREAD_ID) {
|
if (threadId !== MAIN_THREAD_ID) {
|
||||||
if (chat.isForum) {
|
if (chat.isForum) {
|
||||||
if (chat.isNotJoined) {
|
if (chat.isNotJoined) {
|
||||||
@ -157,7 +157,7 @@ export function getCanPostInChat(chat: ApiChat, threadId: number, isComments?: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chat.isRestricted || chat.isForbidden || chat.migratedTo
|
if (chat.isRestricted || chat.isForbidden || chat.migratedTo
|
||||||
|| (!isComments && chat.isNotJoined) || isChatWithRepliesBot(chat.id)) {
|
|| (!isMessageThread && chat.isNotJoined) || isChatWithRepliesBot(chat.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
} from '../../config';
|
} from '../../config';
|
||||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||||
import {
|
import {
|
||||||
areSortedArraysEqual, omit, pickTruthy, unique,
|
areSortedArraysEqual, excludeSortedArray, omit, pick, pickTruthy, unique,
|
||||||
} from '../../util/iteratees';
|
} from '../../util/iteratees';
|
||||||
import {
|
import {
|
||||||
isLocalMessageId, mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
isLocalMessageId, mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
||||||
@ -31,7 +31,7 @@ import {
|
|||||||
selectOutlyingLists,
|
selectOutlyingLists,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectScheduledIds,
|
selectScheduledIds,
|
||||||
selectTabState, selectThreadInfo,
|
selectTabState, selectThreadIdFromMessage, selectThreadInfo,
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
} from '../selectors';
|
} from '../selectors';
|
||||||
import { updateTabState } from './tabs';
|
import { updateTabState } from './tabs';
|
||||||
@ -100,17 +100,16 @@ export function updateTabThread<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateThread<T extends GlobalState>(
|
export function updateThread<T extends GlobalState>(
|
||||||
global: T, chatId: string, threadId: number, threadUpdate: Partial<Thread>,
|
global: T, chatId: string, threadId: number, threadUpdate: Partial<Thread> | undefined,
|
||||||
): T {
|
): T {
|
||||||
const current = global.messages.byChatId[chatId];
|
if (!threadUpdate) {
|
||||||
|
return updateMessageStore(global, chatId, {
|
||||||
if (threadUpdate.listedIds?.length) {
|
threadsById: omit(global.messages.byChatId[chatId]?.threadsById, [threadId]),
|
||||||
const lastListedId = threadUpdate.listedIds[threadUpdate.listedIds.length - 1];
|
});
|
||||||
if (lastListedId) {
|
|
||||||
global = updateTopicLastMessageId(global, chatId, threadId, lastListedId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const current = global.messages.byChatId[chatId];
|
||||||
|
|
||||||
return updateMessageStore(global, chatId, {
|
return updateMessageStore(global, chatId, {
|
||||||
threadsById: {
|
threadsById: {
|
||||||
...(current?.threadsById),
|
...(current?.threadsById),
|
||||||
@ -243,40 +242,49 @@ export function deleteChatMessages<T extends GlobalState>(
|
|||||||
if (!byId) {
|
if (!byId) {
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
const newById = omit(byId, messageIds);
|
|
||||||
|
orderHistoryIds(messageIds);
|
||||||
|
const updatedThreads = new Map<number, number[]>();
|
||||||
|
updatedThreads.set(MAIN_THREAD_ID, messageIds);
|
||||||
|
|
||||||
|
messageIds.forEach((messageId) => {
|
||||||
|
const message = byId[messageId];
|
||||||
|
if (!message) return;
|
||||||
|
const threadId = selectThreadIdFromMessage(global, message);
|
||||||
|
if (!threadId || threadId === MAIN_THREAD_ID) return;
|
||||||
|
const threadMessages = updatedThreads.get(threadId) || [];
|
||||||
|
threadMessages.push(messageId);
|
||||||
|
updatedThreads.set(threadId, threadMessages);
|
||||||
|
});
|
||||||
|
|
||||||
const deletedForwardedPosts = Object.values(pickTruthy(byId, messageIds)).filter(
|
const deletedForwardedPosts = Object.values(pickTruthy(byId, messageIds)).filter(
|
||||||
({ forwardInfo }) => forwardInfo?.isLinkedChannelPost,
|
({ forwardInfo }) => forwardInfo?.isLinkedChannelPost,
|
||||||
);
|
);
|
||||||
|
|
||||||
const threadIds = Object.keys(global.messages.byChatId[chatId].threadsById).map(Number);
|
updatedThreads.forEach((threadMessageIds, threadId) => {
|
||||||
threadIds.forEach((threadId) => {
|
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
|
|
||||||
let listedIds = selectListedIds(global, chatId, threadId);
|
let listedIds = selectListedIds(global, chatId, threadId);
|
||||||
let pinnedIds = selectPinnedIds(global, chatId, threadId);
|
let pinnedIds = selectPinnedIds(global, chatId, threadId);
|
||||||
let outlyingLists = selectOutlyingLists(global, chatId, threadId);
|
let outlyingLists = selectOutlyingLists(global, chatId, threadId);
|
||||||
let mainPinnedIds = selectPinnedIds(global, chatId, MAIN_THREAD_ID);
|
|
||||||
let newMessageCount = threadInfo?.messagesCount;
|
let newMessageCount = threadInfo?.messagesCount;
|
||||||
|
|
||||||
messageIds.forEach((messageId) => {
|
if (listedIds) {
|
||||||
if (listedIds?.includes(messageId)) {
|
listedIds = excludeSortedArray(listedIds, threadMessageIds);
|
||||||
listedIds = listedIds.filter((id) => id !== messageId);
|
}
|
||||||
if (newMessageCount !== undefined && !isLocalMessageId(messageId)) newMessageCount -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
outlyingLists = outlyingLists?.map((list) => {
|
if (outlyingLists) {
|
||||||
if (!list.includes(messageId)) return list;
|
outlyingLists = outlyingLists.map((list) => excludeSortedArray(list, threadMessageIds));
|
||||||
return list.filter((id) => id !== messageId);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (pinnedIds?.includes(messageId)) {
|
if (pinnedIds) {
|
||||||
pinnedIds = pinnedIds.filter((id) => id !== messageId);
|
pinnedIds = excludeSortedArray(pinnedIds, orderPinnedIds(threadMessageIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mainPinnedIds?.includes(messageId)) {
|
const nonLocalMessageCount = threadMessageIds.filter((id) => !isLocalMessageId(id)).length;
|
||||||
mainPinnedIds = mainPinnedIds.filter((id) => id !== messageId);
|
if (newMessageCount !== undefined) {
|
||||||
}
|
newMessageCount -= nonLocalMessageCount;
|
||||||
});
|
}
|
||||||
|
|
||||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
let viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
let viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||||
@ -293,7 +301,6 @@ export function deleteChatMessages<T extends GlobalState>(
|
|||||||
global = replaceThreadParam(global, chatId, threadId, 'listedIds', listedIds);
|
global = replaceThreadParam(global, chatId, threadId, 'listedIds', listedIds);
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'outlyingLists', outlyingLists);
|
global = replaceThreadParam(global, chatId, threadId, 'outlyingLists', outlyingLists);
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'pinnedIds', pinnedIds);
|
global = replaceThreadParam(global, chatId, threadId, 'pinnedIds', pinnedIds);
|
||||||
global = replaceThreadParam(global, chatId, MAIN_THREAD_ID, 'pinnedIds', mainPinnedIds);
|
|
||||||
|
|
||||||
if (threadInfo && newMessageCount !== undefined) {
|
if (threadInfo && newMessageCount !== undefined) {
|
||||||
global = updateThreadInfo(global, chatId, threadId, {
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
@ -313,16 +320,17 @@ export function deleteChatMessages<T extends GlobalState>(
|
|||||||
const { fromChatId, fromMessageId } = message.forwardInfo!;
|
const { fromChatId, fromMessageId } = message.forwardInfo!;
|
||||||
const originalPost = selectChatMessage(global, fromChatId!, fromMessageId!);
|
const originalPost = selectChatMessage(global, fromChatId!, fromMessageId!);
|
||||||
|
|
||||||
if (canDeleteCurrentThread && currentThreadId === fromMessageId) {
|
if (canDeleteCurrentThread && currentThreadId === message.id) {
|
||||||
global = updateCurrentMessageList(global, chatId, undefined, undefined, undefined, undefined, tabId);
|
global = updateCurrentMessageList(global, chatId, undefined, undefined, undefined, undefined, tabId);
|
||||||
}
|
}
|
||||||
if (originalPost) {
|
if (originalPost) {
|
||||||
global = updateChatMessage(global, fromChatId!, fromMessageId!, { repliesThreadInfo: undefined });
|
global = updateThread(global, fromChatId!, fromMessageId!, undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newById = omit(byId, messageIds);
|
||||||
global = replaceChatMessages(global, chatId, newById);
|
global = replaceChatMessages(global, chatId, newById);
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
@ -477,14 +485,26 @@ export function safeReplacePinnedIds<T extends GlobalState>(
|
|||||||
|
|
||||||
export function updateThreadInfo<T extends GlobalState>(
|
export function updateThreadInfo<T extends GlobalState>(
|
||||||
global: T, chatId: string, threadId: number, update: Partial<ApiThreadInfo> | undefined,
|
global: T, chatId: string, threadId: number, update: Partial<ApiThreadInfo> | undefined,
|
||||||
|
doNotUpdateLinked?: boolean,
|
||||||
): T {
|
): T {
|
||||||
const newThreadInfo = {
|
const newThreadInfo = {
|
||||||
...(selectThreadInfo(global, chatId, threadId) as ApiThreadInfo),
|
...(selectThreadInfo(global, chatId, threadId) as ApiThreadInfo),
|
||||||
...update,
|
...update,
|
||||||
};
|
} as ApiThreadInfo;
|
||||||
|
|
||||||
if (!newThreadInfo.threadId) {
|
if (!doNotUpdateLinked) {
|
||||||
return global;
|
const linkedUpdate = pick(newThreadInfo, ['messagesCount', 'lastMessageId', 'lastReadInboxMessageId']);
|
||||||
|
if (newThreadInfo.isCommentsInfo) {
|
||||||
|
if (newThreadInfo.threadId) {
|
||||||
|
global = updateThreadInfo(
|
||||||
|
global, newThreadInfo.chatId, newThreadInfo.threadId, linkedUpdate, true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (newThreadInfo.fromChannelId && newThreadInfo.fromMessageId) {
|
||||||
|
global = updateThreadInfo(
|
||||||
|
global, newThreadInfo.fromChannelId, newThreadInfo.fromMessageId, linkedUpdate, true,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return replaceThreadParam(global, chatId, threadId, 'threadInfo', newThreadInfo);
|
return replaceThreadParam(global, chatId, threadId, 'threadInfo', newThreadInfo);
|
||||||
@ -494,7 +514,10 @@ export function updateThreadInfos<T extends GlobalState>(
|
|||||||
global: T, updates: Partial<ApiThreadInfo>[],
|
global: T, updates: Partial<ApiThreadInfo>[],
|
||||||
): T {
|
): T {
|
||||||
updates.forEach((update) => {
|
updates.forEach((update) => {
|
||||||
global = updateThreadInfo(global, update.chatId!, update.threadId!, update);
|
global = updateThreadInfo(global,
|
||||||
|
update.isCommentsInfo ? update.originChannelId! : update.chatId!,
|
||||||
|
update.isCommentsInfo ? update.originMessageId! : update.threadId!,
|
||||||
|
update);
|
||||||
});
|
});
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
|
|||||||
@ -248,34 +248,6 @@ export function selectThreadMessagesCount(global: GlobalState, chatId: string, t
|
|||||||
return threadInfo.messagesCount;
|
return threadInfo.messagesCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectThreadOriginChat<T extends GlobalState>(global: T, chatId: string, threadId: number) {
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
|
||||||
return selectChat(global, chatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
|
||||||
|
|
||||||
return selectChat(global, threadInfo?.originChannelId || chatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function selectThreadTopMessageId<T extends GlobalState>(global: T, chatId: string, threadId: number) {
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (chat?.isForum) {
|
|
||||||
return threadId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
|
||||||
if (!threadInfo) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return threadInfo.topMessageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function selectThreadByMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
export function selectThreadByMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
||||||
const threadId = selectThreadIdFromMessage(global, message);
|
const threadId = selectThreadIdFromMessage(global, message);
|
||||||
if (!threadId || threadId === MAIN_THREAD_ID) {
|
if (!threadId || threadId === MAIN_THREAD_ID) {
|
||||||
@ -325,10 +297,12 @@ export function selectIsViewportNewest<T extends GlobalState>(
|
|||||||
} else {
|
} else {
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
if (!threadInfo || !threadInfo.lastMessageId) {
|
if (!threadInfo || !threadInfo.lastMessageId) {
|
||||||
return undefined;
|
if (!threadInfo?.threadId) return undefined;
|
||||||
|
// No messages in thread, except for the thread message itself
|
||||||
|
lastMessageId = threadInfo?.threadId;
|
||||||
|
} else {
|
||||||
|
lastMessageId = threadInfo.lastMessageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastMessageId = threadInfo.lastMessageId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge case: outgoing `lastMessage` is updated with a delay to optimize animation
|
// Edge case: outgoing `lastMessage` is updated with a delay to optimize animation
|
||||||
@ -580,9 +554,9 @@ export function selectAllowedMessageActions<T extends GlobalState>(global: T, me
|
|||||||
);
|
);
|
||||||
|
|
||||||
const threadInfo = selectThreadInfo(global, message.chatId, threadId);
|
const threadInfo = selectThreadInfo(global, message.chatId, threadId);
|
||||||
const isComments = Boolean(threadInfo?.originChannelId);
|
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
const canReply = !isLocal && !isServiceNotification && !chat.isForbidden
|
const canReply = !isLocal && !isServiceNotification && !chat.isForbidden
|
||||||
&& getCanPostInChat(chat, threadId, isComments)
|
&& getCanPostInChat(chat, threadId, isMessageThread)
|
||||||
&& (!messageTopic || !messageTopic.isClosed || messageTopic.isOwner || getHasAdminRight(chat, 'manageTopics'));
|
&& (!messageTopic || !messageTopic.isClosed || messageTopic.isOwner || getHasAdminRight(chat, 'manageTopics'));
|
||||||
|
|
||||||
const hasPinPermission = isPrivate || (
|
const hasPinPermission = isPrivate || (
|
||||||
@ -799,7 +773,7 @@ export function selectRealLastReadId<T extends GlobalState>(global: T, chatId: s
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!threadInfo.lastReadInboxMessageId) {
|
if (!threadInfo.lastReadInboxMessageId) {
|
||||||
return threadInfo.topMessageId;
|
return threadInfo.threadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some previously read messages may be deleted
|
// Some previously read messages may be deleted
|
||||||
@ -977,9 +951,15 @@ export function selectNewestMessageWithBotKeyboardButtons<T extends GlobalState>
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageId = findLast(viewportIds, (id) => selectShouldDisplayReplyKeyboard(global, chatMessages[id]));
|
const messageId = findLast(viewportIds, (id) => {
|
||||||
|
const message = chatMessages[id];
|
||||||
|
return message && selectShouldDisplayReplyKeyboard(global, message);
|
||||||
|
});
|
||||||
|
|
||||||
const replyHideMessageId = findLast(viewportIds, (id) => selectShouldHideReplyKeyboard(global, chatMessages[id]));
|
const replyHideMessageId = findLast(viewportIds, (id) => {
|
||||||
|
const message = chatMessages[id];
|
||||||
|
return message && selectShouldHideReplyKeyboard(global, message);
|
||||||
|
});
|
||||||
|
|
||||||
if (messageId && replyHideMessageId && replyHideMessageId > messageId) {
|
if (messageId && replyHideMessageId && replyHideMessageId > messageId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -1391,20 +1371,18 @@ export function selectTopicLink<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function selectMessageReplyInfo<T extends GlobalState>(
|
export function selectMessageReplyInfo<T extends GlobalState>(
|
||||||
global: T, chatId: string, threadId: number = MAIN_THREAD_ID, additionalReplyInfo?: ApiInputMessageReplyInfo,
|
global: T, chatId: string, threadId: number, additionalReplyInfo?: ApiInputMessageReplyInfo,
|
||||||
) {
|
) {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return undefined;
|
if (!chat) return undefined;
|
||||||
|
const isMainThread = threadId === MAIN_THREAD_ID;
|
||||||
const replyingToTopId = selectThreadTopMessageId(global, chatId, threadId);
|
if (!additionalReplyInfo && isMainThread) return undefined;
|
||||||
|
|
||||||
if (!additionalReplyInfo && !replyingToTopId) return undefined;
|
|
||||||
|
|
||||||
const replyInfo: ApiInputMessageReplyInfo = {
|
const replyInfo: ApiInputMessageReplyInfo = {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
...additionalReplyInfo,
|
...additionalReplyInfo,
|
||||||
replyToMsgId: additionalReplyInfo?.replyToMsgId || replyingToTopId!,
|
replyToMsgId: additionalReplyInfo?.replyToMsgId || threadId,
|
||||||
replyToTopId: additionalReplyInfo?.replyToTopId || replyingToTopId,
|
replyToTopId: additionalReplyInfo?.replyToTopId || (!isMainThread ? threadId : undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
return replyInfo;
|
return replyInfo;
|
||||||
|
|||||||
@ -400,6 +400,11 @@ export type TabState = {
|
|||||||
|
|
||||||
webPagePreview?: ApiWebPage;
|
webPagePreview?: ApiWebPage;
|
||||||
|
|
||||||
|
loadingThread?: {
|
||||||
|
loadingChatId: string;
|
||||||
|
loadingMessageId: number;
|
||||||
|
};
|
||||||
|
|
||||||
forwardMessages: {
|
forwardMessages: {
|
||||||
isModalShown?: boolean;
|
isModalShown?: boolean;
|
||||||
fromChatId?: string;
|
fromChatId?: string;
|
||||||
@ -1176,6 +1181,7 @@ export interface ActionPayloads {
|
|||||||
shouldReplace?: boolean;
|
shouldReplace?: boolean;
|
||||||
};
|
};
|
||||||
openChatWithInfo: ActionPayloads['openChat'] & { profileTab?: ProfileTabType } & WithTabId;
|
openChatWithInfo: ActionPayloads['openChat'] & { profileTab?: ProfileTabType } & WithTabId;
|
||||||
|
openThreadWithInfo: ActionPayloads['openThread'] & WithTabId;
|
||||||
openLinkedChat: { id: string } & WithTabId;
|
openLinkedChat: { id: string } & WithTabId;
|
||||||
loadMoreMembers: WithTabId | undefined;
|
loadMoreMembers: WithTabId | undefined;
|
||||||
setActiveChatFolder: {
|
setActiveChatFolder: {
|
||||||
@ -1211,11 +1217,6 @@ export interface ActionPayloads {
|
|||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
openSupportChat: WithTabId | undefined;
|
openSupportChat: WithTabId | undefined;
|
||||||
focusMessageInComments: {
|
|
||||||
chatId: string;
|
|
||||||
threadId: number;
|
|
||||||
messageId: number;
|
|
||||||
} & WithTabId;
|
|
||||||
openChatByPhoneNumber: {
|
openChatByPhoneNumber: {
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
startAttach?: string | boolean;
|
startAttach?: string | boolean;
|
||||||
@ -1267,6 +1268,8 @@ export interface ActionPayloads {
|
|||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: number;
|
||||||
shouldForceRender?: boolean;
|
shouldForceRender?: boolean;
|
||||||
|
onLoaded?: NoneToVoidFunction;
|
||||||
|
onError?: NoneToVoidFunction;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
sendMessage: {
|
sendMessage: {
|
||||||
text?: string;
|
text?: string;
|
||||||
@ -1400,10 +1403,6 @@ export interface ActionPayloads {
|
|||||||
usernameOrId: string;
|
usernameOrId: string;
|
||||||
isPrivate?: boolean;
|
isPrivate?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
requestThreadInfoUpdate: {
|
|
||||||
chatId: string;
|
|
||||||
threadId: number;
|
|
||||||
};
|
|
||||||
setScrollOffset: {
|
setScrollOffset: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: number;
|
||||||
@ -1769,17 +1768,36 @@ export interface ActionPayloads {
|
|||||||
|
|
||||||
openChat: {
|
openChat: {
|
||||||
id: string | undefined;
|
id: string | undefined;
|
||||||
threadId?: number;
|
|
||||||
type?: MessageListType;
|
type?: MessageListType;
|
||||||
shouldReplaceHistory?: boolean;
|
shouldReplaceHistory?: boolean;
|
||||||
shouldReplaceLast?: boolean;
|
shouldReplaceLast?: boolean;
|
||||||
noForumTopicPanel?: boolean;
|
noForumTopicPanel?: boolean;
|
||||||
noRequestThreadInfoUpdate?: boolean;
|
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
openComments: {
|
openThread: {
|
||||||
id: string;
|
type?: MessageListType;
|
||||||
|
shouldReplaceHistory?: boolean;
|
||||||
|
shouldReplaceLast?: boolean;
|
||||||
|
noForumTopicPanel?: boolean;
|
||||||
|
focusMessageId?: number;
|
||||||
|
} & ({
|
||||||
|
isComments: true;
|
||||||
|
chatId?: string;
|
||||||
|
originMessageId: number;
|
||||||
|
originChannelId: string;
|
||||||
|
} | {
|
||||||
|
isComments?: false;
|
||||||
|
chatId: string;
|
||||||
threadId: number;
|
threadId: number;
|
||||||
originChannelId?: string;
|
}) & WithTabId;
|
||||||
|
// Used by both openThread & openChat
|
||||||
|
processOpenChatOrThread: {
|
||||||
|
chatId: string | undefined;
|
||||||
|
threadId: number;
|
||||||
|
type?: MessageListType;
|
||||||
|
shouldReplaceHistory?: boolean;
|
||||||
|
shouldReplaceLast?: boolean;
|
||||||
|
noForumTopicPanel?: boolean;
|
||||||
|
isComments?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
loadFullChat: {
|
loadFullChat: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
|||||||
@ -63,6 +63,16 @@ export function omit<T extends object, K extends keyof T>(object: T, keys: K[]):
|
|||||||
return pick(object, savedKeys);
|
return pick(object, savedKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function omitUndefined<T extends object>(object: T): T {
|
||||||
|
return Object.keys(object).reduce((result, stringKey) => {
|
||||||
|
const key = stringKey as keyof T;
|
||||||
|
if (object[key] !== undefined) {
|
||||||
|
result[key as keyof T] = object[key];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, {} as T);
|
||||||
|
}
|
||||||
|
|
||||||
export function orderBy<T>(
|
export function orderBy<T>(
|
||||||
collection: T[],
|
collection: T[],
|
||||||
orderRule: (keyof T) | OrderCallback<T> | ((keyof T) | OrderCallback<T>)[],
|
orderRule: (keyof T) | OrderCallback<T> | ((keyof T) | OrderCallback<T>)[],
|
||||||
@ -119,6 +129,29 @@ export function areSortedArraysIntersecting(array1: any[], array2: any[]) {
|
|||||||
export function findIntersectionWithSet<T>(array: T[], set: Set<T>): T[] {
|
export function findIntersectionWithSet<T>(array: T[], set: Set<T>): T[] {
|
||||||
return array.filter((a) => set.has(a));
|
return array.filter((a) => set.has(a));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Exlude elements from base array. Both arrays should be sorted in same order
|
||||||
|
* @param base
|
||||||
|
* @param toExclude
|
||||||
|
* @returns New array without excluded elements
|
||||||
|
*/
|
||||||
|
export function excludeSortedArray<T extends any>(base: T[], toExclude: T[]) {
|
||||||
|
if (!base?.length) return base;
|
||||||
|
|
||||||
|
const result: T[] = [];
|
||||||
|
|
||||||
|
let excludeIndex = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < base.length; i++) {
|
||||||
|
if (toExclude[excludeIndex] === base[i]) {
|
||||||
|
excludeIndex += 1;
|
||||||
|
} else {
|
||||||
|
result.push(base[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function split<T extends any>(array: T[], chunkSize: number) {
|
export function split<T extends any>(array: T[], chunkSize: number) {
|
||||||
const result: T[][] = [];
|
const result: T[][] = [];
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import type { MethodArgs, Methods } from '../api/gramjs/methods/types';
|
|||||||
import type { ApiInitialArgs } from '../api/types';
|
import type { ApiInitialArgs } from '../api/types';
|
||||||
import type { GlobalState } from '../global/types';
|
import type { GlobalState } from '../global/types';
|
||||||
|
|
||||||
import { DATA_BROADCAST_CHANNEL_NAME, DEBUG, MULTITAB_LOCALSTORAGE_KEY } from '../config';
|
import { DATA_BROADCAST_CHANNEL_NAME, MULTITAB_LOCALSTORAGE_KEY } from '../config';
|
||||||
import { selectTabState } from '../global/selectors';
|
import { selectTabState } from '../global/selectors';
|
||||||
import {
|
import {
|
||||||
callApiLocal,
|
callApiLocal,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user